fix: resolve macOS 12 window not showing due to WebKit top-level-await bug

On macOS 12 (Monterey), the app launches but no main window appears.
The root cause is WebKit bug #242740: Safari 15's WKWebView has a race
condition with top-level await in ES modules, causing silent JS failure
that prevents `getCurrentWindow().show()` from ever being called.

Three complementary fixes:

1. Change Vite build target from `esnext` to `safari14` (Tauri recommended)
   and add `vite-plugin-top-level-await` to transform TLA into compatible
   Promise chains, avoiding the WebKit bug entirely.

2. Replace top-level `await import("./App")` in main.tsx with a `.then()`
   chain, removing TLA dependency at the source level.

3. Add a Rust-side 8-second timeout safety net in `setup()` that
   force-shows the main window if the frontend JS fails to do so,
   preventing permanently hidden windows regardless of the failure cause.

Closes #95

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
delibae 2026-03-26 19:57:52 +09:00
parent 5d0be68695
commit bdc57978ba
5 changed files with 229 additions and 9 deletions

View file

@ -68,6 +68,7 @@
"tailwindcss": "^4.1.18",
"typescript": "^5.9.3",
"vite": "^6.3.5",
"vite-plugin-top-level-await": "^1.6.0",
"vitest": "^3.1.1"
}
}

View file

