diff --git a/src/components/Dashboard/common/DashboardNavigation.jsx b/src/components/Dashboard/common/DashboardNavigation.jsx
index eb3890a..2dc40cb 100644
--- a/src/components/Dashboard/common/DashboardNavigation.jsx
+++ b/src/components/Dashboard/common/DashboardNavigation.jsx
@@ -64,7 +64,7 @@ const DashboardNavigation = () => {
icon:
})
const isMobile = useMediaQuery({ maxWidth: 768 })
- const { platform, isElectron, setSidebarViewMenu } =
+ const { platform, isElectron, setSidebarViewMenu, isFullscreen } =
useContext(ElectronContext)
const { availableUpdate, checkForUpdates } = useAppUpdateContext()
const mainMenuItems = useMemo(
@@ -349,7 +349,7 @@ const DashboardNavigation = () => {
<>
{isElectron ? (
{navigationContents}
diff --git a/src/desktop/window.js b/src/desktop/window.js
index 57ca70a..cd05d52 100644
--- a/src/desktop/window.js
+++ b/src/desktop/window.js
@@ -184,6 +184,8 @@ function applyWindowsStartupWindowState(window) {
setTimeout(() => {
window.maximize?.()
+ // maximize() settles asynchronously; sync state after the frame updates.
+ setTimeout(() => handleWindowsWindowChange(window), 100)
}, WINDOWS_STARTUP_MAXIMIZE_DELAY_MS)
}
@@ -339,17 +341,23 @@ export function handleWindowControl(action) {
case 'minimize':
mainWindow.minimize?.()
break
- case 'maximize':
- if (mainWindow.isMaximized?.()) {
+ case 'maximize': {
+ const currentlyMaximized = isWindows
+ ? isWindowWorkAreaMaximized(mainWindow)
+ : (mainWindow.isMaximized?.() ?? false)
+
+ if (currentlyMaximized) {
mainWindow.unmaximize?.()
} else {
mainWindow.maximize?.()
}
if (isWindows) {
- handleWindowsWindowChange(mainWindow)
+ // Frame updates after maximize/unmaximize are async on Windows.
+ setTimeout(() => handleWindowsWindowChange(mainWindow), 100)
}
break
+ }
case 'fullscreen':
if (mainWindow.isFullScreen?.()) {
mainWindow.setFullScreen?.(false)
diff --git a/src/desktop/windows-work-area.js b/src/desktop/windows-work-area.js
index ce49de7..141c5c3 100644
--- a/src/desktop/windows-work-area.js
+++ b/src/desktop/windows-work-area.js
@@ -1,6 +1,15 @@
import { Screen } from 'electrobun/bun'
const FRAME_TOLERANCE_PX = 4
+// Win32 DWM invisible borders sit outside the client work area; Electrobun's
+// maximized frame includes those overhangs, so we treat this adjusted rect as
+// the canonical "maximized to work area" geometry.
+const WIN32_FRAME_OVERHANG = {
+ x: -8,
+ y: 0,
+ width: 16,
+ height: 8
+}
function framesMatch(a, b, tolerance = FRAME_TOLERANCE_PX) {
return (
@@ -11,6 +20,15 @@ function framesMatch(a, b, tolerance = FRAME_TOLERANCE_PX) {
)
}
+function getMaximizedWorkAreaFrame(workArea) {
+ return {
+ x: workArea.x + WIN32_FRAME_OVERHANG.x,
+ y: workArea.y + WIN32_FRAME_OVERHANG.y,
+ width: workArea.width + WIN32_FRAME_OVERHANG.width,
+ height: workArea.height + WIN32_FRAME_OVERHANG.height
+ }
+}
+
function getFrameCenter(frame) {
return {
x: frame.x + frame.width / 2,
@@ -50,7 +68,10 @@ export function isWindowWorkAreaMaximized(window) {
const frame = window.getFrame()
const { workArea } = getDisplayForFrame(frame)
- return framesMatch(frame, workArea)
+ return (
+ framesMatch(frame, workArea) ||
+ framesMatch(frame, getMaximizedWorkAreaFrame(workArea))
+ )
}
export function clampWindowToWorkArea(window) {
@@ -69,8 +90,9 @@ export function clampWindowToWorkArea(window) {
const display = getDisplayForFrame(frame)
const { bounds, workArea } = display
+ const maximizedFrame = getMaximizedWorkAreaFrame(workArea)
- if (framesMatch(frame, workArea)) {
+ if (framesMatch(frame, workArea) || framesMatch(frame, maximizedFrame)) {
return false
}
@@ -78,6 +100,11 @@ export function clampWindowToWorkArea(window) {
return false
}
- window.setFrame(workArea.x - 8, workArea.y, workArea.width + 16, workArea.height + 8)
+ window.setFrame(
+ maximizedFrame.x,
+ maximizedFrame.y,
+ maximizedFrame.width,
+ maximizedFrame.height
+ )
return true
}