How Munder Difflin's Auto-Update Mechanism Works and What Happens on Restart
Munder Difflin's auto-update mechanism checks for new versions every 6 hours using Electron's native updater, falls back to GitHub API polling when native updates fail, and only installs updates after the user explicitly triggers a restart via autoUpdater.quitAndInstall().
The auto-update mechanism in Munder Difflin is implemented entirely within the Electron main process, coordinating with the renderer UI through a focused IPC bridge. According to the chaitanyagiri/munder-difflin source code, the system prioritizes silent, automatic updates while maintaining full transparency through status events and explicit user consent for installation.
Initialization and IPC Registration
The update lifecycle begins in src/main/updater.ts with initAutoUpdater(), which registers five IPC handlers:
update:checkNow— triggers an immediate version checkupdate:download— starts downloading an available updateupdate:restartAndInstall— applies the staged updateupdate:current— returns the running versionupdate:simulateandupdate:openRelease— debugging and fallback utilities
This function also initializes a periodic timer that drives automatic checks. The handlers are established before the app fully boots, ensuring the renderer can query update state immediately.
Periodic Update Checks
The runCheck() function executes automatically under two conditions: after a 30-second boot delay and every 6 hours thereafter. However, it only proceeds when:
- The
autoUpdateuser preference is enabled (stored insrc/renderer/src/store/config.ts) - The app is running from a packaged build (skipped in development)
// Trigger a manual check from the renderer
await window.ipcRenderer.invoke('update:checkNow');
When invoked, runCheck() first emits a checking status, then attempts the native update path through loadAutoUpdater().
Native Update Path via electron-updater
The loadAutoUpdater() function (lines 96-105 in updater.ts) safely resolves the autoUpdater export from electron-updater, handling CJS/ESM interoperability edge cases. Once loaded, the native flow proceeds through three event-driven stages:
| Event | Emitted Status | Trigger |
|---|---|---|
checking-for-update |
checking |
Update check initiated |
update-available |
available |
Newer version found on server |
download-progress |
downloading |
Download in progress (includes percent, bytesPerSecond) |
update-downloaded |
downloaded |
Update ready to install |
The event handlers (lines 310-322) translate native updater events into structured UpdateStatus objects that the renderer consumes.
// Start downloading an announced update
await window.ipcRenderer.invoke('update:download');
Fallback GitHub API Path
When the native updater fails to load or throws—common in unsigned builds or sandboxed environments—the error is logged and fallbackCheck() executes. This function polls https://api.github.com/repos/chaitanyagiri/munder-difflin/releases/latest, compares the GitHub tag with the running version, and emits an available-manual status if newer.
Unlike the native path, the fallback only offers a link to the release page rather than automatic download:
// Open release page when self-update unavailable
await window.ipcRenderer.invoke('update:openRelease', 'https://github.com/chaitanyagiri/munder-difflin/releases/latest');
State Reduction and Version Guards
All status emissions pass through reduceStatus() in src/shared/updateState.ts (lines 92-99). This function implements a critical safety guarantee: once an update reaches a "staged" state (downloaded or available-manual), subsequent checking or not-available events for the same version cannot overwrite it.
However, if a newer version appears during an active staged state, that newer release takes precedence. This prevents race conditions where a slow download or delayed user action could leave the UI showing stale information.
UI Presentation and User Actions
The renderer receives status updates via the 'update:status' IPC channel. Two descriptor functions map internal state to user-facing interfaces:
describeUpdate()— used byUpdateBadge.tsxfor the toolbar badge; returnslabel,tone,tooltip, and clickactiondescribeUpdateSettings()— used on the Settings page for richer descriptions and primary button states
The badge adapts dynamically: it shows nothing when no update exists, pulses during download, and persists with action buttons once downloaded.
What Happens During Restart
The restart-to-install flow is the final and most consequential step. When the user clicks "Restart to update" or the badge's install action, the renderer invokes:
await window.ipcRenderer.invoke('update:restartAndInstall');
The main process handler (lines 40-46 in updater.ts) executes autoUpdater.quitAndInstall(). Critically, autoInstallOnAppQuit is set to false during initialization, ensuring the update only applies after explicit user confirmation rather than on any routine quit.
The sequence is atomic:
quitAndInstall()terminates all renderer and main process windows- The downloaded installer executes immediately
- The new version launches with preserved user data and settings
No manual re-download occurs; the staged update package is validated by electron-updater before installation begins.
Comprehensive Event Logging
Every significant event appends to updater.log in the user-data directory through logLine() (lines 60-68). Logged events include:
- Native check success/failure
- Download progress milestones
- Fallback poll results
quitAndInstallinvocations
This provides post-mortem visibility without blocking the update flow or exposing sensitive data.
Summary
- Entry point:
initAutoUpdater()insrc/main/updater.tsregisters IPC handlers and starts the periodic timer - Check frequency: Every 6 hours, plus 30-second post-boot delay, gated by
autoUpdatepreference and packaged-build detection - Dual path design: Native
electron-updaterpreferred; GitHub API fallback for unsupported environments - State safety:
reduceStatus()prevents staged updates from being overwritten by stale checks - User control: Updates download automatically but only install after explicit
restartAndInstallinvocation - Atomic restart:
autoUpdater.quitAndInstall()exits and replaces the application in one operation
Frequently Asked Questions
How do I disable automatic update checks in Munder Difflin?
Set the autoUpdate preference to false in the application's Settings page. This is stored in src/renderer/src/store/config.ts and gates runCheck() from executing. Manual checks via update:checkNow remain available regardless of this setting.
Why does Munder Difflin sometimes show "Update available" without downloading?
This occurs when the fallback path activates. If electron-updater cannot load—typically due to code signing issues or sandbox restrictions—the app detects newer versions via GitHub API but cannot self-update. The UI presents a link to manually download the release instead.
Will I lose my work if I restart to update?
No. The restart handler calls quitAndInstall() only after you explicitly trigger it, giving opportunity to save. The underlying electron-updater implementation preserves the user-data directory and application state across versions. However, unsaved in-app work should be saved normally before any quit.
How can developers simulate update states for testing?
Invoke the update:simulate IPC method with a desired status payload. This bypasses the native updater entirely and emits synthetic status events through the standard pipeline, allowing UI testing of download progress, availability notices, and error states without network dependencies or actual version mismatches.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →