From 74c9879359eab0f872254d34c629b425e7946ade Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Thu, 20 Aug 2026 10:54:27 +0200 Subject: [PATCH] fix(tui): size every switchable session, not just the one being attached MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switching with Alt+N landed in a pane that filled part of the terminal with tmux padding the rest as a dot grid — reported from the beta with a screenshot showing the pane in the left half and dots everywhere else. Codeman pins every window `window-size manual` at the BROWSER's size (tmux-manager.ts), so no attaching client can resize it. The attach already lifted that for the session it opened, which is why a plain attach looked right; `switch-client` then moved the user into a session that had never been lifted, and the old pin reasserted itself. `window-size latest` now goes on every session the strip can reach, alongside the bar those sessions already get, and each one's original sizing is snapshotted and restored on detach. Verified by round-tripping a session pinned at 120x40 manual: latest 190x49 while attached, back to 120x40 manual after, with no dot rows at either step and the bar intact at full width after a switch. --- src/tui/tui-app.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/tui/tui-app.ts b/src/tui/tui-app.ts index 20d2d284..e2dd1ef8 100644 --- a/src/tui/tui-app.ts +++ b/src/tui/tui-app.ts @@ -58,6 +58,7 @@ import { ATTACH_BANNER_MARKER, TuiClient, type TuiSessionOptions, + type TuiWindowSizing, type TuiApprovalAnswer, type TuiEventStream, type TuiLiveSessionMetrics, @@ -541,8 +542,6 @@ export async function beginAttachHandoff( // dots. `latest` (not a one-off resize to our size) is also what makes a // terminal resized MID-attach follow along: tmux recomputes on every SIGWINCH // and the caller is blocked in `spawnSync`. - const sizing = await client.readWindowSizing(muxName); - await client.followAttachingClient(muxName); // Claimed only when tmux has nothing there: an attach must never shadow a // binding the user put in their own config. const alias = heldCtrlAlias(detachKey ?? DEFAULT_DETACH_KEY); @@ -593,6 +592,7 @@ export async function beginAttachHandoff( // you actually are. const banner = bannerFor(muxName, label); const dressed: Array<{ muxName: string; options: TuiSessionOptions }> = []; + const resized: Array<{ muxName: string; sizing: TuiWindowSizing }> = []; const targets = new Map([[muxName, label]]); for (const tab of tabs.slice(0, 9)) { if (tab.muxName && !targets.has(tab.muxName)) targets.set(tab.muxName, tab.label); @@ -601,6 +601,16 @@ export async function beginAttachHandoff( const snapshot = await client.readSessionOptions(target, Object.keys(banner)); if (snapshot) dressed.push({ muxName: target, options: snapshot }); await client.applySessionOptions(target, bannerFor(target, targetLabel)); + // ⚠️ Sizing for EVERY switchable session, not just the one being attached. + // Codeman pins each window `window-size manual` at the BROWSER's size, so a + // session switched into from inside a pane stays pinned and tmux pads the + // gap with dots — the pane filled half the terminal and the rest was a dot + // grid (reported from the beta, with a screenshot, after Alt+N switching + // shipped). `latest` makes each window follow whichever client is looking + // at it, which is the same reason the attached one gets it. + const sizing = await client.readWindowSizing(target); + if (sizing) resized.push({ muxName: target, sizing }); + await client.followAttachingClient(target); } return { chord: oneKey ? ONE_KEY_DETACH : detachChord(prefix, detachKey), @@ -612,7 +622,7 @@ export async function beginAttachHandoff( // back to the pane, and the resize is what re-pins the browser's // authority over the window. for (const entry of dressed) await client.restoreSessionOptions(entry.muxName, entry.options); - if (sizing) await client.restoreWindowSizing(muxName, sizing); + for (const entry of resized) await client.restoreWindowSizing(entry.muxName, entry.sizing); }, }; }