Fix Windows right sidebar titlebar overlap (#2266)
This commit is contained in:
parent
83feaa5817
commit
b25ea6b5bd
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
Loading…
Reference in New Issue