Skip to content

Reflow terminal on layout-driven container resizes#3612

Open
paul-vd wants to merge 1 commit into
pingdotgg:mainfrom
paul-vd:fix/terminal-resize-observer
Open

Reflow terminal on layout-driven container resizes#3612
paul-vd wants to merge 1 commit into
pingdotgg:mainfrom
paul-vd:fix/terminal-resize-observer

Conversation

@paul-vd

@paul-vd paul-vd commented Jun 30, 2026

Copy link
Copy Markdown

What

The terminal only reflowed (sent SIGWINCH to the child process) when the OS window was resized. Resizing the terminal via layout — dragging a split-pane divider, collapsing the sidebar, dragging the editor↔terminal split — left the child process at its stale size.

Why

TerminalViewport's resize effect was gated on window.resize / resizeEpoch. Those only advance on a real window.resize, the drag-handle release, or a visibility toggle. A CSS/flex layout change resizes the xterm DOM node without firing window.resize, so fitAddon.fit() and the terminalResize RPC never ran → no pty.resize() → no SIGWINCH.

Change

  • ThreadTerminalDrawer.tsx — observe the xterm container with a ResizeObserver (rAF-coalesced to one fit per frame) that refits and sends terminalResize directly, instead of depending on window.resize. The existing window/drag/visibility path is left intact.
  • Manager.ts — resize can fail permanently on a Bun build lacking terminal.resize, and the frontend command swallows that error. Log it once per process via Effect.logWarning at the shared resizePtyProcess chokepoint, so the cause is visible in logs without spamming on every resize.

No new dependencies. No lockfile changes.

Testing

Ran a SIGWINCH probe in a terminal, then dragged a split divider (layout-only resize, no window resize):

node -e "process.on('SIGWINCH',()=>console.error('WINCH',process.stdout.columns,process.stdout.rows));setInterval(()=>{},1e9)"
  • Before: no output on a layout-only resize.
  • After: continuous WINCH events with the new cols × rows throughout the drag (cols reflow while rows stay fixed — the signature of a width-only layout resize, distinct from a window resize).

Both apps/web and apps/server typecheck.


Note

Low Risk
Scoped to terminal UI reflow and non-fatal resize logging; no auth, data, or API contract changes.

Overview
Fixes terminals staying at a stale column/row count when only layout changes (split-pane drag, sidebar collapse, editor↔terminal split), because refit/terminalResize previously depended on window.resize and resizeEpoch.

ThreadTerminalDrawer.tsx adds a ResizeObserver on the xterm container, rAF-coalesced like the existing resize effect, to run fitAddon.fit() and terminalResize when the container size changes without a window resize event.

Manager.ts logs Effect.logWarning once per PTY process in resizePtyProcess when resize fails permanently (e.g. Bun without terminal.resize), since the web client swallows resize errors.

Reviewed by Cursor Bugbot for commit e3e879d. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Reflow terminal on layout-driven container resizes

  • Attaches a ResizeObserver in ThreadTerminalDrawer.tsx so the terminal refits and emits a resize to the backend when its container changes size (e.g. split-pane drag, sidebar collapse), not only on window resize.
  • Adds per-process warning deduplication in Manager.ts using a WeakSet: when process.resize fails, a warning is logged once per process with thread, terminal, pid, and cause.

Macroscope summarized e3e879d.

The xterm resize path was gated on window.resize / resizeEpoch, so
container resizes driven by CSS/flex layout (split-pane drag, sidebar
collapse) changed the terminal's DOM size without firing fit() or sending
terminalResize. The pty never resized, so no SIGWINCH reached the child.

Observe the xterm container with a ResizeObserver (rAF-coalesced to one
fit per frame) and refit + send terminalResize directly, instead of
relying on window.resize.

Also surface resize failures: when a process resize throws (e.g. a Bun
build without terminal.resize), the frontend command swallows the error.
Log it once per process via Effect.logWarning at the shared
resizePtyProcess chokepoint so the cause is visible without spamming.
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2ffeb93a-6288-4945-a834-2e8b55aaaf7f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@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 Jun 30, 2026

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e3e879db92

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +813 to +817
fitTerminalSafely(fitAddon);
if (wasAtBottom) {
terminal.scrollToBottom();
}
void resizeTerminal(terminal.cols, terminal.rows);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid resizing hidden terminals to minimum geometry

When switching away from a thread, retained terminal drawers are kept mounted but wrapped in className="hidden" in PersistentThreadTerminalDrawer; that display:none size change also reaches this observer. Because the callback still calls fit() and sends the resulting size to the server, background terminals can be resized to xterm's minimum/zero-container geometry and receive a SIGWINCH while hidden, which can break full-screen/background processes and corrupt wrapping until the thread is shown again. Skip the resize when the observed container size is 0 or pause the observer while the drawer is not visible.

Useful? React with 👍 / 👎.

@macroscopeapp

macroscopeapp Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

This PR adds new ResizeObserver behavior for terminal resizing. An unresolved review comment identifies a potential bug where hidden terminals could be resized to zero geometry, potentially corrupting terminal state. This concern should be addressed before merging.

You can customize Macroscope's approvability policy. Learn more.

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.

1 participant