fix: constrain floating terminal to titlebar (#1955)

This commit is contained in:
Neil 2026-05-15 11:57:22 -07:00 committed by GitHub
parent e42c78107c
commit 69700466e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 69 additions and 7 deletions

View File

@ -0,0 +1,61 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import {
TITLEBAR_SAFE_TOP,
clampFloatingTerminalBounds,
getDefaultFloatingTerminalBounds,
getMaximizedFloatingTerminalBounds
} from './floating-terminal-panel-bounds'
function stubViewport(width: number, height: number, userAgent: string): void {
vi.stubGlobal('window', { innerWidth: width, innerHeight: height })
vi.stubGlobal('navigator', { userAgent })
}
describe('floating terminal panel bounds', () => {
afterEach(() => {
vi.unstubAllGlobals()
})
it('lets dragged panels touch the macOS titlebar without going above it', () => {
stubViewport(1200, 800, 'Macintosh')
const bounds = clampFloatingTerminalBounds({
left: 32,
top: 8,
width: 640,
height: 360
})
expect(bounds.top).toBe(TITLEBAR_SAFE_TOP)
})
it('keeps dragged panels below the renderer titlebar on other platforms', () => {
stubViewport(1200, 800, 'Windows NT')
const bounds = clampFloatingTerminalBounds({
left: 32,
top: 8,
width: 640,
height: 360
})
expect(bounds.top).toBe(TITLEBAR_SAFE_TOP)
})
it('maximizes below the renderer titlebar on non-mac platforms', () => {
stubViewport(1200, 800, 'Windows NT')
expect(getMaximizedFloatingTerminalBounds()).toEqual(
expect.objectContaining({
top: TITLEBAR_SAFE_TOP,
height: 800 - TITLEBAR_SAFE_TOP - 36
})
)
})
it('defaults at or below the titlebar on compact macOS windows', () => {
stubViewport(760, 420, 'Macintosh')
expect(getDefaultFloatingTerminalBounds().top).toBeGreaterThanOrEqual(TITLEBAR_SAFE_TOP)
})
})

View File

@ -4,7 +4,7 @@ export const MIN_PANEL_WIDTH = 420
export const MIN_PANEL_HEIGHT = 280
export const MAXIMIZED_MARGIN = 12
export const MAXIMIZED_BOTTOM_GAP = 36
export const MAC_TITLEBAR_SAFE_TOP = 44
export const TITLEBAR_SAFE_TOP = 36
const DEFAULT_RIGHT_GAP = 24
const DEFAULT_BOTTOM_GAP = 84
@ -18,11 +18,14 @@ export type FloatingTerminalPanelBounds = {
export function getDefaultFloatingTerminalBounds(): FloatingTerminalPanelBounds {
const viewportWidth = typeof window === 'undefined' ? 1200 : window.innerWidth
const viewportHeight = typeof window === 'undefined' ? 800 : window.innerHeight
// Why: the floating panel may touch the renderer titlebar, but must not
// overlap it or the native window controls above it.
const safeTop = TITLEBAR_SAFE_TOP
const width = Math.min(DEFAULT_PANEL_WIDTH, Math.max(MIN_PANEL_WIDTH, viewportWidth - 48))
const height = Math.min(DEFAULT_PANEL_HEIGHT, Math.max(MIN_PANEL_HEIGHT, viewportHeight - 96))
return {
left: Math.max(16, viewportWidth - width - DEFAULT_RIGHT_GAP),
top: Math.max(40, viewportHeight - height - DEFAULT_BOTTOM_GAP),
top: Math.max(safeTop, viewportHeight - height - DEFAULT_BOTTOM_GAP),
width,
height
}
@ -35,20 +38,18 @@ export function clampFloatingTerminalBounds(
typeof window === 'undefined' ? bounds.left + bounds.width : window.innerWidth
const viewportHeight =
typeof window === 'undefined' ? bounds.top + bounds.height : window.innerHeight
const safeTop = TITLEBAR_SAFE_TOP
return {
...bounds,
left: Math.min(Math.max(8, bounds.left), Math.max(8, viewportWidth - 80)),
top: Math.min(Math.max(8, bounds.top), Math.max(8, viewportHeight - 80))
top: Math.min(Math.max(safeTop, bounds.top), Math.max(safeTop, viewportHeight - 80))
}
}
export function getMaximizedFloatingTerminalBounds(): FloatingTerminalPanelBounds {
const viewportWidth = typeof window === 'undefined' ? 1200 : window.innerWidth
const viewportHeight = typeof window === 'undefined' ? 800 : window.innerHeight
const top =
typeof navigator !== 'undefined' && navigator.userAgent.includes('Mac')
? MAC_TITLEBAR_SAFE_TOP
: MAXIMIZED_MARGIN
const top = TITLEBAR_SAFE_TOP
return {
left: MAXIMIZED_MARGIN,
top,