feat(desktop/windows): show update-in-progress feedback before the desktop exits (#50419) (#50448)

Follow-up to #50238/#50381. The restart-loop is now SAFE (marker + launch
gate), but the trigger that lured users into relaunching mid-update remained:
on the in-app update hand-off the desktop window vanished almost immediately
(app.quit() 600ms after spawning the detached updater), before the updater's
own window appeared — a blank-screen gap that looks like a crash.

- Linger on the update overlay for UPDATE_HANDOFF_DWELL_MS (2.5s, was 600ms)
  before quitting, on BOTH hand-off paths (in-app update + Windows bootstrap
  recovery), so the message lands and bridges to the updater window.
- Strengthen the restart-stage copy and the overlay's applyingBody/applyingClose
  to explicitly tell the user the window will reopen automatically and NOT to
  reopen Hermes themselves while it updates. All four locales (en/ja/zh/zh-hant)
  updated in parity.

Pure UX; does not touch the #50381 marker/gate mutual-exclusion safety net.
This commit is contained in:
Teknium 2026-06-21 15:34:52 -07:00 committed by GitHub
parent 624580e836
commit 745c4db235
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 32 additions and 13 deletions

View file

@ -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. Dont 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
}

View file

@ -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 its done.',
applyingBody:
'The Hermes updater takes over in its own window and reopens Hermes automatically when its done. Please dont reopen Hermes yourself while its updating.',
applyingBodyBackend:
'The remote backend is applying the update and will restart. Hermes reconnects automatically when its 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 didnt finish',
errorBody: 'No worries — nothing was lost. You can try again now.',
notNow: 'Not now',

View file

@ -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: '今は後で',

View file

@ -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: '暫不',

View file

@ -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: '暂不',