Merge pull request #12858 from Kilo-Org/sync-terminal-diff-viewer-dimensions

fix(vscode): sync Agent Manager inspector width
This commit is contained in:
Marius
2026-08-04 15:07:49 +02:00
committed by GitHub
5 changed files with 73 additions and 23 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"kilo-code": patch
---
Persist the Agent Manager inspector width and share it between the terminal and diff viewer.
@@ -1,8 +1,10 @@
import { expect, test } from "bun:test"
import { readFileSync } from "node:fs"
import { resolve } from "node:path"
import { clampPanelWidth, maxPanelWidth, minPanelWidth } from "../../webview-ui/agent-manager/side-panel-layout"
const css = readFileSync(resolve(import.meta.dir, "../../webview-ui/agent-manager/agent-manager.css"), "utf8")
const app = readFileSync(resolve(import.meta.dir, "../../webview-ui/agent-manager/AgentManagerApp.tsx"), "utf8")
test("xterm owns the padding used by FitAddon", () => {
const host = css.match(/\.am-terminal-host\s*\{([^}]*)\}/)?.[1]
@@ -13,3 +15,22 @@ test("xterm owns the padding used by FitAddon", () => {
expect(host).not.toMatch(/\bpadding\s*:/)
expect(term).toMatch(/\bpadding\s*:\s*8px\s*;/)
})
test("uses one persisted width for the diff and terminal inspector", () => {
expect(app).toContain("persisted?.sidePanelWidth")
expect(app).toContain("setPanelWidth(pendingSideWidth!)")
expect(app).not.toContain("diffWidth")
expect(app).not.toContain("terminalWidth")
})
test("clamps the restored inspector width to the shared layout bounds", () => {
expect(clampPanelWidth(undefined, 1200)).toBe(600)
expect(clampPanelWidth(500, 1200)).toBe(500)
expect(clampPanelWidth(1000, 1000)).toBe(maxPanelWidth(1000))
expect(clampPanelWidth(100, 1200)).toBe(minPanelWidth(1200))
expect(clampPanelWidth("invalid", 1200)).toBe(600)
expect(minPanelWidth(400)).toBe(200)
expect(maxPanelWidth(400)).toBe(320)
expect(clampPanelWidth(undefined, 400)).toBe(200)
expect(clampPanelWidth(360, 400)).toBe(320)
})
@@ -174,6 +174,7 @@ import { createMarkdownRender } from "./review-preferences"
import { createSidebarCollapse } from "./sidebar-collapse"
import { SidebarToggleButton } from "./SidebarToggleButton"
import { setTabWidths } from "./tab-widths"
import { clampPanelWidth, maxPanelWidth, minPanelWidth } from "./side-panel-layout"
import { buildShortcutCategories } from "./shortcuts"
import { tracker } from "./telemetry"
import { createChatFocus, hasQuestionOption } from "./focus"
@@ -274,7 +275,7 @@ const AgentManagerContent: Component = () => {
const MAX_SIDEBAR_WIDTH_RATIO = 0.4
// Recover persisted local session IDs from webview state
const persisted = vscode.getState<PersistedProjectTabs & { sidebarWidth?: number }>()
const persisted = vscode.getState<PersistedProjectTabs & { sidebarWidth?: number; sidePanelWidth?: number }>()
const registry = createProjectRegistry({
persisted: persisted ?? {},
activeId: () => currentProjectId() ?? "single",
@@ -314,26 +315,15 @@ const AgentManagerContent: Component = () => {
const diffLoading = diffs.diffLoading
const setDiffLoading = diffs.setDiffLoading
const diffNotices = diffs.diffNotices
// The diff and terminal panels each remember their own width: a diff
// benefits from half the window, a terminal only needs about a third.
const TERMINAL_MIN_WIDTH = 360
const TERMINAL_MAX_WIDTH = 640
const [diffWidth, setDiffWidth] = createSignal(Math.round(window.innerWidth * 0.5))
const [terminalWidth, setTerminalWidth] = createSignal(
Math.min(TERMINAL_MAX_WIDTH, Math.max(TERMINAL_MIN_WIDTH, Math.round(window.innerWidth / 3))),
)
// The hidden-but-mounted host still fits the terminal, so pick the
// terminal's width whenever one is alive and no other mode is showing.
const widthMode = () => sidePanel() ?? (terms.sides().length > 0 ? "terminal" : null)
const hostWidth = () => (widthMode() === "terminal" ? terminalWidth() : diffWidth())
const sideMin = () => (widthMode() === "terminal" ? TERMINAL_MIN_WIDTH : 200)
// Diff and terminal views share one inspector width, restored from webview
// state so the user's divider position survives panel reloads.
const [panelWidth, setPanelWidth] = createSignal(clampPanelWidth(persisted?.sidePanelWidth, window.innerWidth))
const resizeSide = (width: number) => {
pendingSideWidth = Math.max(sideMin(), Math.min(width, window.innerWidth * 0.8))
pendingSideWidth = clampPanelWidth(width, window.innerWidth)
if (sideRaf !== undefined) return
sideRaf = requestAnimationFrame(() => {
sideRaf = undefined
if (widthMode() === "terminal") setTerminalWidth(pendingSideWidth!)
else setDiffWidth(pendingSideWidth!)
setPanelWidth(pendingSideWidth!)
})
}
const showSideTerminal = () => {
@@ -643,6 +633,7 @@ const AgentManagerContent: Component = () => {
},
key: () => registry.active().id,
width: sidebarWidth,
panelWidth,
get: () => vscode.getState<Record<string, unknown>>(),
set: (value) => vscode.setState(value),
})
@@ -2636,16 +2627,16 @@ const AgentManagerContent: Component = () => {
<Show when={sidePanel() !== null || terms.sides().length > 0}>
<div
class={`am-diff-resize ${sidePanel() === null ? "am-side-host-hidden" : ""}`}
style={{ width: `${hostWidth()}px` }}
style={{ width: `${panelWidth()}px` }}
inert={sidePanel() === null}
>
<Show when={sidePanel() !== null}>
<ResizeHandle
direction="horizontal"
edge="start"
size={hostWidth()}
min={sideMin()}
max={Math.round(window.innerWidth * 0.8)}
size={panelWidth()}
min={minPanelWidth(window.innerWidth)}
max={maxPanelWidth(window.innerWidth)}
onResize={resizeSide}
/>
</Show>
@@ -2,13 +2,14 @@ import { createEffect, createMemo, onCleanup } from "solid-js"
import type { SessionInfo } from "../../src/types/messages/sessions"
/**
* Persist open tabs and the sidebar width to webview state for recovery.
* Persist open tabs and panel widths to webview state for recovery.
* Debounced so a resize drag does not serialize state on every pixel.
*/
export function persistLocalTabs(opts: {
tabs: () => Record<string, string[]>
key: () => string
width: () => number
panelWidth?: () => number
get: () => Record<string, unknown> | undefined
set: (value: Record<string, unknown>) => void
}): void {
@@ -18,9 +19,16 @@ export function persistLocalTabs(opts: {
const tabs = opts.tabs()
const key = opts.key()
const width = opts.width()
const panel = opts.panelWidth?.()
clearTimeout(timer)
timer = setTimeout(() => {
opts.set({ ...(opts.get() ?? {}), localTabs: tabs, localSessionIDs: tabs[key] ?? [], sidebarWidth: width })
opts.set({
...(opts.get() ?? {}),
localTabs: tabs,
localSessionIDs: tabs[key] ?? [],
sidebarWidth: width,
...(panel === undefined ? {} : { sidePanelWidth: panel }),
})
}, 300)
})
onCleanup(() => clearTimeout(timer))
@@ -0,0 +1,25 @@
export const MIN_PANEL_WIDTH = 360
const DEFAULT_PANEL_WIDTH_RATIO = 0.5
const MAX_PANEL_WIDTH_RATIO = 0.8
function viewportWidth(viewport: number): number {
return Number.isFinite(viewport) && viewport > 0 ? viewport : MIN_PANEL_WIDTH
}
export function minPanelWidth(viewport: number): number {
const width = viewportWidth(viewport)
return Math.min(MIN_PANEL_WIDTH, Math.round(width * DEFAULT_PANEL_WIDTH_RATIO))
}
export function maxPanelWidth(viewport: number): number {
const width = viewportWidth(viewport)
return Math.max(minPanelWidth(width), Math.round(width * MAX_PANEL_WIDTH_RATIO))
}
/** Restore or constrain the shared inspector width without trusting saved state. */
export function clampPanelWidth(value: unknown, viewport: number): number {
const width = viewportWidth(viewport)
const fallback = Math.round(width * DEFAULT_PANEL_WIDTH_RATIO)
const candidate = typeof value === "number" && Number.isFinite(value) ? value : fallback
return Math.round(Math.max(minPanelWidth(width), Math.min(candidate, maxPanelWidth(width))))
}