Revert "fix(terminal): apply pane padding on all four edges (#15544)" (#15623)

This reverts commit 4b2ed5ddd4.
This commit is contained in:
Jinjing
2026-08-20 09:57:55 -07:00
committed by GitHub
parent 0f26ff4ad8
commit d8e9fa1bb9
12 changed files with 37 additions and 456 deletions
+1 -1
View File
@@ -91,7 +91,7 @@
"test:e2e:workspace-session-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/golden-quit-relaunch-session.spec.ts tests/e2e/golden-terminal-file-link.spec.ts tests/e2e/golden-worktree-create-switch.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1",
"test:e2e:multi-client-navigation": "node config/scripts/run-multi-client-navigation-e2e.mjs",
"test:e2e:floating-mobile-emulator": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/floating-mobile-emulator-tab.spec.ts --config tests/playwright.config.ts --project electron-headless --workers=1",
"test:e2e:terminal-rendering-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/terminal-raw-emoji-table-scroll-restore.spec.ts tests/e2e/terminal-webgl-atlas-budget.spec.ts tests/e2e/terminal-padding-geometry-golden.spec.ts --grep @terminal-rendering-golden --config tests/playwright.config.ts --project electron-headless --workers=1",
"test:e2e:terminal-rendering-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/terminal-raw-emoji-table-scroll-restore.spec.ts tests/e2e/terminal-webgl-atlas-budget.spec.ts --grep @terminal-rendering-golden --config tests/playwright.config.ts --project electron-headless --workers=1",
"test:e2e:source-control-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/golden-file-open-edit-save.spec.ts tests/e2e/golden-source-control-commit.spec.ts tests/e2e/golden-source-control-open-diff.spec.ts --grep @golden --config tests/playwright.config.ts --project electron-headless --workers=1",
"test:e2e:posix-profile-index-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/golden-posix-fresh-startup.spec.ts tests/e2e/golden-posix-profile-index-fsync.spec.ts --grep @posix-profile-index-golden --config tests/playwright.config.ts --project electron-headless --workers=1",
"test:e2e:windows-fresh-startup-golden": "pnpm run ensure:electron-runtime && npx playwright test tests/e2e/golden-windows-fresh-startup.spec.ts --grep @windows-fresh-startup-golden --config tests/playwright.config.ts --project electron-headless --workers=1",
+2 -2
View File
@@ -90,9 +90,9 @@ describe('mapGhosttyToOrca — window-padding', () => {
expect(result.unsupportedKeys).toEqual([])
})
it('rounds odd-sum dual-value padding to the integer used for fitting', () => {
it('preserves odd-sum dual-value padding as a fractional average', () => {
const result = mapGhosttyToOrca({ 'window-padding-x': '1,2' })
expect(result.diff).toEqual({ terminalPaddingX: 2 })
expect(result.diff).toEqual({ terminalPaddingX: 1.5 })
expect(result.unsupportedKeys).toEqual([])
})
+4 -5
View File
@@ -1,5 +1,3 @@
import { normalizeTerminalPadding } from '../../shared/terminal-padding-settings'
// Why: `Number("1e10")` succeeds and passes `Number.isInteger`, so a Ghostty
// config with `window-padding-x = 1e9` would sail through the mapper and land
// an absurd value in the store. Restrict to plain decimal integers.
@@ -12,8 +10,9 @@ export const parseStrictInt = (v: string): number | null => {
return Number.isFinite(num) ? num : null
}
// Why: Orca stores one integer per axis, so collapse paired paddings to the
// nearest value that xterm's integer-based fit calculation will also use.
// Why: Ghostty accepts "top,bottom" / "left,right" pairs for window paddings,
// but Orca stores a single value per axis — average the pair so the total
// padding along the axis stays the same.
export const parsePaddingValue = (v: string): number | null => {
const parts = v.split(',')
if (parts.length > 2) {
@@ -27,5 +26,5 @@ export const parsePaddingValue = (v: string): number | null => {
}
nums.push(num)
}
return normalizeTerminalPadding(nums.reduce((sum, num) => sum + num, 0) / nums.length)
return nums.reduce((sum, num) => sum + num, 0) / nums.length
}
@@ -5,7 +5,9 @@ const terminalCss = fs.readFileSync(new URL('./terminal.css', import.meta.url),
describe('terminal container geometry', () => {
it('keeps the hidden link tooltip out of the fitted terminal height', () => {
expect(terminalCss).toMatch(/\.xterm-container\s*{[^}]*height:\s*100%;/s)
expect(terminalCss).toMatch(
/\.xterm-container\s*{[^}]*height:\s*calc\(100% - var\(--pane-padding-y, 4px\)\);/s
)
expect(terminalCss).toMatch(
/\.pane\[data-has-title\] \.xterm-container\s*{[^}]*height:\s*calc\(100% - var\(--orca-pane-title-height\)\);/s
)
@@ -13,12 +15,4 @@ describe('terminal container geometry', () => {
/\.pane-link-tooltip\s*{[^}]*height:\s*var\(--orca-terminal-link-tooltip-height\);/s
)
})
it('insets the xterm grid on all four edges', () => {
expect(terminalCss).toMatch(
/\.xterm-container \.xterm\s*{[^}]*padding:\s*var\(--pane-padding-y, 4px\) var\(--pane-padding-x, 4px\);/s
)
expect(terminalCss).toMatch(/\.xterm-container\s*{[^}]*width:\s*100%;/s)
expect(terminalCss).not.toMatch(/\.xterm-container\s*{[^}]*margin-left:/s)
})
})
+9 -8
View File
@@ -25,11 +25,9 @@
outline: none;
}
.xterm-container .xterm {
box-sizing: border-box;
.pane-manager-root .xterm {
height: 100%;
width: 100%;
padding: var(--pane-padding-y, 4px) var(--pane-padding-x, 4px);
}
.pane-manager-root .xterm-viewport {
@@ -179,7 +177,7 @@
.pane[data-terminal-attention] .xterm-container::after {
content: '';
position: absolute;
inset: 0;
inset: 0 4px 4px 0;
z-index: 5;
pointer-events: none;
border-radius: 2px;
@@ -500,13 +498,16 @@
border-color: var(--ring);
}
/* The container fills the pane; xterm owns the four-edge grid inset so its fit
calculation subtracts the configured padding before choosing cols/rows. */
/* Baseline xterm-container layout — padding is driven by CSS custom properties
so Ghostty-imported window-padding-x/y can be applied without inline styles.
The fallback 4px preserves the legacy default. */
.xterm-container {
box-sizing: border-box;
position: relative;
width: 100%;
height: 100%;
width: calc(100% - var(--pane-padding-x, 4px));
height: calc(100% - var(--pane-padding-y, 4px));
margin-top: var(--pane-padding-y, 4px);
margin-left: var(--pane-padding-x, 4px);
}
/* When a pane has a title, shift the terminal content down to make room.
@@ -1,81 +0,0 @@
// @vitest-environment happy-dom
import { act, type ReactNode } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type { GlobalSettings } from '../../../../shared/global-settings-types'
import { getDefaultSettings } from '../../../../shared/constants'
import { TerminalWindowSection } from './TerminalWindowSection'
type NumberFieldProps = {
label: string
value: number
onChange: (value: number) => void
}
const numberFields = vi.hoisted(() => new Map<string, NumberFieldProps>())
vi.mock('@/i18n/i18n', () => ({
translate: (_key: string, defaultValue: string) => defaultValue
}))
vi.mock('./SettingsFormControls', () => ({
ColorField: function ColorField() {
return null
},
NumberField: function NumberField(props: NumberFieldProps) {
numberFields.set(props.label, props)
return null
}
}))
vi.mock('./SearchableSetting', () => ({
SearchableSetting: function SearchableSetting({ children }: { children?: ReactNode }) {
return children ?? null
}
}))
vi.mock('../ui/button', () => ({ Button: () => null }))
vi.mock('../ui/label', () => ({ Label: () => null }))
vi.mock('../ui/switch', () => ({ Switch: () => null }))
describe('TerminalWindowSection padding', () => {
let container: HTMLDivElement
let root: Root
beforeEach(() => {
globalThis.IS_REACT_ACT_ENVIRONMENT = true
numberFields.clear()
container = document.createElement('div')
document.body.appendChild(container)
root = createRoot(container)
})
afterEach(() => {
act(() => root.unmount())
document.body.replaceChildren()
})
it('displays and persists the same integer padding used by terminal fitting', () => {
const updateSettings = vi.fn()
const settings = {
...getDefaultSettings('/tmp'),
terminalPaddingX: 1.5,
terminalPaddingY: 2.5
} as GlobalSettings
act(() =>
root.render(<TerminalWindowSection settings={settings} updateSettings={updateSettings} />)
)
const horizontal = numberFields.get('Horizontal Padding')
const vertical = numberFields.get('Vertical Padding')
expect(horizontal?.value).toBe(2)
expect(vertical?.value).toBe(3)
act(() => horizontal?.onChange(3.5))
act(() => vertical?.onChange(4.5))
expect(updateSettings).toHaveBeenNthCalledWith(1, { terminalPaddingX: 4 })
expect(updateSettings).toHaveBeenNthCalledWith(2, { terminalPaddingY: 5 })
})
})
@@ -1,6 +1,5 @@
import { useRef, useState } from 'react'
import { RotateCw } from 'lucide-react'
import { normalizeTerminalPadding } from '../../../../shared/terminal-padding-settings'
import type { GlobalSettings } from '../../../../shared/global-settings-types'
import { Button } from '../ui/button'
import { Label } from '../ui/label'
@@ -186,15 +185,13 @@ export function TerminalWindowSection({
'Horizontal Padding'
)}
description=""
value={normalizeTerminalPadding(settings.terminalPaddingX ?? 4)}
value={settings.terminalPaddingX ?? 4}
defaultValue={4}
min={0}
max={512}
step={1}
suffix="px"
onChange={(value) =>
updateSettings({ terminalPaddingX: normalizeTerminalPadding(value) })
}
onChange={(value) => updateSettings({ terminalPaddingX: Math.max(0, value) })}
/>
</SearchableSetting>
@@ -215,15 +212,13 @@ export function TerminalWindowSection({
'Vertical Padding'
)}
description=""
value={normalizeTerminalPadding(settings.terminalPaddingY ?? 4)}
value={settings.terminalPaddingY ?? 4}
defaultValue={4}
min={0}
max={512}
step={1}
suffix="px"
onChange={(value) =>
updateSettings({ terminalPaddingY: normalizeTerminalPadding(value) })
}
onChange={(value) => updateSettings({ terminalPaddingY: Math.max(0, value) })}
/>
</SearchableSetting>
@@ -3,7 +3,6 @@ import type { ManagedPane, PaneManager } from '@/lib/pane-manager/pane-manager'
import { getDefaultSettings } from '../../../../shared/constants'
import {
applyTerminalAppearance,
composeRendererTerminalTheme,
hexToRgba,
publishTerminalViewAttributesAtAppStart
} from './terminal-appearance'
@@ -217,54 +216,6 @@ describe('applyTerminalAppearance theme assignment', () => {
expect(pane.terminal.options.theme?.background).toBe('#102030')
})
it('lets the pane paint a fractional background exactly once', () => {
const pane = makePane(1)
const settings = getDefaultSettings('/tmp')
const manager = makeManager([pane])
applyTerminalAppearance(
manager,
{ ...settings, terminalBackgroundOpacity: 0.5 },
true,
new Map(),
new Map(),
'false',
new Map(),
new Map()
)
expect(pane.terminal.options.theme?.background).toMatch(/^rgba\(.+, 0\)$/)
expect(pane.terminal.options.allowTransparency).toBe(true)
expect(manager.setPaneStyleOptions).toHaveBeenCalledWith(
expect.objectContaining({ paneBackground: expect.stringMatching(/^rgba\(.+, 0\.5\)$/) })
)
})
it('stamps four-edge padding before fitting', () => {
const pane = makePane(1)
pane.fitAddon.proposeDimensions = vi.fn(() => ({ cols: 79, rows: 23 }))
const manager = makeManager([pane])
const settings = getDefaultSettings('/tmp')
applyTerminalAppearance(
manager,
{ ...settings, terminalPaddingX: 11, terminalPaddingY: 7 },
true,
new Map(),
new Map(),
'false',
new Map(),
new Map()
)
expect(manager.setPaneStyleOptions).toHaveBeenCalledWith(
expect.objectContaining({ paddingX: 11, paddingY: 7 })
)
expect(vi.mocked(manager.setPaneStyleOptions).mock.invocationCallOrder[0]).toBeLessThan(
vi.mocked(pane.fitAddon.proposeDimensions).mock.invocationCallOrder[0]!
)
})
// #7934: contrast correction rescues invisible white text on light backgrounds but over-corrects on dark;
// gate by the composed theme's background luminance (either theme slot can hold either kind of theme).
it('keeps xterm contrast correction on light themes', () => {
@@ -411,23 +362,6 @@ describe('applyTerminalAppearance theme assignment', () => {
})
})
describe('composeRendererTerminalTheme', () => {
it('preserves background RGB while removing fractional renderer alpha', () => {
const theme = { background: 'rgba(16, 32, 48, 0.5)', foreground: '#ffffff' }
expect(composeRendererTerminalTheme(theme, 0.5)).toEqual({
...theme,
background: 'rgba(16, 32, 48, 0)'
})
})
it('keeps the opaque fast path unchanged', () => {
const theme = { background: '#102030' }
expect(composeRendererTerminalTheme(theme, 1)).toBe(theme)
})
})
describe('publishTerminalViewAttributesAtAppStart', () => {
// Hidden-at-launch PTYs query OSC 10/11 before any pane mounts; publish with no pane manager (terminal-query-authority.md).
it('publishes composed attributes without any pane mount and dedupes repeats', () => {
@@ -29,7 +29,6 @@ import { publishTerminalViewAttributes } from './terminal-view-attributes-publis
import { normalizeTerminalLineHeight } from '../../../../shared/terminal-line-height-settings'
import { maybePushMode2031Flip } from './terminal-mode-2031-replies'
import { resolveTerminalMinimumContrastRatio } from '@/lib/terminal-contrast-correction'
import { normalizeTerminalPadding } from '../../../../shared/terminal-padding-settings'
export function hexToRgba(hex: string, alpha: number): string {
let clean = hex.replace('#', '')
@@ -133,22 +132,6 @@ function composedTerminalThemesEqual(a: ITheme | undefined, b: ITheme): boolean
return extA.length === extB.length && extA.every((value, i) => value === extB[i])
}
export function composeRendererTerminalTheme(
theme: ITheme | null,
backgroundOpacity: number | undefined
): ITheme | null {
if (!theme?.background || backgroundOpacity === undefined || backgroundOpacity >= 1) {
return theme
}
const transparentBackground = theme.background.replace(
/^rgba\(([^,]+),\s*([^,]+),\s*([^,]+),\s*[^)]+\)$/,
'rgba($1, $2, $3, 0)'
)
return transparentBackground === theme.background
? theme
: { ...theme, background: transparentBackground }
}
export function applyTerminalAppearance(
manager: PaneManager,
settings: GlobalSettings,
@@ -166,7 +149,6 @@ export function applyTerminalAppearance(
// Publish composed appearance to main's hidden-PTY query responder — the only point it exists; deduped in the publisher.
publishTerminalViewAttributes(theme, appearance.mode, settings)
const paneBackground = theme?.background ?? '#000000'
const rendererTheme = composeRendererTerminalTheme(theme, settings.terminalBackgroundOpacity)
const terminalFontWeights = resolveTerminalFontWeights(
settings.terminalFontWeight,
@@ -177,23 +159,10 @@ export function applyTerminalAppearance(
settings.terminalFontFamily
)
// Why before fit: FitAddon reads live .xterm padding when proposing cols/rows.
manager.setPaneStyleOptions({
splitBackground: paneBackground,
paneBackground,
inactivePaneOpacity: paneStyles.inactivePaneOpacity,
activePaneOpacity: paneStyles.activePaneOpacity,
opacityTransitionMs: paneStyles.opacityTransitionMs,
dividerThicknessPx: paneStyles.dividerThicknessPx,
focusFollowsMouse: paneStyles.focusFollowsMouse,
paddingX: normalizeTerminalPadding(settings.terminalPaddingX ?? 4),
paddingY: normalizeTerminalPadding(settings.terminalPaddingY ?? 4)
})
for (const pane of manager.getPanes()) {
// Why value-gated: writing options.theme rebuilds the palette, discarding TUI OSC 4/10/11/12 mutations; skip on no-op change.
if (rendererTheme && !composedTerminalThemesEqual(pane.terminal.options.theme, rendererTheme)) {
pane.terminal.options.theme = rendererTheme
if (theme && !composedTerminalThemesEqual(pane.terminal.options.theme, theme)) {
pane.terminal.options.theme = theme
}
// Gate off the configured theme background; the live OSC-11 background is deliberately preserved by the
// theme write above, so a TUI that repaints its background at runtime won't re-gate (known limitation).
@@ -258,4 +227,16 @@ export function applyTerminalAppearance(
safeFit(pane)
}
}
manager.setPaneStyleOptions({
splitBackground: paneBackground,
paneBackground,
inactivePaneOpacity: paneStyles.inactivePaneOpacity,
activePaneOpacity: paneStyles.activePaneOpacity,
opacityTransitionMs: paneStyles.opacityTransitionMs,
dividerThicknessPx: paneStyles.dividerThicknessPx,
focusFollowsMouse: paneStyles.focusFollowsMouse,
paddingX: settings.terminalPaddingX,
paddingY: settings.terminalPaddingY
})
}
@@ -1,14 +0,0 @@
import { describe, expect, it } from 'vitest'
import { normalizeTerminalPadding } from './terminal-padding-settings'
describe('normalizeTerminalPadding', () => {
it('rounds fractional padding to the nearest fitted pixel', () => {
expect(normalizeTerminalPadding(1.5)).toBe(2)
expect(normalizeTerminalPadding(2.49)).toBe(2)
})
it('keeps padding inside the settings range', () => {
expect(normalizeTerminalPadding(-1)).toBe(0)
expect(normalizeTerminalPadding(513)).toBe(512)
})
})
-6
View File
@@ -1,6 +0,0 @@
const MIN_TERMINAL_PADDING = 0
const MAX_TERMINAL_PADDING = 512
export function normalizeTerminalPadding(value: number): number {
return Math.min(MAX_TERMINAL_PADDING, Math.max(MIN_TERMINAL_PADDING, Math.round(value)))
}
@@ -1,222 +0,0 @@
import type { Page } from '@stablyai/playwright-test'
import { test, expect } from './helpers/orca-app'
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
import { waitForActiveTerminalManager } from './helpers/terminal'
type TerminalGeometry = {
cellWidth: number
cellHeight: number
cols: number
rows: number
scrollbarWidth: number
pane: { top: number; right: number; bottom: number; left: number; width: number; height: number }
terminal: { top: number; right: number; bottom: number; left: number }
screen: {
top: number
right: number
bottom: number
left: number
width: number
height: number
}
}
const FIRST_PADDING = { x: 11, y: 7 }
const SECOND_PADDING = { x: 17, y: 13 }
const TARGET_COLS = 70
const TARGET_ROWS = 20
const MIN_REMAINDER = 2
async function readTerminalGeometry(page: Page): Promise<TerminalGeometry> {
return page.evaluate(() => {
const terminalElement = document.querySelector<HTMLElement>('.pane .xterm')
const paneElement = terminalElement?.closest<HTMLElement>('.pane')
const screenElement = terminalElement?.querySelector<HTMLElement>('.xterm-screen')
const manager = [...(window.__paneManagers?.values() ?? [])].find((candidate) =>
candidate.getPanes().some((pane) => pane.container === paneElement)
)
const pane = manager?.getPanes().find((candidate) => candidate.container === paneElement)
const cell = pane?.terminal.dimensions?.css.cell
if (!terminalElement || !paneElement || !screenElement || !pane || !cell) {
throw new Error('Active terminal geometry is unavailable')
}
const paneRect = paneElement.getBoundingClientRect()
const terminalRect = terminalElement.getBoundingClientRect()
const screenRect = screenElement.getBoundingClientRect()
return {
cellWidth: cell.width,
cellHeight: cell.height,
cols: pane.terminal.cols,
rows: pane.terminal.rows,
scrollbarWidth: pane.terminal.options.scrollbar?.width ?? 14,
pane: {
top: paneRect.top,
right: paneRect.right,
bottom: paneRect.bottom,
left: paneRect.left,
width: paneRect.width,
height: paneRect.height
},
terminal: {
top: terminalRect.top,
right: terminalRect.right,
bottom: terminalRect.bottom,
left: terminalRect.left
},
screen: {
top: screenRect.top,
right: screenRect.right,
bottom: screenRect.bottom,
left: screenRect.left,
width: screenRect.width,
height: screenRect.height
}
}
})
}
function findNonDivisibleSize(cellSize: number, cells: number, chrome: number): number {
const floor = Math.ceil(cellSize * cells + chrome)
for (let size = floor; size < floor + Math.ceil(cellSize); size += 1) {
const available = size - chrome
const remainder = available - Math.floor(available / cellSize) * cellSize
if (remainder > MIN_REMAINDER && remainder < cellSize - MIN_REMAINDER) {
return size
}
}
throw new Error(`Could not force a non-divisible ${cellSize}px cell geometry`)
}
async function applyPaddingAndPaneSize(
page: Page,
padding: { x: number; y: number },
size: { width: number; height: number }
): Promise<void> {
await page.evaluate(
async ({ nextPadding, nextSize }) => {
const store = window.__store
const terminal = document.querySelector<HTMLElement>('.pane .xterm')
const pane = terminal?.closest<HTMLElement>('.pane')
if (!store || !pane) {
throw new Error('Terminal settings surface is unavailable')
}
await store.getState().updateSettings({
terminalPaddingX: nextPadding.x,
terminalPaddingY: nextPadding.y
})
pane.style.setProperty('width', `${nextSize.width}px`, 'important')
pane.style.setProperty('height', `${nextSize.height}px`, 'important')
},
{ nextPadding: padding, nextSize: size }
)
await page.waitForFunction(
({ expectedPadding, expectedSize }) => {
const store = window.__store
const terminal = document.querySelector<HTMLElement>('.pane .xterm')
const pane = terminal?.closest<HTMLElement>('.pane')
if (!store || !pane) {
return false
}
const settings = store.getState().settings
const rect = pane.getBoundingClientRect()
return (
settings?.terminalPaddingX === expectedPadding.x &&
settings?.terminalPaddingY === expectedPadding.y &&
Math.abs(rect.width - expectedSize.width) < 0.5 &&
Math.abs(rect.height - expectedSize.height) < 0.5
)
},
{ expectedPadding: padding, expectedSize: size }
)
await page.evaluate(() => {
for (const manager of window.__paneManagers?.values() ?? []) {
for (const pane of manager.getPanes()) {
pane.fitAddon.fit()
}
}
})
await page.waitForTimeout(100)
}
function expectClose(actual: number, expected: number): void {
expect(Math.abs(actual - expected)).toBeLessThan(0.75)
}
function assertFourEdgeGeometry(
geometry: TerminalGeometry,
padding: { x: number; y: number }
): { horizontalRemainder: number; verticalRemainder: number } {
const verticalRemainder = geometry.pane.height - padding.y * 2 - geometry.screen.height
// The old layout only exposed its trailing gap when cell fitting left a remainder.
expect(verticalRemainder).toBeGreaterThan(MIN_REMAINDER)
expect(verticalRemainder).toBeLessThan(geometry.cellHeight)
expect({
left: geometry.terminal.left - geometry.pane.left,
top: geometry.terminal.top - geometry.pane.top,
right: geometry.pane.right - geometry.terminal.right,
bottom: geometry.pane.bottom - geometry.terminal.bottom
}).toEqual({ left: 0, top: 0, right: 0, bottom: 0 })
const horizontalRemainder =
geometry.pane.width - padding.x * 2 - geometry.scrollbarWidth - geometry.screen.width
expect(horizontalRemainder).toBeGreaterThan(0)
expect(horizontalRemainder).toBeLessThan(geometry.cellWidth)
expectClose(geometry.screen.left - geometry.pane.left, padding.x)
expectClose(geometry.screen.top - geometry.pane.top, padding.y)
expectClose(
geometry.pane.right - geometry.screen.right,
padding.x + geometry.scrollbarWidth + horizontalRemainder
)
expectClose(geometry.pane.bottom - geometry.screen.bottom, padding.y + verticalRemainder)
return { horizontalRemainder, verticalRemainder }
}
test.describe('terminal padding geometry', () => {
test.beforeEach(async ({ orcaPage }) => {
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
await waitForActiveTerminalManager(orcaPage)
})
// #13252: the old start-edge margin left the grid flush right/bottom. The
// #14583 re-land was reverted, so this guard checks geometry, not its paint mechanism.
test('keeps configured padding on all four edges with a forced cell remainder @terminal-rendering-golden', async ({
orcaPage
}) => {
const baseline = await readTerminalGeometry(orcaPage)
const firstSize = {
width: findNonDivisibleSize(
baseline.cellWidth,
TARGET_COLS,
FIRST_PADDING.x * 2 + baseline.scrollbarWidth
),
height: findNonDivisibleSize(baseline.cellHeight, TARGET_ROWS, FIRST_PADDING.y * 2)
}
await applyPaddingAndPaneSize(orcaPage, FIRST_PADDING, firstSize)
const first = await readTerminalGeometry(orcaPage)
const firstRemainders = assertFourEdgeGeometry(first, FIRST_PADDING)
// Keep usable dimensions constant while changing both setting values.
const secondSize = {
width: firstSize.width + (SECOND_PADDING.x - FIRST_PADDING.x) * 2,
height: firstSize.height + (SECOND_PADDING.y - FIRST_PADDING.y) * 2
}
await applyPaddingAndPaneSize(orcaPage, SECOND_PADDING, secondSize)
const second = await readTerminalGeometry(orcaPage)
const secondRemainders = assertFourEdgeGeometry(second, SECOND_PADDING)
expect(second.cols).toBe(first.cols)
expect(second.rows).toBe(first.rows)
expectClose(secondRemainders.horizontalRemainder, firstRemainders.horizontalRemainder)
expectClose(secondRemainders.verticalRemainder, firstRemainders.verticalRemainder)
})
})