Skip to content

fix(desktop): sync v1 terminal dimensions to backend on connect#3545

Merged
Kitenite merged 1 commit intomainfrom
vscode-xterm-row-col-initialization-for-v1
Apr 18, 2026
Merged

fix(desktop): sync v1 terminal dimensions to backend on connect#3545
Kitenite merged 1 commit intomainfrom
vscode-xterm-row-col-initialization-for-v1

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Apr 18, 2026

Summary

  • When panes mount during new-workspace / preset flows, attachToContainer's fitAddon.fit() can run before flex layout resolves. The backend PTY gets spawned at stale (often default) dimensions, so the initial shell prompt wraps at the wrong column until the user manually resizes.
  • Fix mirrors v2's pattern in terminal-ws-transport.ts (sendResize on WebSocket open): once createOrAttach succeeds, re-fit against the now-settled container and push the real dims to the backend.
  • Applied at both onSuccess sites — initial attach and restartTerminalSession — via a small syncBackendDimensions helper.

Test plan

  • Create a new workspace with a preset that auto-spawns terminals; confirm tput cols; tput lines matches the visible grid immediately, with no manual resize.
  • Run printf '=%.0s' {1..$(tput cols)}; echo — line fills exactly one row (no early wrap / overflow).
  • Restart a running workspace-run pane; dims should re-sync on the restart path too.
  • Switch between workspaces rapidly — fresh terminals render at correct size on first paint.
  • Sanity-check an existing workspace cold-restore still works.

Summary by cubic

Sync v1 terminal cols/rows to the backend right after connect to prevent shells spawning at stale sizes. Re-fit after layout settles so the PTY starts with correct dimensions and prompts don’t wrap.

  • Bug Fixes
    • Add syncBackendDimensions to call fitAddon.fit() and send resize after createOrAttach and on restartTerminalSession, mirroring v2’s send-resize-on-open and guarding against zero-sized containers.

Written for commit 7e785d5. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed terminal display sizing after restart and initial connection to ensure dimensions are properly applied and functioning correctly.

When panes mount during new-workspace / preset flows, attachToContainer's
fit() can run before flex layout resolves, leaving the backend PTY spawned
at stale defaults (shell prompt wraps wrong until a manual resize).

Mirror v2's sendResize-on-open pattern: once createOrAttach succeeds,
re-fit against the now-settled container and push the real dims.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 18, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e18118ff-d2f9-42be-b585-cc423fa1acc8

📥 Commits

Reviewing files that changed from the base of the PR and between 1979f4c and 7e785d5.

