# How Munder Difflin's Auto-Update Mechanism Works and What Happens on Restart

> Understand Munder Difflin's auto-update mechanism. Learn how it checks for updates every 6 hours, uses GitHub API fallback, and requires manual restart via autoUpdater.quitAndInstall() for installation.

- Repository: [Chaitanya Giri/munder-difflin](https://github.com/chaitanyagiri/munder-difflin)
- Tags: internals
- Published: 2026-08-20

---

**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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/updater.ts) with `initAutoUpdater()`, which registers five IPC handlers:

- `update:checkNow` — triggers an immediate version check
- `update:download` — starts downloading an available update
- `update:restartAndInstall` — applies the staged update
- `update:current` — returns the running version
- `update:simulate` and `update: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:

1. The `autoUpdate` user preference is enabled (stored in [`src/renderer/src/store/config.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/renderer/src/store/config.ts))
2. The app is running from a packaged build (skipped in development)

```typescript
// 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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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.

```typescript
// 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:

```typescript
// 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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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 by [`UpdateBadge.tsx`](https://github.com/chaitanyagiri/munder-difflin/blob/main/UpdateBadge.tsx) for the toolbar badge; returns `label`, `tone`, `tooltip`, and click `action`
- **`describeUpdateSettings()`** — 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:

```typescript
await window.ipcRenderer.invoke('update:restartAndInstall');

```

The main process handler (lines 40-46 in [`updater.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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:

1. `quitAndInstall()` terminates all renderer and main process windows
2. The downloaded installer executes immediately
3. 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
- `quitAndInstall` invocations

This provides post-mortem visibility without blocking the update flow or exposing sensitive data.

## Summary

- **Entry point:** `initAutoUpdater()` in [`src/main/updater.ts`](https://github.com/chaitanyagiri/munder-difflin/blob/main/src/main/updater.ts) registers IPC handlers and starts the periodic timer
- **Check frequency:** Every 6 hours, plus 30-second post-boot delay, gated by `autoUpdate` preference and packaged-build detection
- **Dual path design:** Native `electron-updater` preferred; 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 `restartAndInstall` invocation
- **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`](https://github.com/chaitanyagiri/munder-difflin/blob/main/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.