()
| 89 | } |
| 90 | |
| 91 | function MainApp() { |
| 92 | const platform = usePlatform(); |
| 93 | const [serverReady, setServerReady] = useState(false); |
| 94 | const [startupError, setStartupError] = useState<string | null>(null); |
| 95 | const [loadingMessageIndex, setLoadingMessageIndex] = useState(0); |
| 96 | const serverStartingRef = useRef(false); |
| 97 | |
| 98 | // Automatically check for app updates on startup and show toast notifications |
| 99 | useAutoUpdater({ checkOnMount: true, showToast: true }); |
| 100 | |
| 101 | // Replay the saved chord into the Rust hotkey listener every time |
| 102 | // capture_settings resolves or the user edits the chord. |
| 103 | useChordSync(); |
| 104 | |
| 105 | // Sync stored setting to Rust on startup |
| 106 | useEffect(() => { |
| 107 | if (platform.metadata.isTauri) { |
| 108 | const keepRunning = useServerStore.getState().keepServerRunningOnClose; |
| 109 | platform.lifecycle.setKeepServerRunning(keepRunning).catch((error) => { |
| 110 | console.error('Failed to sync initial setting to Rust:', error); |
| 111 | }); |
| 112 | } |
| 113 | // Empty dependency array - platform is stable from context, only run once |
| 114 | // eslint-disable-next-line react-hooks/exhaustive-deps |
| 115 | }, [platform.metadata.isTauri, platform.lifecycle]); |
| 116 | |
| 117 | // Setup lifecycle callbacks |
| 118 | useEffect(() => { |
| 119 | platform.lifecycle.onServerReady = () => { |
| 120 | setServerReady(true); |
| 121 | }; |
| 122 | // Empty dependency array - platform is stable from context, only run once |
| 123 | // eslint-disable-next-line react-hooks/exhaustive-deps |
| 124 | }, [platform.lifecycle]); |
| 125 | |
| 126 | // Subscribe to server logs |
| 127 | useEffect(() => { |
| 128 | const unsubscribe = platform.lifecycle.subscribeToServerLogs((entry) => { |
| 129 | useLogStore.getState().addEntry(entry); |
| 130 | }); |
| 131 | return unsubscribe; |
| 132 | }, [platform.lifecycle]); |
| 133 | |
| 134 | // Setup window close handler and auto-start server when running in Tauri (production only) |
| 135 | useEffect(() => { |
| 136 | if (!platform.metadata.isTauri) { |
| 137 | const serverUrl = getDefaultServerUrl(); |
| 138 | const currentServerUrl = useServerStore.getState().serverUrl; |
| 139 | if (currentServerUrl !== serverUrl && isLoopbackVoiceboxServerUrl(currentServerUrl)) { |
| 140 | useServerStore.getState().setServerUrl(serverUrl); |
| 141 | } |
| 142 | setServerReady(true); // Web assumes server is running |
| 143 | return; |
| 144 | } |
| 145 | |
| 146 | // Setup window close handler to check setting and stop server if needed |
| 147 | // This works in both dev and prod, but will only stop server if it was started by the app |
| 148 | platform.lifecycle.setupWindowCloseHandler().catch((error) => { |
nothing calls this directly
no test coverage detected