diff --git a/apps/desktop/electron/main.cjs b/apps/desktop/electron/main.cjs index b25a5925140..d263adf4766 100644 --- a/apps/desktop/electron/main.cjs +++ b/apps/desktop/electron/main.cjs @@ -1129,6 +1129,14 @@ function directoryExists(filePath) { // marker's own age ceiling; covers a stuck-but-alive updater). const UPDATE_WAIT_TIMEOUT_MS = 20 * 60 * 1000 const UPDATE_WAIT_POLL_MS = 1000 +// How long the desktop lingers on the "updating, don't reopen" overlay after +// spawning the detached updater, before it quits to release the venv shim. The +// old 600ms was long enough to register the child process but far too short for +// the user to READ the overlay — the window just vanished, looked like a crash, +// and the user relaunched mid-update (the #50238 restart-loop trigger). A +// couple of seconds lets the message land and bridges the gap until the +// updater's own progress window appears. (#50419) +const UPDATE_HANDOFF_DWELL_MS = 2500 // Block until no live update is in progress (or we hit the wait timeout). // Emits a boot-progress phase so the renderer shows "Update in progress…" @@ -1867,7 +1875,11 @@ async function applyUpdates(opts = {}) { return { ok: true, manual: true, command, hermesRoot: updateRoot } } - emitUpdateProgress({ stage: 'restart', message: 'Handing off to the Hermes updater…', percent: 100 }) + emitUpdateProgress({ + stage: 'restart', + message: 'Updating Hermes — this window will close and the updater will open. Don’t reopen Hermes yourself; it restarts automatically when the update finishes.', + percent: 100 + }) repairMacUpdaterHelper(updater) const updateRoot = resolveUpdateRoot() @@ -1903,11 +1915,14 @@ async function applyUpdates(opts = {}) { rememberLog(`[updates] launched updater: ${updater} ${updaterArgs.join(' ')}; exiting desktop to release venv shim`) - // Give the OS a beat to register the new process, then quit. The updater - // rebuilds and relaunches us when it's done. + // Linger on the "updating — don't reopen" overlay long enough for the user + // to actually read it (and to bridge the gap until the updater's own window + // appears), THEN quit to release the venv shim. The updater rebuilds and + // relaunches us when it's done. (#50419 — a 600ms quit looked like a crash + // and lured users into the #50238 relaunch loop.) setTimeout(() => { app.quit() - }, 600) + }, UPDATE_HANDOFF_DWELL_MS) return { ok: true, handedOff: true, updater } } finally { @@ -1946,9 +1961,12 @@ async function handOffWindowsBootstrapRecovery(reason) { child.unref() rememberLog(`[bootstrap] handed off ${reason} recovery to updater: ${updater} ${updaterArgs.join(' ')}; exiting desktop to release app.asar`) + // Same dwell as the in-app update hand-off (#50419): give the updater's + // window time to appear before we vanish, so the recovery doesn't look like + // a crash and provoke a mid-recovery relaunch. setTimeout(() => { app.quit() - }, 600) + }, UPDATE_HANDOFF_DWELL_MS) return true } diff --git a/apps/desktop/src/i18n/en.ts b/apps/desktop/src/i18n/en.ts index ea2a6f745bb..6dcbd7d53d8 100644 --- a/apps/desktop/src/i18n/en.ts +++ b/apps/desktop/src/i18n/en.ts @@ -1382,10 +1382,11 @@ export const en: Translations = { copy: 'Copy', copied: 'Copied', done: 'Done', - applyingBody: 'The Hermes updater will take over in its own window and reopen Hermes when it’s done.', + applyingBody: + 'The Hermes updater takes over in its own window and reopens Hermes automatically when it’s done. Please don’t reopen Hermes yourself while it’s updating.', applyingBodyBackend: 'The remote backend is applying the update and will restart. Hermes reconnects automatically when it’s back.', - applyingClose: 'Hermes will close to apply the update.', + applyingClose: 'This window will close while the update runs, then Hermes reopens on its own.', errorTitle: 'Update didn’t finish', errorBody: 'No worries — nothing was lost. You can try again now.', notNow: 'Not now', diff --git a/apps/desktop/src/i18n/ja.ts b/apps/desktop/src/i18n/ja.ts index b02f90486d9..265c7833aa9 100644 --- a/apps/desktop/src/i18n/ja.ts +++ b/apps/desktop/src/i18n/ja.ts @@ -1512,9 +1512,9 @@ export const ja = defineLocale({ copy: 'コピー', copied: 'コピーしました', done: '完了', - applyingBody: 'Hermes アップデーターが独自のウィンドウで引き継ぎ、完了後に Hermes を再度開きます。', + applyingBody: 'Hermes アップデーターが独自のウィンドウで引き継ぎ、完了後に自動的に Hermes を再度開きます。更新中はご自分で Hermes を開き直さないでください。', applyingBodyBackend: 'リモートバックエンドが更新を適用して再起動します。復帰すると Hermes が自動的に再接続します。', - applyingClose: 'Hermes は更新を適用するために閉じます。', + applyingClose: 'このウィンドウは更新中に閉じ、その後 Hermes が自動的に再度開きます。', errorTitle: '更新が完了しませんでした', errorBody: 'ご安心ください。何も失われていません。今すぐ再試行できます。', notNow: '今は後で', diff --git a/apps/desktop/src/i18n/zh-hant.ts b/apps/desktop/src/i18n/zh-hant.ts index f739bfa8e5f..a4adf5cf01a 100644 --- a/apps/desktop/src/i18n/zh-hant.ts +++ b/apps/desktop/src/i18n/zh-hant.ts @@ -1463,9 +1463,9 @@ export const zhHant = defineLocale({ copy: '複製', copied: '已複製', done: '完成', - applyingBody: 'Hermes 更新程式會在自己的視窗中接管,並在完成後重新開啟 Hermes。', + applyingBody: 'Hermes 更新程式會在自己的視窗中接管,並在完成後自動重新開啟 Hermes。更新期間請勿自行重新開啟 Hermes。', applyingBodyBackend: '遠端後端正在套用更新並將重新啟動。恢復後 Hermes 會自動重新連線。', - applyingClose: 'Hermes 將關閉以套用更新。', + applyingClose: '此視窗會在更新期間關閉,隨後 Hermes 會自動重新開啟。', errorTitle: '更新未完成', errorBody: '沒有資料遺失。您可以現在重試。', notNow: '暫不', diff --git a/apps/desktop/src/i18n/zh.ts b/apps/desktop/src/i18n/zh.ts index 5cf9e23d982..cf58eb97715 100644 --- a/apps/desktop/src/i18n/zh.ts +++ b/apps/desktop/src/i18n/zh.ts @@ -1568,9 +1568,9 @@ export const zh: Translations = { copy: '复制', copied: '已复制', done: '完成', - applyingBody: 'Hermes 更新器会在自己的窗口中接管,并在完成后重新打开 Hermes。', + applyingBody: 'Hermes 更新器会在自己的窗口中接管,并在完成后自动重新打开 Hermes。更新期间请不要自行重新打开 Hermes。', applyingBodyBackend: '远程后端正在应用更新并将重启。恢复后 Hermes 会自动重新连接。', - applyingClose: 'Hermes 将关闭以应用更新。', + applyingClose: '此窗口会在更新期间关闭,随后 Hermes 会自动重新打开。', errorTitle: '更新未完成', errorBody: '没有数据丢失。你可以现在重试。', notNow: '暂不',