Skip to content

fix(terminal): mobile scroll — horizontal overflow, scroll-to-bottom, touch-swipe, FitAddon clipping - #135

Open
mateusbellozupko wants to merge 4 commits into
evolution-foundation:mainfrom
mateusbellozupko:fix/terminal-mobile-scroll
Open

mateusbellozupko wants to merge 4 commits into
evolution-foundation:mainfrom
mateusbellozupko:fix/terminal-mobile-scroll

Conversation

@mateusbellozupko

@mateusbellozupko mateusbellozupko commented Sep 21, 2026 •

Copy link
Copy Markdown

Summary

Four related fixes for the terminal and chat experience on mobile (and partially desktop), applied as a batch:

  • fix(terminal): skip forced scroll-to-bottom on silent mobile reconnect — Mobile backgrounding (screen lock, app switch) triggers a silent WS reconnect via visibilitychange. Each reconnect re-fired session_joined which force-scrolled to bottom, overriding any user scroll position. Now uses isSilentReconnectRef to distinguish silent reconnects from real session opens. Also adds ResizeObserver on the container so fit() re-runs when the mobile browser's address bar collapses/expands.

  • fix(chat): prevent horizontal scroll in chat/terminal on mobile — CSS Overflow spec forces overflow-x to compute as auto whenever overflow-y is non-visible. Added explicit overflow-x: hidden on the chat message div, .xterm-viewport { overflow-x: hidden !important } in index.css, and overflow-wrap: break-word on .markdown-content for long unbroken tokens — while preserving overflow-x: auto on <pre> code blocks.

  • fix(terminal): wire touch-swipe scrolling for mobile xterm sessions — xterm.js v6's custom scroll widget only wires mouse wheel and scrollbar dragging, no touch events. Dispatches a synthetic WheelEvent on .xterm-screen (not the container — events bubble up) from a vertical swipe gesture, reusing xterm's existing wheel pipeline (scrollback when present, SGR mouse-report forwarding to the CLI's alt-screen otherwise). Direction disambiguation with 10px threshold; passive: false only on touchmove.

  • fix(terminal): separate padding wrapper from xterm mount div to fix FitAddon row clipping — FitAddon._proposeGeometry() reads parentElement.clientHeight which per the CSS spec includes padding. The xterm mount div had px-4 py-3 directly on it, causing FitAddon to over-count rows by ~1–2 (24px padding ÷ ~16px/row). Those rows rendered but were clipped by the ancestor overflow-hidden, making the last lines invisible on both desktop and mobile. Padding moved to a wrapper div; containerRef is now a bare h-full w-full inner div.

Test plan

  • Open terminal on mobile — verify no horizontal scroll on the page
  • Open terminal on mobile — verify last line of output is visible (not clipped)
  • Open terminal on mobile — verify swipe up/down scrolls terminal output
  • Background the browser (lock screen or switch app) and return — verify terminal reconnects and does NOT snap back to bottom if you had scrolled up
  • Open a new session — verify it opens showing the latest output (scroll-to-bottom on real open still works)
  • Open chat on mobile — verify no horizontal scroll; long URLs/hashes wrap; code blocks still scroll horizontally

🤖 Generated with Claude Code

Summary by Sourcery

Improve mobile terminal and chat scrolling, sizing, and reconnect behavior without disrupting normal session navigation.

Bug Fixes:

  • Prevent terminal sessions from jumping to the bottom after silent mobile background reconnects while preserving scroll-to-bottom behavior for real session opens.
  • Enable touch-swipe scrolling for mobile terminal sessions.
  • Prevent horizontal overflow in mobile chat and terminal views while retaining horizontal scrolling for code blocks.
  • Fix terminal viewport clipping so the final rows of output remain visible across desktop and mobile layouts.

Enhancements:

  • Re-fit terminal dimensions when its container changes size, including mobile browser UI and layout changes.

mateusbellozupko and others added 4 commits September 21, 2026 20:39
Mobile backgrounding (screen lock, app switch, flaky network) triggers
frequent silent WS reconnects via the visibilitychange handler. Each
reconnect re-fired session_joined, which force-scrolled the terminal
to bottom and wiped out any manual scroll-up — making the terminal
feel like it couldn't be scrolled. Now the forced scroll is skipped
specifically on the silent-reconnect path, while genuine session
opens/switches still land on latest output as intended.

Ticket: 84e682fc-d8a6-4707-806e-5998412de5ed

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CSS Overflow spec forces overflow-x: auto whenever overflow-y is
non-visible, so the chat message list and xterm-viewport became
independently scrollable/draggable regions on mobile whenever content
was even slightly wider than the container. Adds explicit
overflow-x: hidden plus word-break/overflow-wrap rules for long
unbroken tokens (URLs, hashes, paths) in markdown content, while
leaving <pre> code blocks free to scroll horizontally as intended.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
xterm.js v6's bundled scrollbar widget only wires mouse wheel and scrollbar
dragging — no touch-pan support. Dispatches a synthetic wheel event on
xterm's screen element from a vertical swipe gesture, reusing xterm's
existing wheel pipeline (scrollback when present, SGR mouse-report
forwarding to the CLI's alt-screen otherwise) instead of calling
scrollLines() directly, which is a no-op with no scrollback.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…itAddon row clipping

FitAddon._proposeGeometry() reads parentElement.clientHeight which per the
CSS spec includes padding. The xterm mount div had px-4 py-3 directly on it,
so FitAddon over-counted rows by ~24px / ~16px-per-row ≈ 1-2 rows. Those
rows rendered but were clipped by the ancestor overflow-hidden, making the
last 1-2 lines invisible on both desktop and mobile.

Fix: padding moved to a wrapper div; containerRef is now a bare h-full w-full
inner div so FitAddon measures the exact content area. Visual breathing room
is preserved. ResizeObserver still targets containerRef (correct — it resizes
with the wrapper).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @mateusbellozupko, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 1 day and 21 hours by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Improves mobile terminal and chat usability by separating silent reconnect behavior from real opens, adding container-aware fitting and touch swipe scrolling, preventing unintended horizontal overflow, and fixing FitAddon row clipping through a padded wrapper and bare mount element.

Sequence diagram for mobile terminal reconnect and scroll behavior

sequenceDiagram
    participant Browser
    participant AgentTerminal
    participant WebSocket
    participant XTerm

    Browser->>AgentTerminal: visibilitychange
    AgentTerminal->>AgentTerminal: setReconnectKey()
    AgentTerminal->>WebSocket: connect and join session
    WebSocket-->>AgentTerminal: session_joined
    alt silent reconnect
        AgentTerminal->>XTerm: preserve current scroll position
    else real session open
        AgentTerminal->>XTerm: scrollToBottom()
    end
Loading

Sequence diagram for mobile terminal touch scrolling

sequenceDiagram
    actor User
    participant TerminalMount
    participant XTermScreen
    participant XTermWheelPipeline

    User->>TerminalMount: touchstart
    User->>TerminalMount: touchmove
    TerminalMount->>TerminalMount: determine vertical swipe after 10px threshold
    TerminalMount->>XTermScreen: dispatch WheelEvent
    XTermScreen->>XTermWheelPipeline: bubble wheel event
    XTermWheelPipeline-->>XTermScreen: scroll scrollback or forward SGR mouse report
Loading

File-Level Changes

Change Details Files
Preserve terminal scroll position across silent mobile reconnects while retaining bottom-pinning for real session opens.
  • Track visibility-triggered reconnects separately from user-initiated session connections.
  • Skip scroll-to-bottom only for silent reconnects and reset the marker after handling the join.
  • Reconnect when the page becomes visible and the WebSocket is no longer open.
dashboard/frontend/src/components/AgentTerminal.tsx
Make terminal sizing and interaction resilient to mobile viewport changes and touch input.
  • Re-fit xterm when its mount container changes size using ResizeObserver.
  • Translate confirmed vertical single-finger swipes into synthetic wheel events on xterm’s screen so both scrollback and CLI mouse-report handling are reused.
  • Ignore taps, horizontal gestures, and multi-touch while preventing native scrolling only for vertical swipes.
dashboard/frontend/src/components/AgentTerminal.tsx
Eliminate unintended horizontal overflow while preserving horizontal scrolling for code blocks.
  • Explicitly hide horizontal overflow in chat messages and the xterm viewport.
  • Wrap long unbroken chat text and markdown tokens.
  • Override wrapping within preformatted code blocks so their horizontal scrolling behavior remains intact.
dashboard/frontend/src/components/AgentChat.tsx
dashboard/frontend/src/index.css
Correct FitAddon’s measured terminal area so the final rows are not clipped.
  • Move terminal padding to an outer wrapper.
  • Use a bare full-size inner mount element for xterm and FitAddon measurement.
dashboard/frontend/src/components/AgentTerminal.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@mateusbellozupko

Copy link
Copy Markdown
Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

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