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:
parent
6e91777f96
commit
9db736c330
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue