Skip to content

[Bug]: Shared terminal is clipped when desktop and native Android use different sizes #14463

Description

@yqecea

Area

apps/server, apps/mobile, apps/web

Steps to reproduce

  1. Connect the native Android app and a desktop client to the same T3 environment.
  2. Open the same terminal in both clients, with substantially different widths.
  3. Run a full-screen terminal CLI. My normal use case is running agent harnesses such as Devin or OpenCode directly in the terminal.
  4. Resize either client or open the phone keyboard, leaving both terminals attached.

Expected behavior

Both clients remain usable. The terminal should have a defined policy for its shared PTY dimensions, and attached renderers should agree with those dimensions. The smaller client should be able to see the whole TUI, for example by using a shared grid with scrolling or an explicit size owner.

Actual behavior

The TUI fits one device but is clipped or leaves an unexpectedly narrow layout on the other. A desktop-sized TUI can be cut off on Android; after the phone resizes it, desktop output can stay constrained to the phone's columns.

Impact

Major degradation when moving between desktop and phone while keeping an agent session alive.

Environment and evidence

The original report is from the native Android app, not a mobile browser. The exact installed mobile version is unknown. Source inspection used main at c2fa9fc.

The server currently keeps one cols/rows pair per terminal. openOrAttachForStream and resizeLocked in apps/server/src/terminal/Manager.ts resize that shared PTY to the requesting client's dimensions. TerminalResizeInput has no client identity or size ownership, and there is no geometry-change event that makes the other renderer adopt the new grid.

The existing server test "resizes running terminal on open when a different size is requested" confirms the last-attaching-client behavior. In an isolated web dev environment I attached two views to the same terminal, left one surface 1146px wide and narrowed the other to 430px. Running stty size from the wide view reported 15 54, consistent with the narrow client's PTY width. This is browser evidence for shared server behavior, not native Android verification.

This report is separate from Android Backspace #8253 and per-key echo latency #12756. Incremental output and input batching work in #11235 and #9456 may help latency, but do not establish shared terminal geometry.

Workaround

Only keep one terminal view attached at a time. This defeats seamless desktop/phone use.

Activity

  1. juliusmarminge commented on Sep 30, 2026

    @juliusmarminge
    Member

    Triage

    Confirmed on main (c2fa9fc). A terminal session has a single PTY size, and whichever client last sent its dimensions wins. The other client isn't told, so its local grid drifts away from the PTY. That explains both symptoms you described: a desktop-sized TUI clipped on the phone, and a phone resize leaving the desktop TUI stuck at the phone's column count.

    Server side. TerminalResizeInput only carries threadId, terminalId, cols, and rows, with no client identity. resizeLocked in apps/server/src/terminal/Manager.ts resizes the shared PTY, stores session.cols / session.rows, and returns without publishing anything. TerminalSessionSnapshot and TerminalSummary don't include geometry, and TerminalEvent has no resize event, so an attached renderer never learns about the new grid. manager.open does the same last-writer update when an existing session is opened at a different size, and the test "resizes running terminal on open when a different size is requested" locks that behavior in.

    Client side. Both clients write their own size into that one slot:

    • Native Android sends cols / rows on attach (ThreadTerminalRouteScreen) and again from handleResize. The native view fires that on any size change, including the keyboard opening (T3TerminalView.onSizeChanged → emitResize).
    • The web drawer doesn't send a size on attach. After layout, Ghostty's fit() calls onResize, which sends terminal.resize (debounced 150 ms in surface.ts). So attaching or resizing the desktop panel overwrites the phone's size, and vice versa.

    A container that doesn't change size won't re-fit, so once the phone is the last writer, stty size on the wide client stays at the phone's grid. That matches your 15×54 reading from the 1146 px view while a 430 px view was attached.

    This isn't the same as #8253 (open issue, Android Backspace) or #12756 (open issue, per-key echo latency), and the open PRs #11235 (incremental output) and #9456 (input batching) don't touch geometry. Nothing else currently tracks shared PTY size.

    Since a PTY can only have one size, the fix needs a policy. Either one client owns the size and the others scroll or scale to that grid, or there's an explicit shared grid plus a geometry event that every attached renderer applies. Until then, your workaround is the right one: keep only one terminal view attached at a time. Thanks for the careful reproduction.

  2. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions