Compare commits

..

1 Commits

Author SHA1 Message Date
NimBold 8931f14f72 test(startup): isolate window persistence (#37)
- Issue #37: remove only the renderer's main-window resize persistence from the Windows package comparison.\n- Keep native startup geometry, the application code, and the delayed stability smoke unchanged so the result isolates the frontend persistence owner.\n\nRefs #37.
2026-08-27 19:03:29 +03:30
6 changed files with 88 additions and 41 deletions
+83
View File
@@ -3659,6 +3659,7 @@ pub struct AppState {
#[derive(Default)]
struct MainWindowRestoreState {
requested: AtomicBool,
startup_complete: AtomicBool,
}
#[cfg(target_os = "macos")]
@@ -3955,6 +3956,25 @@ where
}
pub(crate) fn restore_main_window(app_handle: &tauri::AppHandle) {
let startup_complete = app_handle
.try_state::<MainWindowRestoreState>()
.is_none_or(|state| state.startup_complete.load(Ordering::Acquire));
if !startup_complete {
if let Some(state) = app_handle.try_state::<MainWindowRestoreState>() {
// The first window can be requested by a single-instance callback
// or an opened .torrent path while the native host is still being
// constructed. Do not touch the HWND until RunEvent::Ready.
state.requested.store(true, Ordering::Release);
// Ready may have won the transition between the first load and
// the request store. Re-check before returning so that request is
// serviced by this call instead of being left pending forever.
if !state.startup_complete.load(Ordering::Acquire) {
return;
}
} else {
return;
}
}
let Some(window) = app_handle.get_webview_window("main") else {
if let Some(state) = app_handle.try_state::<MainWindowRestoreState>() {
state.requested.store(true, Ordering::Release);
@@ -3967,6 +3987,24 @@ pub(crate) fn restore_main_window(app_handle: &tauri::AppHandle) {
let _ = window.set_focus();
}
fn mark_main_window_startup_complete(app_handle: &tauri::AppHandle) {
if let Some(state) = app_handle.try_state::<MainWindowRestoreState>() {
state.startup_complete.store(true, Ordering::Release);
}
}
#[cfg(target_os = "windows")]
fn reveal_main_window(app_handle: &tauri::AppHandle) {
if let Some(window) = app_handle.get_webview_window("main") {
if let Err(error) = window.set_focusable(true) {
log::warn!("Could not make the main window focusable: {error}");
}
if let Err(error) = window.show() {
eprintln!("Failed to reveal the main window: {error}");
}
}
}
fn restore_pending_main_window(app_handle: &tauri::AppHandle) {
if app_handle
.try_state::<MainWindowRestoreState>()
@@ -18485,6 +18523,44 @@ pub fn run() {
// Build the window only after all command state is registered. This
// prevents the frontend from racing startup and invoking IPC before
// the database and portable storage layout are available.
let startup_size = persisted_settings
.as_ref()
.and_then(|settings| settings.main_window_size.as_ref())
.and_then(|size| crate::window_geometry::normalize_main_window_size(Some(size)))
.unwrap_or_else(crate::window_geometry::default_main_window_size);
let startup_size = app
.primary_monitor()
.ok()
.flatten()
.and_then(|monitor| {
let scale_factor = monitor.scale_factor();
if !scale_factor.is_finite() || scale_factor <= 0.0 {
return None;
}
let work_area = monitor.work_area().size;
let logical_width = (work_area.width as f64 / scale_factor).round() as u32;
let logical_height = (work_area.height as f64 / scale_factor).round() as u32;
Some(crate::window_geometry::clamp_main_window_size(
startup_size.clone(),
logical_width,
logical_height,
))
})
.unwrap_or(startup_size);
main_window_builder = main_window_builder
.inner_size(startup_size.width as f64, startup_size.height as f64)
.prevent_overflow();
#[cfg(target_os = "windows")]
{
// Wry installs its parent WM_SETFOCUS handler while WebView2
// is still being initialized. Keep the host hidden and
// non-activatable until RunEvent::Ready so Windows cannot
// re-enter that handler during native construction.
main_window_builder = main_window_builder
.visible(false)
.focused(false)
.focusable(false);
}
main_window_builder
.build()
.map_err(|error| format!("failed to create main window: {error}"))?;
@@ -19820,6 +19896,13 @@ pub fn run() {
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|app_handle, event| match event {
tauri::RunEvent::Ready => {
mark_main_window_startup_complete(app_handle);
#[cfg(target_os = "windows")]
reveal_main_window(app_handle);
#[cfg(not(target_os = "windows"))]
restore_pending_main_window(app_handle);
}
#[cfg(target_os = "macos")]
tauri::RunEvent::Opened { urls } => {
let paths = collect_opened_torrent_paths(
+1 -1
View File
@@ -16,7 +16,7 @@
"create": false,
"title": "Firelink",
"width": 1280,
"height": 760,
"height": 800,
"minWidth": 960,
"minHeight": 640,
"transparent": false
+1 -1
View File
@@ -5,7 +5,7 @@
"create": false,
"title": "Firelink",
"width": 1280,
"height": 760,
"height": 800,
"minWidth": 960,
"minHeight": 640,
"transparent": false,
+1 -1
View File
@@ -5,7 +5,7 @@
"create": false,
"title": "Firelink",
"width": 1280,
"height": 760,
"height": 800,
"minWidth": 960,
"minHeight": 640,
"transparent": true,
+1 -1
View File
@@ -5,7 +5,7 @@
"create": false,
"title": "Firelink",
"width": 1280,
"height": 760,
"height": 800,
"minWidth": 960,
"minHeight": 640,
"transparent": true,
+1 -37
View File
@@ -15,8 +15,7 @@ import { getCurrentWindow } from '@tauri-apps/api/window';
import { initDownloadListener } from './store/downloadStore';
import {
subscribeToSettingsPersistenceErrors,
useSettingsStore,
waitForSettingsPersistence
useSettingsStore
} from "./store/useSettingsStore";
import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/plugin-notification';
import { WindowControls } from "./components/WindowControls";
@@ -40,9 +39,7 @@ import { changeAppLocale, localeDirection, resolveAppLocale, syncDocumentLocale
import { useTranslation } from 'react-i18next';
import { formatDownloadBytes } from './utils/downloadProgress';
import { synchronizeDocumentAppearance } from './utils/documentAppearance';
import { createMainWindowSizePersistence } from './utils/mainWindowState';
import { createSidebarResizeSession } from './utils/sidebarResize';
import type { MainWindowSize } from './bindings/MainWindowSize';
import {
beginSchedulerControl,
consumeSchedulerHandoffIds,
@@ -494,44 +491,14 @@ function App() {
useEffect(() => {
const disposePersistence = initializeDownloadPersistence(getCurrentWindow().label);
let active = true;
let exitRequested = false;
let exiting = false;
let settingsHydrated = useSettingsStore.persist.hasHydrated();
let latestSizeBeforeHydration: MainWindowSize | null = null;
const unlistenSettingsHydration = settingsHydrated
? null
: useSettingsStore.persist.onFinishHydration(() => {
settingsHydrated = true;
const size = latestSizeBeforeHydration;
latestSizeBeforeHydration = null;
if (size && active && !exitRequested && !exiting) {
useSettingsStore.getState().setMainWindowSize(size);
}
});
const mainWindowSizePersistence = createMainWindowSizePersistence({
appWindow: getCurrentWindow(),
onSize: size => {
if (!active || exiting) return;
if (!settingsHydrated) {
latestSizeBeforeHydration = size;
return;
}
useSettingsStore.getState().setMainWindowSize(size);
}
});
let cleanupListeners: (() => void) | null = null;
let unlistenExit: (() => void) | null = null;
const exitListener = listen('app-exit-requested', async () => {
exitRequested = true;
try {
await mainWindowSizePersistence.flush();
await waitForSettingsPersistence();
await flushDownloadPersistence();
} catch (error) {
console.error('Failed to flush download state before exit:', error);
} finally {
exiting = true;
latestSizeBeforeHydration = null;
await invoke('ack_frontend_exit').catch(error => {
console.error('Failed to acknowledge frontend exit flush:', error);
});
@@ -550,7 +517,6 @@ function App() {
let unlistenDeepLink: (() => void) | null = null;
const disposeListeners = () => {
void queueFrontendReadyUpdate(false).catch(() => {});
mainWindowSizePersistence.dispose();
unlistenExit?.();
unlistenExit = null;
unlistenTerminalState?.();
@@ -756,8 +722,6 @@ function App() {
cleanupListeners = null;
unlistenExit?.();
unlistenExit = null;
unlistenSettingsHydration?.();
mainWindowSizePersistence.dispose();
disposePersistence();
};
}, [addToast, enqueueAddInput, processExtensionDownload, queueFrontendReadyUpdate]);