diff --git a/src/components/launch/LaunchWindow.test.tsx b/src/components/launch/LaunchWindow.test.tsx index 06c83db94..b65e8518b 100644 --- a/src/components/launch/LaunchWindow.test.tsx +++ b/src/components/launch/LaunchWindow.test.tsx @@ -1358,9 +1358,19 @@ describe("LaunchWindow overlay sizing", () => { stubBox(bar.parentElement as HTMLElement, 400, stackHeight); await flushResizeObservers(); + // The stub sits at (0, 0), which is not where the stack is in the allocated + // window. The reported position must come from the allocation, not the + // measurement: a measured one can belong to a viewport the main process has + // already resized, and anchoring the next layout flip on it walks the bar (#951). + const sizeCalls = ( + window.electronAPI.setHudOverlaySize as unknown as { + mock: { calls: [number, number, unknown][] }; + } + ).mock.calls; + const [allocatedWidth, allocatedHeight] = sizeCalls[sizeCalls.length - 1]; expect(window.electronAPI.setHudOverlayContent).toHaveBeenLastCalledWith({ - x: 0, - y: 0, + x: (allocatedWidth - 400) / 2, + y: allocatedHeight - HUD_BAR_BOTTOM - stackHeight, width: 400, height: stackHeight, }); diff --git a/src/components/launch/LaunchWindow.tsx b/src/components/launch/LaunchWindow.tsx index d92ebe7f4..ca9dd076a 100644 --- a/src/components/launch/LaunchWindow.tsx +++ b/src/components/launch/LaunchWindow.tsx @@ -459,18 +459,18 @@ export function LaunchWindow() { const stackRect = anchorRect?.width && anchorRect.height ? anchorRect : barRect; const stackWidth = stackRect.width || barWidth; const stackHeight = stackRect.height || barHeight; - const currentContent = { - x: stackRect.x, - y: stackRect.y, + const contentFor = (size: { width: number; height: number }) => ({ + x: (size.width - stackWidth) / 2, + y: size.height - HUD_BAR_BOTTOM - stackHeight, width: stackWidth, height: stackHeight, - }; - const grantedContent = { - x: (granted.width - stackWidth) / 2, - y: granted.height - HUD_BAR_BOTTOM - stackHeight, - width: stackWidth, - height: stackHeight, - }; + }); + // Computed from the allocation too, never read off stackRect: right after a + // resize the observer fires while the old viewport is still laid out, and a + // measured position would hand the main process a rect from a window it has + // already replaced — the next flip then anchors on it and walks the bar (#951). + const currentContent = contentFor(allocated); + const grantedContent = contentFor(granted); if (!needsResize) { const last = lastSentHudContentRef.current;