Fix Windows right sidebar titlebar overlap (#2266)

This commit is contained in:
Jinwoo Hong 2026-05-18 16:09:31 -04:00 committed by GitHub
parent 83feaa5817
commit b25ea6b5bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 175 additions and 4 deletions

View File

@ -301,14 +301,19 @@ function RightSidebarInner(): React.JSX.Element {
{activityBarPosition === 'top' ? (
/* ── Top activity bar: horizontal icon row ── */
<ContextMenu>
<div className="flex items-center justify-between border-b border-border h-[36px] min-h-[36px] pl-2 pr-1 right-sidebar-header-inset right-sidebar-header-drag">
<div className="flex items-center border-b border-border h-[36px] min-h-[36px] pl-2 pr-1 right-sidebar-header-inset right-sidebar-header-drag overflow-hidden">
<TooltipProvider delayDuration={400}>
<ContextMenuTrigger asChild>
<div className="flex items-center right-sidebar-header-no-drag">
{activityBarIcons}
<div className="right-sidebar-activity-strip flex min-w-0 flex-1 items-center overflow-x-auto overflow-y-hidden scrollbar-sleek right-sidebar-header-no-drag">
{/* Why: Windows window controls can leave less safe header width
than the activity buttons need; scroll inside the safe area
instead of letting buttons extend under the overlay. */}
<div className="flex shrink-0">{activityBarIcons}</div>
</div>
</ContextMenuTrigger>
<div className="flex items-center">{closeButton}</div>
<div className="flex shrink-0 items-center right-sidebar-header-no-drag">
{closeButton}
</div>
</TooltipProvider>
</div>
<ActivityBarPositionMenu

View File

@ -0,0 +1,166 @@
import { test, expect } from './helpers/orca-app'
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
type RightSidebarHeaderGeometry = {
controlsLeft: number
stripRight: number
closeRight: number
stripScrollWidth: number
stripClientWidth: number
firstButtonCenterHitsFirst: boolean
}
type ScrolledActivityButtonGeometry = {
controlsLeft: number
lastButtonRight: number
lastButtonCenterHitsLast: boolean
}
test.describe('Right sidebar Windows titlebar spacing', () => {
test('top activity buttons stay reachable when the right sidebar is narrowed', async ({
orcaPage
}) => {
await orcaPage.addInitScript(() => {
const userAgent =
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/146 Safari/537.36'
Object.defineProperty(navigator, 'userAgent', {
get: () => userAgent,
configurable: true
})
})
await orcaPage.reload({ waitUntil: 'domcontentloaded' })
await orcaPage.waitForFunction(() => Boolean(window.__store), null, { timeout: 30_000 })
await waitForSessionReady(orcaPage)
await waitForActiveWorktree(orcaPage)
await ensureTerminalVisible(orcaPage)
await expect
.poll(
async () =>
orcaPage.evaluate(() => ({
hasWindowsUserAgent: navigator.userAgent.includes('Windows'),
hasWindowsTitlebarChrome: Boolean(document.querySelector('.window-controls'))
})),
{
timeout: 5_000,
message: 'Renderer did not switch to the Windows titlebar branch'
}
)
.toEqual({ hasWindowsUserAgent: true, hasWindowsTitlebarChrome: true })
await orcaPage.evaluate(() => {
const store = window.__store
if (!store) {
throw new Error('window.__store is not available - is the app in dev mode?')
}
store.setState({
activityBarPosition: 'top',
rightSidebarOpen: true,
rightSidebarWidth: 220
})
})
const measureHeader = async (): Promise<RightSidebarHeaderGeometry | null> =>
orcaPage.evaluate(() => {
const controls = document.querySelector<HTMLElement>('.window-controls')
const header = document.querySelector<HTMLElement>('.right-sidebar-header-inset')
const strip = header?.querySelector<HTMLElement>('.right-sidebar-activity-strip') ?? null
const closeButton =
header?.querySelector<HTMLButtonElement>('button[aria-label="Toggle right sidebar"]') ??
null
const activityButtons = Array.from(
header?.querySelectorAll<HTMLButtonElement>(
'button[aria-label]:not([aria-label="Toggle right sidebar"])'
) ?? []
)
const firstButton = activityButtons[0]
if (!controls || !header || !strip || !closeButton || !firstButton) {
return null
}
const controlsRect = controls.getBoundingClientRect()
const stripRect = strip.getBoundingClientRect()
const closeRect = closeButton.getBoundingClientRect()
const firstRect = firstButton.getBoundingClientRect()
const firstCenterX = firstRect.left + firstRect.width / 2
const firstCenterY = firstRect.top + firstRect.height / 2
const elementAtFirstCenter = document.elementFromPoint(firstCenterX, firstCenterY)
return {
controlsLeft: controlsRect.left,
stripRight: stripRect.right,
closeRight: closeRect.right,
stripScrollWidth: strip.scrollWidth,
stripClientWidth: strip.clientWidth,
firstButtonCenterHitsFirst:
elementAtFirstCenter !== null && firstButton.contains(elementAtFirstCenter)
}
})
let headerGeometry: RightSidebarHeaderGeometry | null = null
await expect
.poll(
async () => {
headerGeometry = await measureHeader()
return headerGeometry !== null && headerGeometry.stripClientWidth > 0
},
{
timeout: 5_000,
message: 'Right sidebar header never reached a measurable narrowed state'
}
)
.toBe(true)
expect(headerGeometry).not.toBeNull()
expect(headerGeometry!.stripRight).toBeLessThanOrEqual(headerGeometry!.controlsLeft)
expect(headerGeometry!.closeRight).toBeLessThanOrEqual(headerGeometry!.controlsLeft)
expect(headerGeometry!.stripScrollWidth).toBeGreaterThan(headerGeometry!.stripClientWidth)
expect(headerGeometry!.firstButtonCenterHitsFirst).toBe(true)
await orcaPage.evaluate(() => {
const strip = document.querySelector<HTMLElement>('.right-sidebar-activity-strip')
if (!strip) {
throw new Error('Right sidebar activity strip is missing')
}
strip.scrollLeft = strip.scrollWidth
})
// Why: hidden Electron e2e windows can throttle requestAnimationFrame for a
// long time; scrollLeft is synchronous, so a short Playwright-side delay is
// enough to let layout settle without depending on renderer frame cadence.
await orcaPage.waitForTimeout(50)
const scrolledGeometry = await orcaPage.evaluate((): ScrolledActivityButtonGeometry | null => {
const controls = document.querySelector<HTMLElement>('.window-controls')
const header = document.querySelector<HTMLElement>('.right-sidebar-header-inset')
const activityButtons = Array.from(
header?.querySelectorAll<HTMLButtonElement>(
'button[aria-label]:not([aria-label="Toggle right sidebar"])'
) ?? []
)
const lastButton = activityButtons.at(-1)
if (!controls || !lastButton) {
return null
}
const controlsRect = controls.getBoundingClientRect()
const lastRect = lastButton.getBoundingClientRect()
const lastCenterX = lastRect.left + lastRect.width / 2
const lastCenterY = lastRect.top + lastRect.height / 2
const elementAtLastCenter = document.elementFromPoint(lastCenterX, lastCenterY)
return {
controlsLeft: controlsRect.left,
lastButtonRight: lastRect.right,
lastButtonCenterHitsLast:
elementAtLastCenter !== null && lastButton.contains(elementAtLastCenter)
}
})
expect(scrolledGeometry).not.toBeNull()
expect(scrolledGeometry!.lastButtonRight).toBeLessThanOrEqual(scrolledGeometry!.controlsLeft)
expect(scrolledGeometry!.lastButtonCenterHitsLast).toBe(true)
})
})