@ -326,7 +326,27 @@ pub fn run() {
.manage(claude::ClaudeProcessState::default())
.manage(latex::LatexCompilerState::default())
.manage(zotero::ZoteroOAuthState::default())
.setup(|_app| Ok(()))
.setup(|app| {
// Safety net: force-show the main window after a timeout if the
// frontend JS never calls `getCurrentWindow().show()`.
// This prevents the window from staying permanently hidden when
// WKWebView fails to execute JS (e.g. WebKit top-level-await bug
// on macOS 12). See https://bugs.webkit.org/show_bug.cgi?id=242740
let handle = app.handle().clone();
tauri::async_runtime::spawn(async move {
tokio::time::sleep(std::time::Duration::from_secs(8)).await;
if let Some(window) = handle.get_webview_window("main") {
if !window.is_visible().unwrap_or(true) {
eprintln!(
"[safety] Main window still hidden after 8s, force-showing"
);
let _ = window.show();
let _ = window.set_focus();
}
}
});
Ok(())
})
.invoke_handler(tauri::generate_handler![
create_new_window,
detect_editors,

View file

@ -73,10 +73,11 @@ if (isDebugWindow) {
});
} else {
// Main app window
const { App } = await import("./App");
ReactDOM.createRoot(rootEl).render(
<React.StrictMode>
<App onReady={hideLoadingScreen} />
</React.StrictMode>,
);
import("./App").then(({ App }) => {
ReactDOM.createRoot(rootEl).render(
<React.StrictMode>
<App onReady={hideLoadingScreen} />
</React.StrictMode>,
);
});
}

View file

@ -1,11 +1,12 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import topLevelAwait from "vite-plugin-top-level-await";
import path from "node:path";
const host = process.env.TAURI_DEV_HOST;
export default defineConfig({
plugins: [react()],
plugins: [react(), topLevelAwait()],
resolve: {
alias: {
"@": path.resolve(__dirname, "src"),
@ -35,7 +36,8 @@ export default defineConfig({
},
envPrefix: ["VITE_", "TAURI_"],
build: {
target: "esnext",
target:
process.env.TAURI_ENV_PLATFORM === "windows" ? "chrome105" : "safari14",
minify: !process.env.TAURI_ENV_DEBUG ? "esbuild" : false,
sourcemap: !!process.env.TAURI_ENV_DEBUG,
},

196
pnpm-lock.yaml generated
View file

@ -184,6 +184,9 @@ importers:
vite:
specifier: ^6.3.5
version: 6.4.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.2)
vite-plugin-top-level-await:
specifier: ^1.6.0
version: 1.6.0(rollup@4.59.0)(vite@6.4.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.2))
vitest:
specifier: ^3.1.1
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.10)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.2)(msw@2.12.10(@types/node@25.0.10)(typescript@5.9.3))(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.2)
@ -2555,6 +2558,15 @@ packages:
'@rolldown/pluginutils@1.0.0-beta.27':
resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
'@rollup/plugin-virtual@3.0.2':
resolution: {integrity: sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: '>=4.58.0'
peerDependenciesMeta:
rollup:
optional: true
'@rollup/rollup-android-arm-eabi@4.59.0':
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
cpu: [arm]
@ -2926,9 +2938,105 @@ packages:
'@speed-highlight/core@1.2.14':
resolution: {integrity: sha512-G4ewlBNhUtlLvrJTb88d2mdy2KRijzs4UhnlrOSRT4bmjh/IqNElZa3zkrZ+TC47TwtlDWzVLFADljF1Ijp5hA==}
'@swc/core-darwin-arm64@1.15.21':
resolution: {integrity: sha512-SA8SFg9dp0qKRH8goWsax6bptFE2EdmPf2YRAQW9WoHGf3XKM1bX0nd5UdwxmC5hXsBUZAYf7xSciCler6/oyA==}
engines: {node: '>=10'}
cpu: [arm64]
os: [darwin]
'@swc/core-darwin-x64@1.15.21':
resolution: {integrity: sha512-//fOVntgowz9+V90lVsNCtyyrtbHp3jWH6Rch7MXHXbcvbLmbCTmssl5DeedUWLLGiAAW1wksBdqdGYOTjaNLw==}
engines: {node: '>=10'}
cpu: [x64]
os: [darwin]
'@swc/core-linux-arm-gnueabihf@1.15.21':
resolution: {integrity: sha512-meNI4Sh6h9h8DvIfEc0l5URabYMSuNvyisLmG6vnoYAS43s8ON3NJR8sDHvdP7NJTrLe0q/x2XCn6yL/BeHcZg==}
engines: {node: '>=10'}
cpu: [arm]
os: [linux]
'@swc/core-linux-arm64-gnu@1.15.21':
resolution: {integrity: sha512-QrXlNQnHeXqU2EzLlnsPoWEh8/GtNJLvfMiPsDhk+ht6Xv8+vhvZ5YZ/BokNWSIZiWPKLAqR0M7T92YF5tmD3g==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@swc/core-linux-arm64-musl@1.15.21':
resolution: {integrity: sha512-8/yGCMO333ultDaMQivE5CjO6oXDPeeg1IV4sphojPkb0Pv0i6zvcRIkgp60xDB+UxLr6VgHgt+BBgqS959E9g==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@swc/core-linux-ppc64-gnu@1.15.21':
resolution: {integrity: sha512-ucW0HzPx0s1dgRvcvuLSPSA/2Kk/VYTv9st8qe1Kc22Gu0Q0rH9+6TcBTmMuNIp0Xs4BPr1uBttmbO1wEGI49Q==}
engines: {node: '>=10'}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@swc/core-linux-s390x-gnu@1.15.21':
resolution: {integrity: sha512-ulTnOGc5I7YRObE/9NreAhQg94QkiR5qNhhcUZ1iFAYjzg/JGAi1ch+s/Ixe61pMIr8bfVrF0NOaB0f8wjaAfA==}
engines: {node: '>=10'}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-gnu@1.15.21':
resolution: {integrity: sha512-D0RokxtM+cPvSqJIKR6uja4hbD+scI9ezo95mBhfSyLUs9wnPPl26sLp1ZPR/EXRdYm3F3S6RUtVi+8QXhT24Q==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@swc/core-linux-x64-musl@1.15.21':
resolution: {integrity: sha512-nER8u7VeRfmU6fMDzl1NQAbbB/G7O2avmvCOwIul1uGkZ2/acbPH+DCL9h5+0yd/coNcxMBTL6NGepIew+7C2w==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@swc/core-win32-arm64-msvc@1.15.21':
resolution: {integrity: sha512-+/AgNBnjYugUA8C0Do4YzymgvnGbztv7j8HKSQLvR/DQgZPoXQ2B3PqB2mTtGh/X5DhlJWiqnunN35JUgWcAeQ==}
engines: {node: '>=10'}
cpu: [arm64]
os: [win32]
'@swc/core-win32-ia32-msvc@1.15.21':
resolution: {integrity: sha512-IkSZj8PX/N4HcaFhMQtzmkV8YSnuNoJ0E6OvMwFiOfejPhiKXvl7CdDsn1f4/emYEIDO3fpgZW9DTaCRMDxaDA==}
engines: {node: '>=10'}
cpu: [ia32]
os: [win32]
'@swc/core-win32-x64-msvc@1.15.21':
resolution: {integrity: sha512-zUyWso7OOENB6e1N1hNuNn8vbvLsTdKQ5WKLgt/JcBNfJhKy/6jmBmqI3GXk/MyvQKd5SLvP7A0F36p7TeDqvw==}
engines: {node: '>=10'}
cpu: [x64]
os: [win32]
'@swc/core@1.15.21':
resolution: {integrity: sha512-fkk7NJcBscrR3/F8jiqlMptRHP650NxqDnspBMrRe5d8xOoCy9MLL5kOBLFXjFLfMo3KQQHhk+/jUULOMlR1uQ==}
engines: {node: '>=10'}
peerDependencies:
'@swc/helpers': '>=0.5.17'
peerDependenciesMeta:
'@swc/helpers':
optional: true
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
'@swc/helpers@0.5.15':
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
'@swc/types@0.1.26':
resolution: {integrity: sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==}
'@swc/wasm@1.15.21':
resolution: {integrity: sha512-AVomFdpE9LiB8Q0dNo1UqpqGut//Q7HJNyyCzyHsywHSUM/1pKfzywIjdha4WipgjyOPebYb93pTDBUn2iWvVA==}
'@tailwindcss/node@4.1.18':
resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==}
@ -6253,6 +6361,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
uuid@10.0.0:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
hasBin: true
validate-npm-package-name@7.0.2:
resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==}
engines: {node: ^20.17.0 || >=22.9.0}
@ -6281,6 +6393,11 @@ packages:
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
vite-plugin-top-level-await@1.6.0:
resolution: {integrity: sha512-bNhUreLamTIkoulCR9aDXbTbhLk6n1YE8NJUTTxl5RYskNRtzOR0ASzSjBVRtNdjIfngDXo11qOsybGLNsrdww==}
peerDependencies:
vite: '>=2.8'
vite@6.4.1:
resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
@ -9245,6 +9362,10 @@ snapshots:
'@rolldown/pluginutils@1.0.0-beta.27': {}
'@rollup/plugin-virtual@3.0.2(rollup@4.59.0)':
optionalDependencies:
rollup: 4.59.0
'@rollup/rollup-android-arm-eabi@4.59.0':
optional: true
@ -9669,10 +9790,72 @@ snapshots:
'@speed-highlight/core@1.2.14': {}
'@swc/core-darwin-arm64@1.15.21':
optional: true
'@swc/core-darwin-x64@1.15.21':
optional: true
'@swc/core-linux-arm-gnueabihf@1.15.21':
optional: true
'@swc/core-linux-arm64-gnu@1.15.21':
optional: true
'@swc/core-linux-arm64-musl@1.15.21':
optional: true
'@swc/core-linux-ppc64-gnu@1.15.21':
optional: true
'@swc/core-linux-s390x-gnu@1.15.21':
optional: true
'@swc/core-linux-x64-gnu@1.15.21':
optional: true
'@swc/core-linux-x64-musl@1.15.21':
optional: true
'@swc/core-win32-arm64-msvc@1.15.21':
optional: true
'@swc/core-win32-ia32-msvc@1.15.21':
optional: true
'@swc/core-win32-x64-msvc@1.15.21':
optional: true
'@swc/core@1.15.21':
dependencies:
'@swc/counter': 0.1.3
'@swc/types': 0.1.26
optionalDependencies:
'@swc/core-darwin-arm64': 1.15.21
'@swc/core-darwin-x64': 1.15.21
'@swc/core-linux-arm-gnueabihf': 1.15.21
'@swc/core-linux-arm64-gnu': 1.15.21
'@swc/core-linux-arm64-musl': 1.15.21
'@swc/core-linux-ppc64-gnu': 1.15.21
'@swc/core-linux-s390x-gnu': 1.15.21
'@swc/core-linux-x64-gnu': 1.15.21
'@swc/core-linux-x64-musl': 1.15.21
'@swc/core-win32-arm64-msvc': 1.15.21
'@swc/core-win32-ia32-msvc': 1.15.21
'@swc/core-win32-x64-msvc': 1.15.21
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.15':
dependencies:
tslib: 2.8.1
'@swc/types@0.1.26':
dependencies:
'@swc/counter': 0.1.3
'@swc/wasm@1.15.21': {}
'@tailwindcss/node@4.1.18':
dependencies:
'@jridgewell/remapping': 2.3.5
@ -13697,6 +13880,8 @@ snapshots:
util-deprecate@1.0.2: {}
uuid@10.0.0: {}
validate-npm-package-name@7.0.2: {}
vary@1.1.2: {}
@ -13746,6 +13931,17 @@ snapshots:
- tsx
- yaml
vite-plugin-top-level-await@1.6.0(rollup@4.59.0)(vite@6.4.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
'@rollup/plugin-virtual': 3.0.2(rollup@4.59.0)
'@swc/core': 1.15.21
'@swc/wasm': 1.15.21
uuid: 10.0.0
vite: 6.4.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.2)
transitivePeerDependencies:
- '@swc/helpers'
- rollup
vite@6.4.1(@types/node@25.0.10)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.16.9)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
esbuild: 0.25.12