🐛 fix(window): 加固 Windows 冷启动半窗,原生最大化并兜底铺满工作区

- Windows 启动 WindowStartState 改为 Maximised,默认尺寸提升到 1440×900
- 无记忆/历史 1024×768/覆盖率过低的普通窗记忆改为启动最大化
- Maximise 多次失败时铺满工作区兜底,避免残留浮动半窗
- 用户刻意拉大的普通窗仍先 Unmaximise 再恢复尺寸位置
This commit is contained in:
Syngnat
2026-07-09 14:02:53 +08:00
parent 901b436107
commit d210b3f39c
5 changed files with 220 additions and 27 deletions

View File

@@ -5,6 +5,9 @@ import {
isStartupWindowRestorePending,
markStartupWindowRestorePending,
resolveDefaultStartupWindowBounds,
resolveWorkAreaFillWindowBounds,
shouldPreferWindowsStartupMaximise,
WINDOWS_STARTUP_MAXIMISE_AREA_RATIO,
} from './windowStartupLayout';
describe('windowStartupLayout', () => {
@@ -69,4 +72,61 @@ describe('windowStartupLayout', () => {
vi.setSystemTime(new Date('2026-07-09T10:00:01.001Z'));
expect(isStartupWindowRestorePending()).toBe(false);
});
it('fills the OS work area as a maximise-failure fallback', () => {
expect(resolveWorkAreaFillWindowBounds({
availWidth: 1920,
availHeight: 1040,
availLeft: 0,
availTop: 0,
})).toEqual({
width: 1920,
height: 1040,
x: 0,
y: 0,
});
expect(resolveWorkAreaFillWindowBounds({
availWidth: 1600,
availHeight: 900,
availLeft: 1920,
availTop: 40,
})).toEqual({
width: 1600,
height: 900,
x: 1920,
y: 40,
});
});
it('prefers maximise for missing, legacy 1024×768, and undersized default windows', () => {
const viewport = {
availWidth: 1920,
availHeight: 1080,
availLeft: 0,
availTop: 0,
};
expect(shouldPreferWindowsStartupMaximise(null, viewport)).toBe(true);
expect(shouldPreferWindowsStartupMaximise({
width: 1024,
height: 768,
x: 0,
y: 0,
}, viewport)).toBe(true);
// 84%×84% default area ≈ 0.706 < 0.78 → 最大化
const defaultBounds = resolveDefaultStartupWindowBounds(viewport);
expect((defaultBounds.width * defaultBounds.height) / (1920 * 1080))
.toBeLessThan(WINDOWS_STARTUP_MAXIMISE_AREA_RATIO);
expect(shouldPreferWindowsStartupMaximise(defaultBounds, viewport)).toBe(true);
// 用户刻意拉大的普通窗应保留
expect(shouldPreferWindowsStartupMaximise({
width: 1760,
height: 980,
x: 80,
y: 40,
}, viewport)).toBe(false);
});
});

View File

@@ -16,6 +16,16 @@ export type StartupWindowBounds = {
const MIN_STARTUP_WIDTH = 900;
const MIN_STARTUP_HEIGHT = 600;
/**
* 工作区覆盖率低于该阈值时,视为「半窗 / 默认小窗」记忆Windows 启动改走最大化。
* 84%×84% 居中默认窗的面积比约为 0.706,会被捕获;用户刻意拉大的普通窗通常更高。
*/
export const WINDOWS_STARTUP_MAXIMISE_AREA_RATIO = 0.78;
/** Align with historical main.go Width/Height defaults that look half-open on modern screens. */
const LEGACY_DEFAULT_WIDTH = 1024;
const LEGACY_DEFAULT_HEIGHT = 768;
/**
* Resolve a usable first-launch window when no persisted bounds exist.
* Windows defaults to top-left 1024x768 which looks "half open" on modern screens.
@@ -50,6 +60,62 @@ export const resolveDefaultStartupWindowBounds = (
};
};
/**
* Fill the OS work area (taskbar excluded). Used when Maximise API fails on Windows
* so the shell still looks "full" instead of lingering at 1024×768 / 84% floating.
*/
export const resolveWorkAreaFillWindowBounds = (
viewport: StartupVisibleViewport,
): StartupWindowBounds => {
const availWidth = Math.max(0, Math.trunc(Number(viewport.availWidth) || 0));
const availHeight = Math.max(0, Math.trunc(Number(viewport.availHeight) || 0));
const availLeft = Math.trunc(Number(viewport.availLeft) || 0);
const availTop = Math.trunc(Number(viewport.availTop) || 0);
if (availWidth <= 0 || availHeight <= 0) {
return resolveDefaultStartupWindowBounds(viewport);
}
return {
width: Math.max(MIN_STARTUP_WIDTH, availWidth),
height: Math.max(MIN_STARTUP_HEIGHT, availHeight),
x: availLeft,
y: availTop,
};
};
/**
* Decide whether Windows cold-start should prefer maximise over restoring bounds.
* - 无记忆 / 非法尺寸 → 最大化
* - 仍像旧默认 1024×768 → 最大化
* - 覆盖工作区面积过低(含历史 84% 居中默认窗)→ 最大化
*/
export const shouldPreferWindowsStartupMaximise = (
bounds: StartupWindowBounds | null | undefined,
viewport: StartupVisibleViewport,
): boolean => {
if (!bounds) {
return true;
}
const width = Math.trunc(Number(bounds.width) || 0);
const height = Math.trunc(Number(bounds.height) || 0);
if (width < 400 || height < 300) {
return true;
}
if (width <= LEGACY_DEFAULT_WIDTH && height <= LEGACY_DEFAULT_HEIGHT) {
return true;
}
const availWidth = Math.max(0, Math.trunc(Number(viewport.availWidth) || 0));
const availHeight = Math.max(0, Math.trunc(Number(viewport.availHeight) || 0));
if (availWidth <= 0 || availHeight <= 0) {
return false;
}
const areaRatio = (width * height) / (availWidth * availHeight);
return areaRatio < WINDOWS_STARTUP_MAXIMISE_AREA_RATIO;
};
let startupWindowRestorePendingUntil = 0;
/** Mark a short grace window while startup maximise/fullscreen is still settling. */