Compare commits

..

2 Commits

Author SHA1 Message Date
NimBold fa8d158f36 test(startup): isolate full renderer without logger IPC (#37)
- Issue #37: test the full 1.4.0 renderer without the module-time logger query.\n- Keep the normal native window lifecycle and all other startup code intact.\n\nRefs #37.
2026-08-27 22:02:53 +03:30
NimBold 8d1cde8d2c fix(startup): restore Windows renderer startup (#37)
- Issue #37: Windows 1.4.0 exited before showing its main window.\n- Keep the logger state read independent of WebViewWindow extraction during WebView2 bootstrap.\n- Preserve main-window authorization for logging mutations and keep the process-wide pause state consistent across renderers.\n\nFixes #37.
2026-08-27 21:23:00 +03:30
3 changed files with 44 additions and 33 deletions
+6 -29
View File
@@ -3993,18 +3993,6 @@ fn mark_main_window_startup_complete(app_handle: &tauri::AppHandle) {
}
}
#[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>()
@@ -18200,9 +18188,12 @@ fn toggle_log_pause(caller: tauri::WebviewWindow, pause: bool) -> Result<(), Str
}
#[tauri::command]
fn is_log_paused(caller: tauri::WebviewWindow) -> bool {
properties_window::ensure_main_window(&caller).is_ok()
&& LOG_PAUSED.load(std::sync::atomic::Ordering::Relaxed)
fn is_log_paused() -> bool {
// This read is needed during renderer bootstrap, including by standalone
// Properties windows. Avoid extracting a WebviewWindow here: on Windows,
// WebView2 can invoke the command while its host is still initializing.
// Mutating logging commands remain restricted to the main window below.
LOG_PAUSED.load(std::sync::atomic::Ordering::Relaxed)
}
#[tauri::command]
@@ -18550,17 +18541,6 @@ pub fn run() {
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}"))?;
@@ -19898,9 +19878,6 @@ pub fn run() {
.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")]
+37 -1
View File
@@ -15,7 +15,8 @@ import { getCurrentWindow } from '@tauri-apps/api/window';
import { initDownloadListener } from './store/downloadStore';
import {
subscribeToSettingsPersistenceErrors,
useSettingsStore
useSettingsStore,
waitForSettingsPersistence
} from "./store/useSettingsStore";
import { isPermissionGranted, requestPermission, sendNotification } from '@tauri-apps/plugin-notification';
import { WindowControls } from "./components/WindowControls";
@@ -39,7 +40,9 @@ 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,
@@ -491,14 +494,44 @@ 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);
});
@@ -517,6 +550,7 @@ function App() {
let unlistenDeepLink: (() => void) | null = null;
const disposeListeners = () => {
void queueFrontendReadyUpdate(false).catch(() => {});
mainWindowSizePersistence.dispose();
unlistenExit?.();
unlistenExit = null;
unlistenTerminalState?.();
@@ -722,6 +756,8 @@ function App() {
cleanupListeners = null;
unlistenExit?.();
unlistenExit = null;
unlistenSettingsHydration?.();
mainWindowSizePersistence.dispose();
disposePersistence();
};
}, [addToast, enqueueAddInput, processExtensionDownload, queueFrontendReadyUpdate]);
+1 -3
View File
@@ -10,14 +10,12 @@ import "./index.css";
import { i18nReady } from "./i18n";
import { ErrorBoundary } from "./components/ErrorBoundary";
import { ToastProvider } from "./contexts/ToastContext";
import { error as logError, warn as logWarn, initLogger } from "./utils/logger";
import { error as logError, warn as logWarn } from "./utils/logger";
import { getCurrentWindow } from '@tauri-apps/api/window';
import { invokeCommand as invoke } from './ipc';
const isPropertiesWindow = getCurrentWindow().label.startsWith('properties-');
void initLogger();
const serializeConsoleArguments = (values: unknown[]) => values.map(value => {
if (value instanceof Error) return `${value.name}: ${value.message}\n${value.stack || ''}`;
if (typeof value === 'string') return value;