fix: pr-bug-scan findings from #1583 (#1618)

Fixed Windows titlebar bugs: WindowControls icon now seeded via new ipc isMaximized() getter on mount; CSS height dropped from 42px to 36px; spacer added to floating right-sidebar toggle and RightSidebar header so content isn't occluded.

Findings addressed:
- [medium] src/renderer/src/App.tsx:50-54 — WindowControls maximize icon wrong on startup if window starts maximized
- [medium] src/renderer/src/assets/main.css:434-460 — Window controls 42px tall but titlebar 36px — bottom 6px overlays content
- [low] src/renderer/src/App.tsx:776-803 — Spacer only rendered in workspace-active titlebar branch

Rebased onto current main to drop ~140 unrelated stale-main reverts; only the 6 Fixer-summary files are touched.

Co-authored-by: orca-bot <bot@stably.ai>
This commit is contained in:
buf0-bot[bot] 2026-05-09 03:59:12 -07:00 committed by GitHub
parent 6e91777f96
commit 9db736c330
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 53 additions and 9 deletions

View File

@ -655,10 +655,20 @@ export function createMainWindow(
const onPopupMenu = (): void => {
Menu.getApplicationMenu()?.popup({ window: mainWindow })
}
// Why: the renderer's WindowControls mounts after ready-to-show, which is
// also when savedMaximized is restored — so window:maximize-changed has
// already fired (or not fired, if maximize() was called pre-mount) before
// the listener attaches. Expose a synchronous getter so the button can
// initialize its icon to match the current state on mount.
const isMaximizedChannel = 'window:isMaximized'
const onIsMaximized = (): boolean => {
return !mainWindow.isDestroyed() && mainWindow.isMaximized()
}
ipcMain.on(minimizeChannel, onMinimize)
ipcMain.on(maximizeChannel, onMaximize)
ipcMain.on(requestCloseChannel, onRequestClose)
ipcMain.on(popupMenuChannel, onPopupMenu)
ipcMain.handle(isMaximizedChannel, onIsMaximized)
ipcMain.on(confirmCloseChannel, onConfirmClose)
mainWindow.on('closed', () => {
@ -671,6 +681,7 @@ export function createMainWindow(
ipcMain.removeListener(maximizeChannel, onMaximize)
ipcMain.removeListener(requestCloseChannel, onRequestClose)
ipcMain.removeListener(popupMenuChannel, onPopupMenu)
ipcMain.removeHandler(isMaximizedChannel)
ipcMain.removeListener(confirmCloseChannel, onConfirmClose)
ipcMain.removeListener(markdownFocusChannel, onMarkdownEditorFocused)
mainWindow.webContents.removeListener('context-menu', onMainContextMenu)

View File

@ -1101,6 +1101,7 @@ export type PreloadApi = {
onFullscreenChanged: (callback: (isFullScreen: boolean) => void) => () => void
minimize: () => void
maximize: () => void
isMaximized: () => Promise<boolean>
onMaximizeChanged: (callback: (isMaximized: boolean) => void) => () => void
requestClose: () => void
popupMenu: () => void

View File

@ -1970,6 +1970,10 @@ const api = {
maximize: (): void => {
ipcRenderer.send('window:maximize')
},
/** Windows only: read the current maximize state on mount, since
* window:maximize-changed only fires on transitions and a window that
* starts maximized would otherwise show the wrong icon. */
isMaximized: (): Promise<boolean> => ipcRenderer.invoke('window:isMaximized'),
/** Windows only: subscribe to maximize state changes so the renderer-drawn
* maximize button can show the correct restore/maximize icon. */
onMaximizeChanged: (callback: (isMaximized: boolean) => void): (() => void) => {

View File

@ -64,7 +64,20 @@ const isWindows = !isMac && navigator.userAgent.includes('Windows')
function WindowControls(): React.JSX.Element {
const [maximized, setMaximized] = useState(false)
useEffect(() => {
return window.api.ui.onMaximizeChanged(setMaximized)
// Why: window:maximize-changed only fires on transitions, so a window
// restored to a maximized state at startup would render the wrong icon
// until the user first clicks the button. Seed from main on mount.
let cancelled = false
void window.api.ui.isMaximized().then((value) => {
if (!cancelled) {
setMaximized(value)
}
})
const unsubscribe = window.api.ui.onMaximizeChanged(setMaximized)
return () => {
cancelled = true
unsubscribe()
}
}, [])
return (
<div className="window-controls">
@ -1091,12 +1104,18 @@ function App(): React.JSX.Element {
{workspaceActive && !rightSidebarOpen && (
<div
className="absolute top-0 z-10 flex items-center h-[36px]"
style={{
right: 'var(--window-controls-width)',
WebkitAppRegion: 'no-drag'
} as React.CSSProperties}
style={
{
right: 'var(--window-controls-width)',
WebkitAppRegion: 'no-drag'
} as React.CSSProperties
}
>
{rightSidebarToggle}
{/* Why: the fixed-position window-controls overlay (138px,
top-right) sits on top of this floating toggle on Windows.
Reserve its width so the toggle stays clickable. */}
{isWindows && <div className="window-controls-titlebar-spacer" />}
</div>
)}
<div className="flex flex-1 min-w-0 min-h-0 flex-col">

View File

@ -433,7 +433,7 @@
z-index: 9999;
display: flex;
flex-direction: row;
height: 42px;
height: 36px;
-webkit-app-region: no-drag;
}
@ -442,7 +442,7 @@
align-items: center;
justify-content: center;
width: 46px;
height: 42px;
height: 36px;
background: transparent;
border: none;
color: var(--muted-foreground);

View File

@ -72,6 +72,7 @@ type ActivityBarItem = {
}
const isMac = navigator.userAgent.includes('Mac')
const isWindows = !isMac && navigator.userAgent.includes('Windows')
const mod = isMac ? '\u2318' : 'Ctrl+'
const ACTIVITY_ITEMS: ActivityBarItem[] = [
@ -305,7 +306,10 @@ function RightSidebarInner(): React.JSX.Element {
<div className="flex items-center justify-between border-b border-border h-[36px] min-h-[36px] pl-2 pr-1 right-sidebar-header-inset">
<TooltipProvider delayDuration={400}>
<div className="flex items-center">{activityBarIcons}</div>
{closeButton}
<div className="flex items-center">
{closeButton}
{isWindows && <div className="window-controls-titlebar-spacer" />}
</div>
</TooltipProvider>
</div>
</ContextMenuTrigger>
@ -320,7 +324,12 @@ function RightSidebarInner(): React.JSX.Element {
<span className="text-[11px] font-semibold uppercase tracking-wider text-foreground">
{visibleItems.find((item) => item.id === effectiveTab)?.title ?? ''}
</span>
<TooltipProvider delayDuration={400}>{closeButton}</TooltipProvider>
<TooltipProvider delayDuration={400}>
<div className="flex items-center">
{closeButton}
{isWindows && <div className="window-controls-titlebar-spacer" />}
</div>
</TooltipProvider>
</div>
)}