fix(tmux): use actual pane dimensions and configured min width for grid calculation
Agent area width now uses real mainPane.width instead of hardcoded 50% ratio. Grid planning, split availability, and spawn target finding now respect user's agent_pane_min_width config instead of hardcoded MIN_PANE_WIDTH=52, enabling 2-column grid layouts on narrower terminals.
This commit is contained in:
@@ -112,6 +112,21 @@ describe("canSplitPaneAnyDirection", () => {
|
||||
// then
|
||||
expect(result).toBe(false)
|
||||
})
|
||||
|
||||
it("#given custom minPaneWidth #when pane fits smaller width #then returns true", () => {
|
||||
//#given - pane too small for default MIN_PANE_WIDTH(52) but fits custom 30
|
||||
const customMin = 30
|
||||
const customMinSplitW = 2 * customMin + 1
|
||||
const pane = createPane(customMinSplitW, MIN_SPLIT_HEIGHT - 1)
|
||||
|
||||
//#when
|
||||
const defaultResult = canSplitPaneAnyDirection(pane)
|
||||
const customResult = canSplitPaneAnyDirection(pane, customMin)
|
||||
|
||||
//#then
|
||||
expect(defaultResult).toBe(false)
|
||||
expect(customResult).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("getBestSplitDirection", () => {
|
||||
@@ -179,6 +194,21 @@ describe("getBestSplitDirection", () => {
|
||||
// then
|
||||
expect(result).toBe("-v")
|
||||
})
|
||||
|
||||
it("#given custom minPaneWidth #when pane width below default but above custom #then returns -h", () => {
|
||||
//#given
|
||||
const customMin = 30
|
||||
const customMinSplitW = 2 * customMin + 1
|
||||
const pane = createPane(customMinSplitW, MIN_SPLIT_HEIGHT - 1)
|
||||
|
||||
//#when
|
||||
const defaultResult = getBestSplitDirection(pane)
|
||||
const customResult = getBestSplitDirection(pane, customMin)
|
||||
|
||||
//#then
|
||||
expect(defaultResult).toBe(null)
|
||||
expect(customResult).toBe("-h")
|
||||
})
|
||||
})
|
||||
|
||||
describe("decideSpawnActions", () => {
|
||||
@@ -362,6 +392,20 @@ describe("calculateCapacity", () => {
|
||||
//#then
|
||||
expect(customCapacity.cols).toBeGreaterThanOrEqual(defaultCapacity.cols)
|
||||
})
|
||||
|
||||
it("#given non-50 main pane width #when calculating capacity #then uses real agent area width", () => {
|
||||
//#given
|
||||
const windowWidth = 220
|
||||
const windowHeight = 44
|
||||
const mainPaneWidth = 132
|
||||
|
||||
//#when
|
||||
const capacity = calculateCapacity(windowWidth, windowHeight, 52, mainPaneWidth)
|
||||
|
||||
//#then
|
||||
expect(capacity.cols).toBe(1)
|
||||
expect(capacity.total).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
describe("decideSpawnActions with custom agentPaneWidth", () => {
|
||||
@@ -416,4 +460,40 @@ describe("decideSpawnActions with custom agentPaneWidth", () => {
|
||||
expect(result.actions[0].splitDirection).toBe("-h")
|
||||
}
|
||||
})
|
||||
|
||||
it("#given wider main pane #when capacity needs two evictions #then replace is chosen", () => {
|
||||
//#given
|
||||
const config: CapacityConfig = { mainPaneMinWidth: 120, agentPaneWidth: 40 }
|
||||
const state = createWindowState(220, 44, [
|
||||
{ paneId: "%1", width: 43, height: 44, left: 133, top: 0 },
|
||||
{ paneId: "%2", width: 43, height: 44, left: 177, top: 0 },
|
||||
{ paneId: "%3", width: 43, height: 21, left: 133, top: 22 },
|
||||
{ paneId: "%4", width: 43, height: 21, left: 177, top: 22 },
|
||||
{ paneId: "%5", width: 43, height: 21, left: 133, top: 33 },
|
||||
])
|
||||
state.mainPane = {
|
||||
paneId: "%0",
|
||||
width: 132,
|
||||
height: 44,
|
||||
left: 0,
|
||||
top: 0,
|
||||
title: "main",
|
||||
isActive: true,
|
||||
}
|
||||
const mappings: SessionMapping[] = [
|
||||
{ sessionId: "old-1", paneId: "%1", createdAt: new Date("2024-01-01") },
|
||||
{ sessionId: "old-2", paneId: "%2", createdAt: new Date("2024-01-02") },
|
||||
{ sessionId: "old-3", paneId: "%3", createdAt: new Date("2024-01-03") },
|
||||
{ sessionId: "old-4", paneId: "%4", createdAt: new Date("2024-01-04") },
|
||||
{ sessionId: "old-5", paneId: "%5", createdAt: new Date("2024-01-05") },
|
||||
]
|
||||
|
||||
//#when
|
||||
const result = decideSpawnActions(state, "ses-new", "new task", config, mappings)
|
||||
|
||||
//#then
|
||||
expect(result.canSpawn).toBe(true)
|
||||
expect(result.actions).toHaveLength(1)
|
||||
expect(result.actions[0].type).toBe("replace")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -28,8 +28,12 @@ export function calculateCapacity(
|
||||
windowWidth: number,
|
||||
windowHeight: number,
|
||||
minPaneWidth: number = MIN_PANE_WIDTH,
|
||||
mainPaneWidth?: number,
|
||||
): GridCapacity {
|
||||
const availableWidth = Math.floor(windowWidth * (1 - MAIN_PANE_RATIO))
|
||||
const availableWidth =
|
||||
typeof mainPaneWidth === "number"
|
||||
? Math.max(0, windowWidth - mainPaneWidth - DIVIDER_SIZE)
|
||||
: Math.floor(windowWidth * (1 - MAIN_PANE_RATIO))
|
||||
const cols = Math.min(
|
||||
MAX_GRID_SIZE,
|
||||
Math.max(
|
||||
@@ -55,8 +59,15 @@ export function computeGridPlan(
|
||||
windowWidth: number,
|
||||
windowHeight: number,
|
||||
paneCount: number,
|
||||
mainPaneWidth?: number,
|
||||
minPaneWidth?: number,
|
||||
): GridPlan {
|
||||
const capacity = calculateCapacity(windowWidth, windowHeight)
|
||||
const capacity = calculateCapacity(
|
||||
windowWidth,
|
||||
windowHeight,
|
||||
minPaneWidth ?? MIN_PANE_WIDTH,
|
||||
mainPaneWidth,
|
||||
)
|
||||
const { cols: maxCols, rows: maxRows } = capacity
|
||||
|
||||
if (maxCols === 0 || maxRows === 0 || paneCount === 0) {
|
||||
@@ -79,7 +90,10 @@ export function computeGridPlan(
|
||||
}
|
||||
}
|
||||
|
||||
const availableWidth = Math.floor(windowWidth * (1 - MAIN_PANE_RATIO))
|
||||
const availableWidth =
|
||||
typeof mainPaneWidth === "number"
|
||||
? Math.max(0, windowWidth - mainPaneWidth - DIVIDER_SIZE)
|
||||
: Math.floor(windowWidth * (1 - MAIN_PANE_RATIO))
|
||||
const slotWidth = Math.floor(availableWidth / bestCols)
|
||||
const slotHeight = Math.floor(windowHeight / bestRows)
|
||||
|
||||
|
||||
@@ -56,8 +56,8 @@ export function canSplitPane(
|
||||
return pane.height >= MIN_SPLIT_HEIGHT
|
||||
}
|
||||
|
||||
export function canSplitPaneAnyDirection(pane: TmuxPaneInfo): boolean {
|
||||
return canSplitPaneAnyDirectionWithMinWidth(pane, MIN_PANE_WIDTH)
|
||||
export function canSplitPaneAnyDirection(pane: TmuxPaneInfo, minPaneWidth: number = MIN_PANE_WIDTH): boolean {
|
||||
return canSplitPaneAnyDirectionWithMinWidth(pane, minPaneWidth)
|
||||
}
|
||||
|
||||
export function canSplitPaneAnyDirectionWithMinWidth(
|
||||
|
||||
@@ -5,7 +5,7 @@ import type {
|
||||
TmuxPaneInfo,
|
||||
WindowState,
|
||||
} from "./types"
|
||||
import { MAIN_PANE_RATIO } from "./tmux-grid-constants"
|
||||
import { DIVIDER_SIZE } from "./tmux-grid-constants"
|
||||
import {
|
||||
canSplitPane,
|
||||
findMinimalEvictions,
|
||||
@@ -26,7 +26,10 @@ export function decideSpawnActions(
|
||||
}
|
||||
|
||||
const minPaneWidth = config.agentPaneWidth
|
||||
const agentAreaWidth = Math.floor(state.windowWidth * (1 - MAIN_PANE_RATIO))
|
||||
const agentAreaWidth = Math.max(
|
||||
0,
|
||||
state.windowWidth - state.mainPane.width - DIVIDER_SIZE,
|
||||
)
|
||||
const currentCount = state.agentPanes.length
|
||||
|
||||
if (agentAreaWidth < minPaneWidth) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { SplitDirection, TmuxPaneInfo, WindowState } from "./types"
|
||||
import { MAIN_PANE_RATIO } from "./tmux-grid-constants"
|
||||
import { computeGridPlan, mapPaneToSlot } from "./grid-planning"
|
||||
import { canSplitPane, getBestSplitDirection } from "./pane-split-availability"
|
||||
import { MIN_PANE_WIDTH } from "./types"
|
||||
@@ -52,8 +51,14 @@ function findSplittableTarget(
|
||||
return null
|
||||
}
|
||||
|
||||
const plan = computeGridPlan(state.windowWidth, state.windowHeight, existingCount + 1)
|
||||
const mainPaneWidth = Math.floor(state.windowWidth * MAIN_PANE_RATIO)
|
||||
const plan = computeGridPlan(
|
||||
state.windowWidth,
|
||||
state.windowHeight,
|
||||
existingCount + 1,
|
||||
state.mainPane.width,
|
||||
minPaneWidth,
|
||||
)
|
||||
const mainPaneWidth = state.mainPane.width
|
||||
const occupancy = buildOccupancy(state.agentPanes, plan, mainPaneWidth)
|
||||
const targetSlot = findFirstEmptySlot(occupancy, plan)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user