📒 Files selected for processing (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts

📝 Walkthrough

Walkthrough

A syncBackendDimensions helper was added to the terminal lifecycle hook to synchronize terminal dimensions with the backend. The helper calls fitAddon.fit() and then resizeRef.current() to push the settled terminal size. It is invoked in two newly extended success paths following terminal restart and initial attachment.

Changes

Cohort / File(s) Summary
Terminal Lifecycle Enhancement
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts
Added syncBackendDimensions helper to synchronize container dimensions with backend after terminal restart and initial attachment. Includes guards against stale measurements when container dimensions are zero.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Poem

🐰 A terminal grows tall and wide,
With dimensions now synchronized with pride,
Restart or attach, the backend knows,
How many cols and rows it grows!
Hop-hop, the panes are finally right!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch vscode-xterm-row-col-initialization-for-v1

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 and usage tips.

@Kitenite Kitenite merged commit 867ef87 into main Apr 18, 2026
6 of 7 checks passed
@Kitenite Kitenite deleted the vscode-xterm-row-col-initialization-for-v1 branch April 18, 2026 05:48
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 18, 2026

Greptile Summary

This PR fixes a race condition in the v1 terminal (desktop app) where fitAddon.fit() runs inside attachToContainer before the flex layout has resolved, causing the backend PTY to spawn at stale (often default) column/row dimensions. The fix introduces a syncBackendDimensions helper that re-fits against the now-settled container after createOrAttach resolves and pushes the correct dimensions to the backend — mirroring the existing v2 sendResize-on-WebSocket-open pattern. It is applied at both onSuccess sites: initial attach and restartTerminalSession.

Key observations:

  • The clientWidth === 0 || clientHeight === 0 guard is a sound safety valve; detached/hidden containers will have zero client dimensions, preventing spurious resize RPCs on unmount.
  • In the initial attach path, syncBackendDimensions is correctly placed after markTerminalSessionReady and stream setup, but before scrollback-restore early returns — so the backend PTY always gets the right size regardless of cold-restore state.
  • In the restartTerminalSession path, there is no isUnmounted guard in onSuccess (pre-existing), but the zero-dimension check on the detached container provides an implicit guard for the unmount case.
  • The existing attachToContainer already attempts a resize when dims differ from cache; syncBackendDimensions may fire a second resize RPC on the same attach. This double-fire is benign (idempotent) but worth noting.

Confidence Score: 5/5

Safe to merge — the fix is narrow, well-scoped, and mirrors the existing v2 pattern with no new risk surface.

Single-file change adds a small helper called in exactly two onSuccess callbacks. The zero-dimension guard prevents spurious RPCs on hidden/unmounted containers, the async ordering is correct at both call sites, and the worst-case failure mode (layout still partially unsettled) silently degrades back to the pre-fix behavior rather than introducing new bugs. Both P2 comments are non-blocking style suggestions.

No files require special attention.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts Adds syncBackendDimensions helper called in both createOrAttach.onSuccess sites (initial attach and restart) to re-fit the terminal against the settled container and push correct PTY dimensions to the backend. Logic is correct; two non-blocking style suggestions noted.

Sequence Diagram

sequenceDiagram
    participant React as React Mount
    participant ATC as attachToContainer
    participant RO as ResizeObserver
    participant COA as createOrAttach (IPC)
    participant SBD as syncBackendDimensions
    participant BE as Backend PTY

    React->>ATC: attachToContainer(paneId, container, onResize)
    ATC->>ATC: fitAddon.fit() (layout may not have settled)
    ATC->>RO: register ResizeObserver
    ATC-->>BE: resizeRef (if dims changed vs cache)
    note over ATC,BE: Stale dims if flex not yet resolved

    React->>COA: createOrAttach({ cols, rows, ... })
    note over COA: async IPC — layout settles during this time

    COA-->>React: onSuccess(result)
    React->>SBD: syncBackendDimensions()
    SBD->>SBD: guard clientWidth/clientHeight == 0?
    SBD->>ATC: fitAddon.fit() (settled container)
    SBD-->>BE: resizeRef({ cols: xterm.cols, rows: xterm.rows })
    note over SBD,BE: Correct dims pushed after layout settled

    RO-->>BE: future resizeRef on user window resize
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts
Line: 268-272

Comment:
**Silent skip when layout still unresolved**

The guard bails out if `clientWidth === 0 || clientHeight === 0`, which is the right thing to do for a fully hidden/detached container. However, if the flex layout has only *partially* resolved (e.g. the container has the correct width but the height is still collapsing), the guard passes and `fitAddon.fit()` is called with an intermediate height — meaning the bug this PR is fixing could still manifest in that narrow timing window.

A small hardening option: also check that `xterm.cols > 0 && xterm.rows > 0` after `fitAddon.fit()` before calling `resizeRef`, so a degenerate fit result is never forwarded:

```suggestion
		const syncBackendDimensions = () => {
			if (container.clientWidth === 0 || container.clientHeight === 0) return;
			fitAddon.fit();
			if (xterm.cols > 0 && xterm.rows > 0) {
				resizeRef.current({ paneId, cols: xterm.cols, rows: xterm.rows });
			}
		};
```

This is defensive rather than strictly required — in practice, a non-zero-size container should always produce valid dimensions from `fitAddon.fit()`.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts
Line: 394-397

Comment:
**`syncBackendDimensions` ordering in restart path**

In the initial attach `onSuccess`, `syncBackendDimensions` is called after `markTerminalSessionReady` and `startStream` — i.e. after the backend session is fully live. In the `restartTerminalSession` `onSuccess`, it is called first, before `pendingInitialStateRef.current = result` and `maybeApplyInitialState()`.

The restart path doesn't call `v1TerminalCache.startStream` / `setStreamReady` (the stream is already running from the previous session), so calling `resizeRef` at this point is safe — the backend session exists as soon as `createOrAttach` resolves. The ordering is fine for correctness, but a brief comment here (similar to the one in the initial-attach path) would make the intent clear to future readers.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(desktop): sync v1 terminal dimension..." | Re-trigger Greptile

Comment on lines +268 to +272
const syncBackendDimensions = () => {
if (container.clientWidth === 0 || container.clientHeight === 0) return;
fitAddon.fit();
resizeRef.current({ paneId, cols: xterm.cols, rows: xterm.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 Silent skip when layout still unresolved

The guard bails out if clientWidth === 0 || clientHeight === 0, which is the right thing to do for a fully hidden/detached container. However, if the flex layout has only partially resolved (e.g. the container has the correct width but the height is still collapsing), the guard passes and fitAddon.fit() is called with an intermediate height — meaning the bug this PR is fixing could still manifest in that narrow timing window.

A small hardening option: also check that xterm.cols > 0 && xterm.rows > 0 after fitAddon.fit() before calling resizeRef, so a degenerate fit result is never forwarded:

Suggested change
const syncBackendDimensions = () => {
if (container.clientWidth === 0 || container.clientHeight === 0) return;
fitAddon.fit();
resizeRef.current({ paneId, cols: xterm.cols, rows: xterm.rows });
};
const syncBackendDimensions = () => {
if (container.clientWidth === 0 || container.clientHeight === 0) return;
fitAddon.fit();
if (xterm.cols > 0 && xterm.rows > 0) {
resizeRef.current({ paneId, cols: xterm.cols, rows: xterm.rows });
}
};

This is defensive rather than strictly required — in practice, a non-zero-size container should always produce valid dimensions from fitAddon.fit().

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts
Line: 268-272

Comment:
**Silent skip when layout still unresolved**

The guard bails out if `clientWidth === 0 || clientHeight === 0`, which is the right thing to do for a fully hidden/detached container. However, if the flex layout has only *partially* resolved (e.g. the container has the correct width but the height is still collapsing), the guard passes and `fitAddon.fit()` is called with an intermediate height — meaning the bug this PR is fixing could still manifest in that narrow timing window.

A small hardening option: also check that `xterm.cols > 0 && xterm.rows > 0` after `fitAddon.fit()` before calling `resizeRef`, so a degenerate fit result is never forwarded:

```suggestion
		const syncBackendDimensions = () => {
			if (container.clientWidth === 0 || container.clientHeight === 0) return;
			fitAddon.fit();
			if (xterm.cols > 0 && xterm.rows > 0) {
				resizeRef.current({ paneId, cols: xterm.cols, rows: xterm.rows });
			}
		};
```

This is defensive rather than strictly required — in practice, a non-zero-size container should always produce valid dimensions from `fitAddon.fit()`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 394 to +397
return;
}
setConnectionError(null);
syncBackendDimensions();
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 syncBackendDimensions ordering in restart path

In the initial attach onSuccess, syncBackendDimensions is called after markTerminalSessionReady and startStream — i.e. after the backend session is fully live. In the restartTerminalSession onSuccess, it is called first, before pendingInitialStateRef.current = result and maybeApplyInitialState().

The restart path doesn't call v1TerminalCache.startStream / setStreamReady (the stream is already running from the previous session), so calling resizeRef at this point is safe — the backend session exists as soon as createOrAttach resolves. The ordering is fine for correctness, but a brief comment here (similar to the one in the initial-attach path) would make the intent clear to future readers.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts
Line: 394-397

Comment:
**`syncBackendDimensions` ordering in restart path**

In the initial attach `onSuccess`, `syncBackendDimensions` is called after `markTerminalSessionReady` and `startStream` — i.e. after the backend session is fully live. In the `restartTerminalSession` `onSuccess`, it is called first, before `pendingInitialStateRef.current = result` and `maybeApplyInitialState()`.

The restart path doesn't call `v1TerminalCache.startStream` / `setStreamReady` (the stream is already running from the previous session), so calling `resizeRef` at this point is safe — the backend session exists as soon as `createOrAttach` resolves. The ordering is fine for correctness, but a brief comment here (similar to the one in the initial-attach path) would make the intent clear to future readers.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 18, 2026
Manually port upstream 867ef87 (superset-sh#3545) to fork's useTerminalLifecycle.
Straight cherry-pick failed because fork has diverged around cold-restore
handling — notably the 2nd createOrAttach success handler does NOT call
markTerminalSessionReady / startStream here (deferred until the real
shell spawns), so upstream's syncBackendDimensions() call point shifts
up by a few lines.

Adds the same syncBackendDimensions() helper and calls it at both
createOrAttach onSuccess sites so the backend PTY sees the settled
container dims before flex layout has a chance to resize (preset tabs,
new workspace bulk creation were spawning at stale defaults).
MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 18, 2026
…m-sync

upstream取り込み: v1 terminal dimensions sync fix (superset-sh#3545, 手動移植)
MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 18, 2026
…al dim sync)

- 1979f4c fix(desktop): v2 sidebar section count reflects visually grouped workspaces (superset-sh#3544) → PR #315 (clean cherry-pick)
- 867ef87 fix(desktop): sync v1 terminal dimensions to backend on connect (superset-sh#3545) → PR #316 (manual port for fork cold-restore divergence)
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