diff --git a/src/features/team-mode/team-layout-tmux/rebalance-team-window.test.ts b/src/features/team-mode/team-layout-tmux/rebalance-team-window.test.ts
new file mode 100644
index 000000000..d392b6c15
--- /dev/null
+++ b/src/features/team-mode/team-layout-tmux/rebalance-team-window.test.ts
@@ -0,0 +1,84 @@
+///
+
+import { beforeEach, describe, expect, it, mock } from "bun:test"
+
+import {
+ rebalanceTeamWindowWith,
+ type RebalanceTeamWindowDeps,
+} from "./rebalance-team-window"
+
+describe("rebalanceTeamWindowWith", () => {
+ let runTmux: RebalanceTeamWindowDeps["runTmux"]
+ let log: RebalanceTeamWindowDeps["log"]
+ let calls: Array>
+
+ beforeEach(() => {
+ calls = []
+ runTmux = mock(async (args: string[]): Promise<{ success: boolean }> => {
+ calls.push(args)
+ return { success: true }
+ })
+ log = mock((): void => undefined)
+ })
+
+ it("#given main-vertical #when rebalance #then select-layout, set main-pane-width 60%, re-select-layout", async () => {
+ // given
+ const deps: RebalanceTeamWindowDeps = { runTmux, log }
+
+ // when
+ const result = await rebalanceTeamWindowWith("@1", "main-vertical", deps)
+
+ // then
+ expect(result).toBe(true)
+ expect(calls).toEqual([
+ ["select-layout", "-t", "@1", "main-vertical"],
+ ["set-window-option", "-t", "@1", "main-pane-width", "60%"],
+ ["select-layout", "-t", "@1", "main-vertical"],
+ ])
+ })
+
+ it("#given focus windowId and pane-list shrunk from 3 to 2 #when rebalanceTeamWindow runs #then select-layout is invoked with main-vertical", async () => {
+ // given
+ const deps: RebalanceTeamWindowDeps = { runTmux, log }
+
+ // when
+ const result = await rebalanceTeamWindowWith("@focus", "main-vertical", deps)
+
+ // then
+ expect(result).toBe(true)
+ expect(calls).toEqual([
+ ["select-layout", "-t", "@focus", "main-vertical"],
+ ["set-window-option", "-t", "@focus", "main-pane-width", "60%"],
+ ["select-layout", "-t", "@focus", "main-vertical"],
+ ])
+ })
+
+ it("#given tiled #when rebalance #then only select-layout called", async () => {
+ // given
+ const deps: RebalanceTeamWindowDeps = { runTmux, log }
+
+ // when
+ const result = await rebalanceTeamWindowWith("@1", "tiled", deps)
+
+ // then
+ expect(result).toBe(true)
+ expect(calls).toEqual([["select-layout", "-t", "@1", "tiled"]])
+ })
+
+ it("#given select-layout fails #when rebalance #then returns false, log once", async () => {
+ // given
+ runTmux = mock(async (args: string[]): Promise<{ success: boolean }> => {
+ calls.push(args)
+ return { success: false }
+ })
+
+ const deps: RebalanceTeamWindowDeps = { runTmux, log }
+
+ // when
+ const result = await rebalanceTeamWindowWith("@1", "main-vertical", deps)
+
+ // then
+ expect(result).toBe(false)
+ expect(log).toHaveBeenCalledTimes(1)
+ })
+})
diff --git a/src/features/team-mode/team-layout-tmux/rebalance-team-window.ts b/src/features/team-mode/team-layout-tmux/rebalance-team-window.ts
new file mode 100644
index 000000000..23abd0716
--- /dev/null
+++ b/src/features/team-mode/team-layout-tmux/rebalance-team-window.ts
@@ -0,0 +1,70 @@
+export type RebalanceLayout = "main-vertical" | "tiled"
+
+export type RebalanceTeamWindowDeps = {
+ runTmux: (args: string[]) => Promise<{ success: boolean }>
+ log: (message: string, meta?: Record) => void
+}
+
+export async function rebalanceTeamWindowWith(
+ windowId: string,
+ layout: RebalanceLayout,
+ deps: RebalanceTeamWindowDeps,
+): Promise {
+ if (windowId.length === 0) {
+ return false
+ }
+
+ const selectLayoutArgs = ["select-layout", "-t", windowId, layout]
+ const initialLayout = await deps.runTmux(selectLayoutArgs)
+ if (!initialLayout.success) {
+ deps.log("[rebalanceTeamWindow] FAILED", { windowId, layout, step: "select-layout" })
+ return false
+ }
+
+ if (layout === "tiled") {
+ return true
+ }
+
+ const setMainPaneWidth = await deps.runTmux([
+ "set-window-option",
+ "-t",
+ windowId,
+ "main-pane-width",
+ "60%",
+ ])
+ if (!setMainPaneWidth.success) {
+ deps.log("[rebalanceTeamWindow] FAILED", { windowId, layout, step: "set-window-option" })
+ return false
+ }
+
+ // tmux applies main-pane-width against the active layout, so select-layout again after resizing.
+ const finalLayout = await deps.runTmux(selectLayoutArgs)
+ if (!finalLayout.success) {
+ deps.log("[rebalanceTeamWindow] FAILED", { windowId, layout, step: "select-layout" })
+ return false
+ }
+
+ return true
+}
+
+export async function rebalanceTeamWindow(
+ windowId: string,
+ layout: RebalanceLayout,
+): Promise {
+ const [{ log }, { getTmuxPath }, { runTmuxCommand }] = await Promise.all([
+ import("../../../shared"),
+ import("../../../tools/interactive-bash/tmux-path-resolver"),
+ import("../../../shared/tmux"),
+ ])
+
+ const tmuxPath = await getTmuxPath()
+ if (!tmuxPath) {
+ log("[rebalanceTeamWindow] SKIP: tmux not found", { windowId, layout })
+ return false
+ }
+
+ return rebalanceTeamWindowWith(windowId, layout, {
+ runTmux: (args) => runTmuxCommand(tmuxPath, args),
+ log,
+ })
+}