Reflow terminal on layout-driven container resizes - #3612

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

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-vdpaul-vd commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

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

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-actionsgithub-actionsBot 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-connectorchatgpt-codex-connectorBot 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

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.

@t3-code

t3-codeBot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

superseded by #4860

@t3-codet3-codeBot closed this Jul 31, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@paul-vd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Reflow terminal on layout-driven container resizes - #3612

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

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-vdpaul-vd commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

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

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-actionsgithub-actionsBot 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-connectorchatgpt-codex-connectorBot 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

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.

@t3-code

t3-codeBot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

superseded by #4860

@t3-codet3-codeBot closed this Jul 31, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@paul-vd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Reflow terminal on layout-driven container resizes - #3612

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

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-vdpaul-vd commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

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

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-actionsgithub-actionsBot 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-connectorchatgpt-codex-connectorBot 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

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.

@t3-code

t3-codeBot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

superseded by #4860

@t3-codet3-codeBot closed this Jul 31, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@paul-vd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Reflow terminal on layout-driven container resizes - #3612

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

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-vdpaul-vd commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

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

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-actionsgithub-actionsBot 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-connectorchatgpt-codex-connectorBot 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

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.

@t3-code

t3-codeBot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

superseded by #4860

@t3-codet3-codeBot closed this Jul 31, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@paul-vd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Reflow terminal on layout-driven container resizes - #3612

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

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-vdpaul-vd commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

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

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-actionsgithub-actionsBot 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-connectorchatgpt-codex-connectorBot 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

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.

@t3-code

t3-codeBot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

superseded by #4860

@t3-codet3-codeBot closed this Jul 31, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@paul-vd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Reflow terminal on layout-driven container resizes - #3612

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

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-vdpaul-vd commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

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

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-actionsgithub-actionsBot 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-connectorchatgpt-codex-connectorBot 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

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.

@t3-code

t3-codeBot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

superseded by #4860

@t3-codet3-codeBot closed this Jul 31, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@paul-vd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Reflow terminal on layout-driven container resizes - #3612

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

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-vdpaul-vd commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

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

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-actionsgithub-actionsBot 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-connectorchatgpt-codex-connectorBot 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

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.

@t3-code

t3-codeBot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

superseded by #4860

@t3-codet3-codeBot closed this Jul 31, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@paul-vd
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Reflow terminal on layout-driven container resizes - #3612

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

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-vdpaul-vd commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

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

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-actionsgithub-actionsBot 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-connectorchatgpt-codex-connectorBot 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

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.

@t3-code

t3-codeBot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

superseded by #4860

@t3-codet3-codeBot closed this Jul 31, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M30-99 changed lines (additions + deletions).vouch:unvouchedPR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@paul-vd