From 3e685338996fd167d10da92c64d5a1abccb0050f Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Tue, 31 Mar 2026 17:26:40 +0800 Subject: [PATCH 1/2] web: show main window behind startup overlay --- src/web-ui/src/app/App.tsx | 54 ++++++++++++++++++++++++++++++++-- src/web-ui/src/main.tsx | 60 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/web-ui/src/app/App.tsx b/src/web-ui/src/app/App.tsx index 2959e9d8..a68de387 100644 --- a/src/web-ui/src/app/App.tsx +++ b/src/web-ui/src/app/App.tsx @@ -15,6 +15,36 @@ import SplashScreen from './components/SplashScreen/SplashScreen'; import { ToolbarModeProvider } from '../flow_chat'; const log = createLogger('App'); +const STARTUP_EPOCH_KEY = '__bitfun_startup_epoch__'; +const STARTUP_STAGE_KEY = '__bitfun_startup_stage_epoch__'; + +function logStartupStage(stage: string, data?: Record): void { + const startupWindow = window as unknown as Window & Record; + const now = performance.now(); + const startupEpoch = + typeof startupWindow[STARTUP_EPOCH_KEY] === 'number' + ? startupWindow[STARTUP_EPOCH_KEY] as number + : now; + const previousStageEpoch = + typeof startupWindow[STARTUP_STAGE_KEY] === 'number' + ? startupWindow[STARTUP_STAGE_KEY] as number + : startupEpoch; + + startupWindow[STARTUP_EPOCH_KEY] = startupEpoch; + startupWindow[STARTUP_STAGE_KEY] = now; + + const stageMs = Math.round(now - previousStageEpoch); + const totalMs = Math.round(now - startupEpoch); + + log.info('Startup timing', { + stage, + stageMs, + totalMs, + stageSeconds: Number((stageMs / 1000).toFixed(3)), + totalSeconds: Number((totalMs / 1000).toFixed(3)), + ...data, + }); +} /** * BitFun main application component. @@ -43,17 +73,25 @@ function App() { const mountTimeRef = useRef(Date.now()); const mainWindowShownRef = useRef(false); + useEffect(() => { + logStartupStage('app.component_mounted'); + }, []); + // Once the workspace finishes loading, wait for the remaining min-display // time and then begin the exit animation. useEffect(() => { if (workspaceLoading) return; const elapsed = Date.now() - mountTimeRef.current; const remaining = Math.max(0, MIN_SPLASH_MS - elapsed); + logStartupStage('workspace.loading_completed', { + remainingSplashMs: remaining, + }); const timer = window.setTimeout(() => setSplashExiting(true), remaining); return () => window.clearTimeout(timer); }, [workspaceLoading]); const handleSplashExited = useCallback(() => { + logStartupStage('splash.exit_completed'); setSplashVisible(false); }, []); @@ -64,8 +102,10 @@ function App() { mainWindowShownRef.current = true; try { + logStartupStage('window.show_requested', { reason }); const { invoke } = await import('@tauri-apps/api/core'); await invoke('show_main_window'); + logStartupStage('window.show_completed', { reason, method: 'invoke' }); log.debug('Main window shown', { reason }); } catch (error: any) { log.error('Failed to show main window', error); @@ -75,6 +115,7 @@ function App() { const mainWindow = getCurrentWindow(); await mainWindow.show(); await mainWindow.setFocus(); + logStartupStage('window.show_completed', { reason, method: 'fallback' }); log.debug('Main window shown via fallback', { reason }); } catch (fallbackError) { log.error('Fallback window show failed', fallbackError); @@ -83,8 +124,14 @@ function App() { } }, []); - // Keep the native window hidden until the startup splash has fully exited. - // This avoids showing a blank/half-painted webview before the first stable frame. + // Reveal the native window as soon as React has painted a frame. + // The splash still covers the UI, so users see immediate feedback instead + // of waiting on a hidden window while startup continues in the background. + useEffect(() => { + void showMainWindow('startup-overlay'); + }, [showMainWindow]); + + // If the early reveal path fails, keep the old post-splash show as a retry. useEffect(() => { if (splashVisible) { return; @@ -109,12 +156,14 @@ function App() { // Startup logs and initialization useEffect(() => { log.info('Application started, initializing systems'); + logStartupStage('app.effect_initializers_started'); // Initialize IDE control system const initIdeControl = async () => { try { const { initializeIdeControl } = await import('../shared/services/ide-control'); await initializeIdeControl(); + logStartupStage('app.ide_control_initialized'); log.debug('IDE control system initialized'); } catch (error) { log.error('Failed to initialize IDE control system', error); @@ -126,6 +175,7 @@ function App() { try { const { MCPAPI } = await import('../infrastructure/api/service-api/MCPAPI'); await MCPAPI.initializeServers(); + logStartupStage('app.mcp_servers_initialized'); log.debug('MCP servers initialized'); } catch (error) { log.error('Failed to initialize MCP servers', error); diff --git a/src/web-ui/src/main.tsx b/src/web-ui/src/main.tsx index 5990482d..7f24937a 100644 --- a/src/web-ui/src/main.tsx +++ b/src/web-ui/src/main.tsx @@ -21,10 +21,56 @@ import { isMinifiedReactErrorMessage, } from './shared/utils/reactProductionError'; +const STARTUP_EPOCH_KEY = '__bitfun_startup_epoch__'; +const STARTUP_STAGE_KEY = '__bitfun_startup_stage_epoch__'; + +function getStartupWindowState(): Window & Record { + return window as unknown as Window & Record; +} + +function initializeStartupClock(): void { + const startupWindow = getStartupWindowState(); + if (typeof startupWindow[STARTUP_EPOCH_KEY] !== 'number') { + const now = performance.now(); + startupWindow[STARTUP_EPOCH_KEY] = now; + startupWindow[STARTUP_STAGE_KEY] = now; + } +} + +function logStartupStage(stage: string, data?: Record): void { + const startupWindow = getStartupWindowState(); + const now = performance.now(); + const startupEpoch = + typeof startupWindow[STARTUP_EPOCH_KEY] === 'number' + ? startupWindow[STARTUP_EPOCH_KEY] as number + : now; + const previousStageEpoch = + typeof startupWindow[STARTUP_STAGE_KEY] === 'number' + ? startupWindow[STARTUP_STAGE_KEY] as number + : startupEpoch; + + startupWindow[STARTUP_EPOCH_KEY] = startupEpoch; + startupWindow[STARTUP_STAGE_KEY] = now; + + const stageMs = Math.round(now - previousStageEpoch); + const totalMs = Math.round(now - startupEpoch); + + log.info('Startup timing', { + stage, + stageMs, + totalMs, + stageSeconds: Number((stageMs / 1000).toFixed(3)), + totalSeconds: Number((totalMs / 1000).toFixed(3)), + ...data, + }); +} + // Install console forwarding before app startup so early console output is persisted too. bootstrapLogger(); +initializeStartupClock(); const log = createLogger('App'); +logStartupStage('web.entry_loaded'); /** Dedupe only for white-screen heuristic (empty #root), not for Error Boundary logs. */ const WHITE_SCREEN_LOGGED_FLAG = '__bitfun_white_screen_crash_logged__'; @@ -218,10 +264,12 @@ async function initializeApp() { try { // Initialize logger state before startup logs. await initLogger(); + logStartupStage('frontend.logger_initialized'); // Sync frontend logger with app.logging.level before startup logs. const { initializeFrontendLogLevelSync } = await import('./infrastructure/config/services/FrontendLogLevelSync'); await initializeFrontendLogLevelSync(); + logStartupStage('frontend.log_level_synced'); log.debug('Monaco loader configured', { vs: monacoPath, isDev }); log.info('Initializing BitFun'); @@ -229,20 +277,24 @@ async function initializeApp() { // Synchronous initialization: core systems that must run first. const { registerDefaultContextTypes } = await import('./shared/context-system/core/registerDefaultTypes'); registerDefaultContextTypes(); + logStartupStage('frontend.context_types_registered'); // Initialize smart recommendation system. const { initRecommendationProviders } = await import('./flow_chat/components/smart-recommendations'); initRecommendationProviders(); + logStartupStage('frontend.recommendations_initialized'); // Initialize theme system. const { themeService } = await import('./infrastructure/theme'); await themeService.initialize(); log.info('Theme system initialized'); + logStartupStage('frontend.theme_initialized'); // Preload editor configuration. const { configManager } = await import('./infrastructure/config'); await configManager.getConfig('editor'); log.info('Editor configuration preloaded'); + logStartupStage('frontend.editor_config_preloaded'); // Note: i18n is initialized by I18nProvider, not here. // This avoids blocking startup and ensures i18n is ready during React render. @@ -289,8 +341,12 @@ async function initializeApp() { }); log.info('BitFun core systems initialized successfully'); + logStartupStage('frontend.parallel_init_completed'); } catch (error) { log.error('Failed to initialize BitFun', error); + logStartupStage('frontend.initialize_failed', { + error: error instanceof Error ? error.message : String(error), + }); } } @@ -310,3 +366,7 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( ); +logStartupStage('react.root_render_scheduled'); +requestAnimationFrame(() => { + logStartupStage('react.first_animation_frame'); +}); From e1097f7b25980b0013b238f42dfa2dd1c2ccce99 Mon Sep 17 00:00:00 2001 From: wgqqqqq Date: Tue, 31 Mar 2026 17:36:03 +0800 Subject: [PATCH 2/2] web: remove startup timing instrumentation --- src/web-ui/src/app/App.tsx | 45 ---------------------------- src/web-ui/src/main.tsx | 60 -------------------------------------- 2 files changed, 105 deletions(-) diff --git a/src/web-ui/src/app/App.tsx b/src/web-ui/src/app/App.tsx index a68de387..5f220ff0 100644 --- a/src/web-ui/src/app/App.tsx +++ b/src/web-ui/src/app/App.tsx @@ -15,37 +15,6 @@ import SplashScreen from './components/SplashScreen/SplashScreen'; import { ToolbarModeProvider } from '../flow_chat'; const log = createLogger('App'); -const STARTUP_EPOCH_KEY = '__bitfun_startup_epoch__'; -const STARTUP_STAGE_KEY = '__bitfun_startup_stage_epoch__'; - -function logStartupStage(stage: string, data?: Record): void { - const startupWindow = window as unknown as Window & Record; - const now = performance.now(); - const startupEpoch = - typeof startupWindow[STARTUP_EPOCH_KEY] === 'number' - ? startupWindow[STARTUP_EPOCH_KEY] as number - : now; - const previousStageEpoch = - typeof startupWindow[STARTUP_STAGE_KEY] === 'number' - ? startupWindow[STARTUP_STAGE_KEY] as number - : startupEpoch; - - startupWindow[STARTUP_EPOCH_KEY] = startupEpoch; - startupWindow[STARTUP_STAGE_KEY] = now; - - const stageMs = Math.round(now - previousStageEpoch); - const totalMs = Math.round(now - startupEpoch); - - log.info('Startup timing', { - stage, - stageMs, - totalMs, - stageSeconds: Number((stageMs / 1000).toFixed(3)), - totalSeconds: Number((totalMs / 1000).toFixed(3)), - ...data, - }); -} - /** * BitFun main application component. * @@ -73,25 +42,17 @@ function App() { const mountTimeRef = useRef(Date.now()); const mainWindowShownRef = useRef(false); - useEffect(() => { - logStartupStage('app.component_mounted'); - }, []); - // Once the workspace finishes loading, wait for the remaining min-display // time and then begin the exit animation. useEffect(() => { if (workspaceLoading) return; const elapsed = Date.now() - mountTimeRef.current; const remaining = Math.max(0, MIN_SPLASH_MS - elapsed); - logStartupStage('workspace.loading_completed', { - remainingSplashMs: remaining, - }); const timer = window.setTimeout(() => setSplashExiting(true), remaining); return () => window.clearTimeout(timer); }, [workspaceLoading]); const handleSplashExited = useCallback(() => { - logStartupStage('splash.exit_completed'); setSplashVisible(false); }, []); @@ -102,10 +63,8 @@ function App() { mainWindowShownRef.current = true; try { - logStartupStage('window.show_requested', { reason }); const { invoke } = await import('@tauri-apps/api/core'); await invoke('show_main_window'); - logStartupStage('window.show_completed', { reason, method: 'invoke' }); log.debug('Main window shown', { reason }); } catch (error: any) { log.error('Failed to show main window', error); @@ -115,7 +74,6 @@ function App() { const mainWindow = getCurrentWindow(); await mainWindow.show(); await mainWindow.setFocus(); - logStartupStage('window.show_completed', { reason, method: 'fallback' }); log.debug('Main window shown via fallback', { reason }); } catch (fallbackError) { log.error('Fallback window show failed', fallbackError); @@ -156,14 +114,12 @@ function App() { // Startup logs and initialization useEffect(() => { log.info('Application started, initializing systems'); - logStartupStage('app.effect_initializers_started'); // Initialize IDE control system const initIdeControl = async () => { try { const { initializeIdeControl } = await import('../shared/services/ide-control'); await initializeIdeControl(); - logStartupStage('app.ide_control_initialized'); log.debug('IDE control system initialized'); } catch (error) { log.error('Failed to initialize IDE control system', error); @@ -175,7 +131,6 @@ function App() { try { const { MCPAPI } = await import('../infrastructure/api/service-api/MCPAPI'); await MCPAPI.initializeServers(); - logStartupStage('app.mcp_servers_initialized'); log.debug('MCP servers initialized'); } catch (error) { log.error('Failed to initialize MCP servers', error); diff --git a/src/web-ui/src/main.tsx b/src/web-ui/src/main.tsx index 7f24937a..5990482d 100644 --- a/src/web-ui/src/main.tsx +++ b/src/web-ui/src/main.tsx @@ -21,56 +21,10 @@ import { isMinifiedReactErrorMessage, } from './shared/utils/reactProductionError'; -const STARTUP_EPOCH_KEY = '__bitfun_startup_epoch__'; -const STARTUP_STAGE_KEY = '__bitfun_startup_stage_epoch__'; - -function getStartupWindowState(): Window & Record { - return window as unknown as Window & Record; -} - -function initializeStartupClock(): void { - const startupWindow = getStartupWindowState(); - if (typeof startupWindow[STARTUP_EPOCH_KEY] !== 'number') { - const now = performance.now(); - startupWindow[STARTUP_EPOCH_KEY] = now; - startupWindow[STARTUP_STAGE_KEY] = now; - } -} - -function logStartupStage(stage: string, data?: Record): void { - const startupWindow = getStartupWindowState(); - const now = performance.now(); - const startupEpoch = - typeof startupWindow[STARTUP_EPOCH_KEY] === 'number' - ? startupWindow[STARTUP_EPOCH_KEY] as number - : now; - const previousStageEpoch = - typeof startupWindow[STARTUP_STAGE_KEY] === 'number' - ? startupWindow[STARTUP_STAGE_KEY] as number - : startupEpoch; - - startupWindow[STARTUP_EPOCH_KEY] = startupEpoch; - startupWindow[STARTUP_STAGE_KEY] = now; - - const stageMs = Math.round(now - previousStageEpoch); - const totalMs = Math.round(now - startupEpoch); - - log.info('Startup timing', { - stage, - stageMs, - totalMs, - stageSeconds: Number((stageMs / 1000).toFixed(3)), - totalSeconds: Number((totalMs / 1000).toFixed(3)), - ...data, - }); -} - // Install console forwarding before app startup so early console output is persisted too. bootstrapLogger(); -initializeStartupClock(); const log = createLogger('App'); -logStartupStage('web.entry_loaded'); /** Dedupe only for white-screen heuristic (empty #root), not for Error Boundary logs. */ const WHITE_SCREEN_LOGGED_FLAG = '__bitfun_white_screen_crash_logged__'; @@ -264,12 +218,10 @@ async function initializeApp() { try { // Initialize logger state before startup logs. await initLogger(); - logStartupStage('frontend.logger_initialized'); // Sync frontend logger with app.logging.level before startup logs. const { initializeFrontendLogLevelSync } = await import('./infrastructure/config/services/FrontendLogLevelSync'); await initializeFrontendLogLevelSync(); - logStartupStage('frontend.log_level_synced'); log.debug('Monaco loader configured', { vs: monacoPath, isDev }); log.info('Initializing BitFun'); @@ -277,24 +229,20 @@ async function initializeApp() { // Synchronous initialization: core systems that must run first. const { registerDefaultContextTypes } = await import('./shared/context-system/core/registerDefaultTypes'); registerDefaultContextTypes(); - logStartupStage('frontend.context_types_registered'); // Initialize smart recommendation system. const { initRecommendationProviders } = await import('./flow_chat/components/smart-recommendations'); initRecommendationProviders(); - logStartupStage('frontend.recommendations_initialized'); // Initialize theme system. const { themeService } = await import('./infrastructure/theme'); await themeService.initialize(); log.info('Theme system initialized'); - logStartupStage('frontend.theme_initialized'); // Preload editor configuration. const { configManager } = await import('./infrastructure/config'); await configManager.getConfig('editor'); log.info('Editor configuration preloaded'); - logStartupStage('frontend.editor_config_preloaded'); // Note: i18n is initialized by I18nProvider, not here. // This avoids blocking startup and ensures i18n is ready during React render. @@ -341,12 +289,8 @@ async function initializeApp() { }); log.info('BitFun core systems initialized successfully'); - logStartupStage('frontend.parallel_init_completed'); } catch (error) { log.error('Failed to initialize BitFun', error); - logStartupStage('frontend.initialize_failed', { - error: error instanceof Error ? error.message : String(error), - }); } } @@ -366,7 +310,3 @@ ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( ); -logStartupStage('react.root_render_scheduled'); -requestAnimationFrame(() => { - logStartupStage('react.first_animation_frame'); -});