* fix(auto-update): commit app to quit before quitAndInstall on macOS (#1215)
macOS in-place auto-update downloaded and unpacked the new version but
never installed it: the app appeared to close, ShipIt never ran, and no
restart happened. A full uninstall + reinstall did not help; only a
manual DMG replace worked.
Root cause is a code-level coordination bug, not the release pipeline.
The published mac zips are correctly Developer-ID signed, notarized, and
stapled (Team H7WS5L2ML4, consistent across 1.1.17 and 1.1.20), and
latest-mac.yml is well-formed — so Squirrel.Mac signature validation
passes. The failure is that quitAndInstall() drives app.quit() while two
normal-quit behaviors keep the process alive:
1. the main-window close handler hides to tray when close-to-tray is
enabled (it only closes when isQuitting is true), and
2. the before-quit dirty-editor guard preventDefault()s the quit for a
5s renderer round-trip.
Either keeps the parent process running, so Squirrel.Mac's ShipIt helper
— which waits on the parent PID to die before swapping the bundle —
lands in launchd "pending spawn / on-demand-only" limbo and the service
is removed without installing. This matches the reporter's diagnosis
exactly ("ShipIt 没有真正启动安装器", launchd on-demand-only).
Fix: before quitAndInstall fires app.quit(), mark the app as quitting
for an update via windowManager.setQuittingForUpdate(true). That sets
isQuitting (bypassing close-to-tray) and the before-quit handler now
returns early when isQuittingForUpdate() is true (skipping the
dirty-editor round-trip), so the process exits cleanly and ShipIt can
run. The same fix also covers the latent Windows NSIS case where
close-to-tray would block an in-place update.
If the install never actually quits the app (quitAndInstall throws, or
returns without app.quit() on a Squirrel follow-up error / stale
download), the quitting-for-update flags are rolled back — synchronously
on throw, and via a short unref'd watchdog otherwise — so the app does
not get stuck permanently bypassing close-to-tray and the quit guard.
Tests: unit tests for the new windowManager flags and for the install
handler — ordering (setQuittingForUpdate before quitAndInstall), tray
cleanup still runs, no-op when the updater fails to load, rollback on
synchronous throw, and watchdog rollback when the app never quits.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(auto-update): keep dirty-editor guard during update install
setQuittingForUpdate only bypasses close-to-tray (so the window actually
closes and Squirrel.Mac's ShipIt can swap the bundle); it must NOT skip the
unsaved-work guard, or clicking "Restart Now" with a dirty SFTP editor would
silently lose edits. If the user cancels to save, the quit aborts and
autoUpdateBridge's watchdog clears the quitting-for-update flags.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(auto-update): clear update-quit state when the quit is cancelled
When the user clicks "Restart Now" with a dirty editor open, the before-quit
guard cancels the quit (settle "stay"). The update path had already called
setQuittingForUpdate(true) (which flips isQuitting=true to bypass close-to-tray
for the install), so without clearing it the app stays in a quitting state —
close-to-tray and other !isQuitting-gated behavior bypassed — until the 10s
watchdog fires. Clear it immediately on the cancelled-quit path (#1215 review).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(auto-update): check for unsaved editors before quitAndInstall (#1215)
The previous fix bypassed close-to-tray so the app process actually exits and
Squirrel.Mac's ShipIt can swap the bundle, while keeping the before-quit
dirty-editor guard as the unsaved-work safety net. But on macOS that net has a
hole: quitAndInstall() closes the window FIRST and only then fires before-quit.
Once setQuittingForUpdate(true) lets the main window truly close (instead of
hiding to tray), the before-quit guard can run after the window is already gone
— isReachableByUser is false, so it commits the quit and silently drops unsaved
SFTP edits.
Fix: move the dirty-editor check to the moment the user clicks "Restart Now",
in the install handler, BEFORE setQuittingForUpdate / quitAndInstall — while the
window and renderer are still alive:
- dirty -> abort the install (don't set the quitting flags, don't
quitAndInstall) and broadcast netcatty:update:needs-save so the
renderer prompts the user to save and retry.
- clean -> proceed with the existing flow (commit-to-quit, tray cleanup,
quitAndInstall, watchdog).
- no reachable main window / crashed renderer -> install directly (no user to
ask), matching the before-quit fail-open path.
The before-quit dirty guard is kept as defense-in-depth: if the window is still
reachable it re-checks (clean, since we just verified), and if it's already gone
it lets the quit through — which is now safe because the install handler already
confirmed there were no unsaved editors.
The request/reply/timeout round-trip is extracted into a shared helper,
electron/bridges/dirtyEditorGuard.cjs (queryDirtyEditors), so the install
handler and main.cjs's before-quit guard use one implementation. main.cjs's
before-quit is refactored onto it (behavior preserved: sender-filtered reply,
fail-open timeout, and the setQuittingForUpdate(false) rollback when the user
cancels to save).
The needs-save notice is BROADCAST to every window, not just the queried main
window: "Restart to Update" can be clicked from the Settings window, which would
otherwise see the click do nothing. preload exposes onUpdateNeedsSave; the
subscription lives in useUpdateCheck (state layer), and both consumers — App.tsx
(main window) and SettingsPage (settings window) — pass an onNeedsSave callback
that shows an actionable toast ("save your editors, then click Restart Now
again") in en / zh-CN / ru.
Also lengthen the quitting-for-update rollback watchdog from 10s to 60s. On
macOS quitAndInstall() can return while Squirrel is still pulling the downloaded
ZIP from the local update server before it closes the windows; on a large/slow
update that can exceed 10s. Clearing isQuitting that early would let the eventual
native quit hit a non-quitting close-to-tray handler and strand the install
again — the exact #1215 failure. The longer window only fires when the app is
realistically stuck, at the cost of close-to-tray staying bypassed a little
longer in the rare genuine-failure case.
Tests: queryDirtyEditors (result / no-dirty / timeout / wrong-sender / dead or
crashed webContents / send-throws / no-ipcMain paths); install handler pre-check
(dirty -> no quitAndInstall + needs-save broadcast to all windows; clean ->
quitAndInstall runs; no main window -> installs without asking). Existing
install-handler tests updated for the now-async handler.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix: prevent double-click update crash and improve update UX (#522)
- Add state guards to prevent checkForUpdates during active download
- Disable "Check for Updates" button during checking/downloading/ready
- Make version badge trigger in-app download instead of opening GitHub
- Change error toast action from "Open Releases" to "View in Settings"
- Add "Download Now" button in system settings as primary action
- Keep GitHub release link as secondary fallback in settings
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: reset download state when downloadUpdate() rejects
Clears _isDownloading and broadcasts error status on catch so the
update UI does not get stuck after a failed download attempt.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: only show Download Now after a completed update check
Prevents downloadUpdate() from being called with stale cached state
before electron-updater has run checkForUpdates(), avoiding a
"Please check update first" error.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use correct broadcast function and prime updater before download
- Replace undefined broadcastUpdateStatus with broadcastToAllWindows
- Call checkForUpdate before downloadUpdate to ensure electron-updater
has populated update metadata
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use correct error payload field and guard unsupported platforms
- Use { error: ... } instead of { message: ... } in download error
broadcast to match renderer expectations
- Bail out of startDownload when checkForUpdate returns unsupported
or throws, instead of entering a failing download path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: guard startDownload against in-flight and no-update check results
Bail out when checkForUpdate returns checking, not-available, or
unsupported states to prevent calling downloadUpdate prematurely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove duplicate error broadcast and fallback to releases on unsupported
- Remove redundant broadcastToAllWindows in download catch (global
error listener already handles it)
- Open release page instead of silently returning when platform
does not support auto-update
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: check supported before available to ensure release page fallback
Unsupported platforms return { available: false, supported: false },
so the supported check must come first to open the release page
instead of silently returning.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: skip download when update is already ready or downloading
Guard against re-downloading when checkForUpdate returns ready or
downloading sentinel, preventing overwrite of valid install state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fallback to release page when electron-updater reports no update
When GitHub API found an update but electron-updater does not,
open the release page instead of silently doing nothing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove 65 debug console.log statements from production code
Remove bracketed debug traces ([SFTP navigateTo], [SFTPBackend],
[ManagedSourceSync], [AutoSync], [CloudSync], [Settings], etc.)
across 16 files. These were development logging that shipped to
production, creating noise in the console.
Also clean up dead variables left behind after log removal
(hotkeyDebug, results, verification reads).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove 43 unused exports and dead type definitions
Remove export keywords from symbols that are never imported outside
their defining file. Symbols still used internally keep their
definitions; symbols not used at all are removed entirely.
Removed entirely: TerminalLine, SessionLogsSettings, KDFParams,
SyncManagerConfig, GoogleTokenResponse, OneDriveTokenResponse,
getSyncStatusColor, resolveHostTerminalAppearance,
TerminalAppearanceDefaults.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add auto-update toggle setting (closes#346)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: re-check auto-update toggle when startup timer fires
Address review feedback: the startup check effect now re-reads the
toggle from localStorage when the delayed timer fires, so toggling
off after launch cancels the pending check. Also avoids setting
hasCheckedOnStartupRef when disabled, allowing re-enable to trigger
a check without restart.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address review feedback on auto-update toggle
P1: When autoDownload=false, onUpdateAvailable no longer transitions
to 'downloading' status. Instead keeps autoDownloadStatus idle so
the manual download link surfaces correctly.
P2: Added reactive autoUpdateEnabled state (synced via storage event)
as a dependency to the startup check effect. Re-enabling the toggle
mid-session now re-triggers the deferred startup check.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address P1/P2 review feedback on auto-update toggle
P1: Main process update-available handler now checks updater.autoDownload
before setting _lastStatus to 'downloading'. When autoDownload=false,
status stays 'idle' so late-opened windows don't hydrate to a stuck
0% download state.
P2: useUpdateCheck now accepts autoUpdateEnabled as a prop from the
caller instead of relying solely on storage events (which don't fire
in the same window). SettingsPage passes settings.autoUpdateEnabled
directly, so toggling in the current window takes effect immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve update-available info for late-opening windows
When autoDownload is off, use status 'available' (instead of 'idle')
in the main process snapshot so late-opening windows can hydrate
version info. The renderer maps 'available' to hasUpdate=true while
keeping autoDownloadStatus='idle' for the manual download path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: re-schedule auto-check on re-enable and guard startup timer
- IPC handler now calls startAutoCheck(2000) when re-enabling so the
user gets automatic checks without restarting the app.
- startAutoCheck timer checks updater.autoDownload at fire time, so
if the renderer disables auto-update via IPC before the 5s startup
timer fires, the check is skipped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: deduplicate auto-check scheduling and clear error on fallback success
P1: startAutoCheck now cancels any existing timer before scheduling
a new one, preventing duplicate concurrent checks from multiple
windows or re-enable toggles.
P2: checkNow fallback now clears manualCheckStatus='error' when
electron-updater successfully finds an update (res.available=true),
so the UI shows 'available' instead of a stale error state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: only reschedule on actual re-enable and hydrate cache before toggle check
P2: Track previous autoDownload state in IPC handler so startAutoCheck
is only called on actual false→true transitions, not on every window
mount that syncs the current value.
P3: Move cache hydration (STORAGE_KEY_UPDATE_LATEST_RELEASE) before
the auto-update toggle check so cached update info is always visible
even when automatic updates are disabled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: persist auto-update preference in main process across restarts
Read/write auto-update preference to a JSON file in userData so the
main process honors it on next launch without waiting for renderer IPC.
getAutoUpdater() now initializes autoDownload from the persisted value.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: suppress cached update toast when disabled and update IPC types
P2: Cache hydration now gates hasUpdate on autoUpdateEnabled so the
App.tsx toast doesn't fire when automatic updates are disabled.
P3: Updated global.d.ts to include 'available' in getUpdateStatus
status union and 'checking' in checkForUpdate return type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: preserve dismissed releases, show cached updates in Settings, guard concurrent checks
P2a: Updater fallback now checks STORAGE_KEY_UPDATE_DISMISSED_VERSION
before re-surfacing a release found by electron-updater.
P2b: Cache hydration always sets hasUpdate truthfully so Settings
shows the available update. Toast suppression for disabled auto-update
moved to App.tsx (reads localStorage directly).
P3: Re-enable IPC handler checks _isChecking before scheduling
startAutoCheck to prevent concurrent electron-updater calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: use localStorageAdapter for lint compliance, skip IPC on initial mount
P1: Replace direct localStorage access with localStorageAdapter in
App.tsx toast guard to fix no-restricted-globals lint error.
P2: Skip setAutoUpdate IPC on initial mount to prevent overwriting
the main-process preference file when renderer localStorage has been
cleared (where the default would be true).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: hydrate auto-update state from main-process preference on mount
Add getAutoUpdate IPC handler so the renderer can query the persisted
preference from auto-update-pref.json. On mount, useSettingsState
reconciles localStorage with the main-process truth, preventing the
toggle from showing 'enabled' when the user had previously disabled
it and localStorage was cleared.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Handle { checking: true } response from bridge.checkForUpdate()
separately instead of treating it as "no update" — an in-flight
check will resolve via IPC events
- Restore dismissUpdate() in "View in Settings" toast onClick so
unsupported-platform users can suppress the notification; on
supported platforms the Settings window picks up download state
via IPC events independently
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Set dismissedAutoDownloadRef when hydration skips a dismissed version
so subsequent IPC events (progress/downloaded) are also suppressed
- When GitHub API fails but electron-updater fallback finds no update,
clear manualCheckStatus from 'error' to 'up-to-date' instead of
leaving Settings stuck in error state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When checkNow's GitHub API call fails (blocked/rate-limited), still
trigger electron-updater's checkForUpdate as a fallback. This restores
the update path for environments where api.github.com is unreachable
but the updater feed is still accessible.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove STORAGE_KEY_UPDATE_LAST_CHECK write from onUpdateNotAvailable
handler — it would prevent the GitHub API fallback from running on app
restart, hiding releases that exist on GitHub but aren't yet in the
electron-updater feed. Let performCheck write the timestamp instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Don't cancel startup GitHub API fallback when electron-updater says
not-available — the GitHub release may exist before updater feed
assets are published, and the fallback provides manual download link
- Rescheduled fallback now re-queries getUpdateStatus to avoid duplicate
notifications on very slow networks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace autoDownloadStatus==='idle' guard in progress/downloaded/error
callbacks with a dedicated dismissedAutoDownloadRef to distinguish
"dismissed version" from "not hydrated yet" in late-opening windows
- Don't clear hasUpdate on update-not-available — GitHub release may
exist even when electron-updater feed says no compatible update,
preserving the manual download fallback path
- Reset dismissedAutoDownloadRef on manual retry via checkNow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- When the startup fallback sees the main process check still in flight,
reschedule after 5s instead of permanently skipping — handles the case
where the auto-check fails silently (check-phase errors not broadcast)
- onUpdateNotAvailable: clear hasUpdate and manualCheckStatus to remove
stale "update available" state from earlier GitHub API checks, since
the updater feed is authoritative on supported platforms
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- checkNow: check dismissed version before marking status as 'available'
to prevent re-downloading a release the user explicitly skipped
- startAutoCheck: verify updater exists before setting _isChecking flag
to avoid permanent stuck state when electron-updater fails to load
- Clear _isChecking in all catch paths to prevent stuck state
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add _isChecking flag in autoUpdateBridge to track whether
checkForUpdates is in flight; return sentinel when manual check
arrives during an active auto-check instead of starting a concurrent
call that electron-updater would reject
- Include isChecking in getUpdateStatus snapshot so the renderer can
query it before starting the GitHub API fallback
- Startup fallback now checks getUpdateStatus().isChecking to skip
when electron-updater is still checking on slow networks
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Store startAutoCheck timer ID so it can be cancelled; cancel it when
the renderer triggers a manual checkForUpdate to avoid concurrent
electron-updater calls that produce false errors
- Record lastCheckedAt and STORAGE_KEY_UPDATE_LAST_CHECK when
update-not-available fires so the throttle works on the common
no-update path and "Last checked" UI shows correctly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Broadcast 'update-not-available' from electron-updater to renderer so
the startup GitHub API check is cancelled when no update exists
- Cancel pending startup timeout in checkNow() to prevent racing with
electron-updater's startAutoCheck (concurrent calls cause false errors)
- Add onUpdateNotAvailable bridge event (preload + global.d.ts types)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- getUpdateStatus hydration: skip restoring download state for dismissed
versions so late-opening windows don't show dismissed release UI
- performCheck: only advance lastCheck timestamp and cache release data
on successful checks — failed checks no longer suppress re-checks for
an hour while leaving stale cached release visible
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- onUpdateAvailable: skip autoDownloadStatus→'downloading' transition when
version is dismissed, preventing download progress/ready toasts
- onUpdateAvailable: cancel pending startup GitHub API check timeout to
eliminate race where electron-updater is still checking at 8s
- onUpdateDownloadProgress/Downloaded/Error: suppress state transitions
when autoDownloadStatus is 'idle' (dismissed version background download)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Return idle instead of up-to-date for dev/invalid builds in
checkNow to avoid false positive status
- Replace stale cached release in getUpdateStatus hydration when
the snapshot reports a different version
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Use semantic version comparison for cached release hydration to
avoid false positives when running a newer build than latest release
- Restore dismissUpdate() in startup toast so unsupported-platform
users can silence repeated notifications
- Remove dismissed-version check from ready-to-install toast since
dismissing availability should not block the install prompt
- Show manual download link in Settings on check errors too
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Persist latestRelease to localStorage so windows opened after the
initial check can hydrate release info without re-fetching
- Remove dismissUpdate() from "View in Settings" toast click — the
dismissed version key was preventing the later install-ready toast
- Hydrate cached release data when startup check is throttled so
Settings windows show the already-found update
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Don't treat unsupported auto-update platforms as download errors;
keep autoDownloadStatus at idle so manual download link shows
- Remove auto-check on SettingsApplicationTab mount to avoid
implicitly triggering downloads when opening Settings
- Update Application tab badge to reflect download/ready state
instead of always showing "Download Now"
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace stale latestRelease when electron-updater reports a different
version than the cached GitHub API result
- Surface checkForUpdate() failures by setting autoDownloadStatus to
error instead of silently dropping them
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove hasUpdate gate from ready-to-install toast so dismissing
availability notification doesn't prevent restart prompt
- Only open releases page on platforms without auto-download
- Increase startup check delay to 8s to let electron-updater fire first
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Skip renderer's GitHub API startup check if electron-updater's
auto-download has already started, preventing duplicate toast
notifications for the same release
- Set hasUpdate in onUpdateAvailable IPC handler, checking dismissed
version so that dismissed releases don't trigger the persistent
"restart now" toast after auto-download completes
- Guard "ready to install" toast with hasUpdate check
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix autoDownloadStatusRef stale read during checkNow retry: eagerly
sync the ref when resetting error->idle so checkForUpdate() fires
- Refactor SettingsApplicationTab to accept update props instead of
creating its own useUpdateCheck instance, preventing duplicate checks
and inconsistent state between Application and System tabs
- Show startup-detected updates (hasUpdate) in System tab, not only
manualCheckStatus=available, so Linux/unsupported platforms see the
update and manual download button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add getUpdateStatus IPC handler so windows opened after download started
can immediately reflect the current state instead of showing stale 'idle'
- Track _lastStatus in main process across all updater events
- Hydrate autoDownloadStatus on useUpdateCheck mount via getUpdateStatus()
- Fix toast race: use ref to track previous autoDownloadStatus so ready/error
toasts only fire on actual status transitions, not when unrelated callback
references change
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
performCheck returns a non-null UpdateCheckResult with error populated
on GitHub API/network failures. Extend the status derivation to treat
result.error as an error state instead of falling through to up-to-date.
P1: change checkNow return type from Promise<null> to Promise<UpdateCheckResult | null>
and return actual result so callers can read hasUpdate/latestRelease.
P2: reset autoDownloadStatus from 'error' to 'idle' when user triggers manual
check, enabling a retry path; also show Check for Updates button in error state.
- Fix 1: when manual check finds update and electron-updater hasn't started
downloading yet (autoDownloadStatus=idle), fire-and-forget checkForUpdate()
to kick off the auto-download pipeline without blocking the UI
- Fix 2: manualCheckStatus='up-to-date' now auto-resets to 'idle' after 5s
so the badge doesn't stay stale until the next check; any new check cancels
the pending timer first
- Fix 3: SettingsSystemTab shows "last checked: X min ago" below the update
section using lastCheckedAt from updateState; new i18n keys added for both
zh-CN and en locales (lastCheckedJustNow, lastCheckedMinutesAgo,
lastCheckedHoursAgo, lastCheckedPrefix)
Internal: add autoDownloadStatusRef and manualCheckResetTimeoutRef to
useUpdateCheck for reliable cross-closure state access and timer lifecycle.
checkNow was calling bridge.checkForUpdate() which invokes updater.checkForUpdates()
via IPC. startAutoCheck() in the main process already calls checkForUpdates() on a
5s timer, and if that network request is still pending, the concurrent IPC call from
checkNow hangs indefinitely, causing the UI to be stuck in "checking" state forever.
Per the original design spec, checkNow should use performCheck() (GitHub API) directly.
This is completely independent of electron-updater's internal state machine, so it
never conflicts with the background startAutoCheck(). performCheck handles isCheckingRef,
isChecking, hasUpdate, and latestRelease; checkNow only manages manualCheckStatus.
- Critical: In Linux fallback path, temporarily reset isCheckingRef before calling
performCheck so its own guard can run (was silently returning null due to double-set)
- Critical: Replace updateState.currentVersion closure in checkNow with currentVersionRef
to avoid reading stale '' version on early user click; remove from useCallback deps
- Important: Add explicit !result guard when bridge is unavailable, returning 'error'
status instead of silently falling through to 'up-to-date'
Implements automatic and manual update checking, displaying toast notifications when new versions are available or when the app is up to date. Enhances the toast utility to support clickable actions, custom labels, and improved accessibility. Updates UI to surface update status more prominently and localizes new update-related messages.
Improves user awareness of available updates and provides a smoother update experience.