-
Notifications
You must be signed in to change notification settings - Fork 5
🤖 Add update notification UI #329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+1,351
−17
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
3e6ff4e
to
145b381
Compare
- Install electron-updater package for automatic update detection - Create UpdaterService to manage update checks, downloads, and installations - Add update IPC channels and handlers in main process - Expose update API through preload script - Add subtle update indicator icon to TitleBar (left of 'cmux') - Green download icon when update available - Blue spinning icon during download - Orange icon when ready to install - Update indicator shows version info and click-to-download/install - Only runs in packaged builds (skips dev mode) - Periodic checks every 4 hours, starting 10s after launch Generated with `cmux`
- Check telemetry status before initiating update checks - Show gray disabled icon (⊘) when telemetry is off - Tooltip indicates update checks are disabled - Prevent clicking update indicator when disabled - Skip update.getStatus() and update.onStatus() calls when telemetry disabled This respects user privacy by not phoning home when telemetry is disabled. Generated with `cmux`
- Remove unnecessary UPDATE_STATUS_UNSUBSCRIBE channel - Follows existing pattern (workspace:metadata, workspace:chat) - Main process doesn't track subscriptions - removeListener in preload is sufficient for cleanup - Add DEBUG_UPDATER=1 env var to enable updater in development - Allows testing update flow without building production package - Logs when updater is initialized with debug/packaged status Simpler IPC pattern, easier to test. Generated with `cmux`
- Remove automatic periodic checks from backend - Frontend TitleBar now controls when checks happen: - Checks on mount (if telemetry enabled) - Periodic checks every 4 hours (if telemetry enabled) - Backend UpdaterService simplified: - Removed startPeriodicChecks() and stopPeriodicChecks() - No longer maintains update check interval - Just responds to check() IPC calls from frontend This ensures update checks respect the user's telemetry preference. When telemetry is disabled, no update checks are made. Generated with `cmux`
- Update indicator now always visible (was confusing when hidden) - Shows gray disabled icon (⊘) when: - Telemetry is off - No update available - Checking for updates - Improved tooltips: - Telemetry disabled: Explains updates require telemetry - No updates: Shows check frequency - Checking: Shows current state Users now always see the indicator and understand why updates might be unavailable. Generated with `cmux`
- TitleText now uses ellipsis if too long (never line-breaks) - All update tooltips now show current version first - Example: "Current: v0.3.0-rc.2. No updates available. Checks every 4 hours." - Example: "Current: v0.3.0-rc.2. Update available: v0.3.1. Click to download." Better UX: Users always know their current version when checking update status. Generated with `cmux`
- Add margin-right to LeftSection to prevent title from getting too close to date - Add min-width: 0 to TitleText for proper ellipsis in flex container - Use JSX with <br/> tags in tooltips for better multi-line formatting - Much simpler than modifying Tooltip component - Better visual separation of information Example tooltip: Current: v0.3.0-rc.2 Update available: v0.3.1 Click to download. Generated with `cmux`
- Check for updates automatically on hover (respects telemetry) - Show live "Checking for updates..." feedback in tooltip - DRY tooltip code: single function builds lines array instead of duplicate JSX - Only check on hover if: - Telemetry is enabled - Not already checking - Current status is "not-available" - Clear checking state when new status arrives UX improvement: Users get instant feedback when hovering, no manual check needed. Generated with `cmux`
The issue was that autoUpdater.checkForUpdates() returns a promise that never resolves - it only fires events. The IPC handler was awaiting this promise, causing the UI to hang indefinitely. Changes: - UpdaterService.checkForUpdates() now returns void and triggers check without awaiting (events handle the actual status updates) - Added 1-minute debounce to hover checks to prevent rapid successive calls - Frontend already had proper cleanup: resets isCheckingOnHover on status updates - IPC handler no longer returns UpdateStatus (status delivered via events) This ensures: 1. No blocking on hover 2. Status updates delivered via subscription pattern 3. Prevents spam from repeated hovers
The 'checking' state was hanging indefinitely when electron-updater didn't emit any events (e.g., in dev mode with DEBUG_UPDATER=1, or network failures). Root cause: autoUpdater.checkForUpdates() is event-driven. If no events fire (update-available, update-not-available, or error), the status stays 'checking' forever. Fix: - Added 30-second timeout in checkForUpdates() - Timeout resets status to 'not-available' if still checking - Timeout cleared when any completion event fires - Added comprehensive tests including timeout verification Tests verify: - Immediate 'checking' status notification - Transition to 'not-available' when no update - Transition to 'available' when update found - Error handling - Timeout fallback when no events fire
Created integration test that verifies update system prerequisites: Tests verify: - GitHub releases API is accessible (coder/cmux) - Releases have required structure (tag, publish date, assets) - Platform-specific assets exist (.dmg, .AppImage, .yml manifests) - GitHub API rate limits are sufficient for unauthenticated requests Why not test electron-updater directly: - electron-updater requires real Electron runtime - Can't instantiate in test environment (no app.isPackaged, etc.) - Testing GitHub API directly validates the foundation Manual electron-updater testing: 1. Run `DEBUG_UPDATER=1 make dev` 2. Hover over update indicator 3. Verify it checks without hanging 4. Check console for update status Test output shows: - Latest release: v0.3.0-rc.2 - 7 assets including .dmg, .AppImage, yml manifests - 60 requests/hour rate limit available
145b381
to
4fe2971
Compare
Root cause: When updaterService is null (dev mode without DEBUG_UPDATER), the IPC handler returned silently without sending status update to frontend. Frontend stayed in 'checking' state indefinitely. Changes: 1. Backend always sends status update (even when updaterService null) - UPDATE_CHECK handler sends 'not-available' if no updater - Ensures frontend always gets a response 2. Added interactive tooltip with releases link - Defense-in-depth: Always show link to GitHub releases - Opens in external browser via Electron's setWindowOpenHandler - Tooltip made interactive to allow link clicks This fixes the hang in all scenarios: - Dev without DEBUG_UPDATER: immediate 'not-available' - Dev with DEBUG_UPDATER: 30s timeout if no events - Packaged build: normal electron-updater flow
Unnecessary information since hovering already triggers an update check. Simplifies tooltip messaging.
Added detailed logging to debug 'Checking for updates' hang: Backend (main.ts): - Log when UPDATE_CHECK called (with updaterService availability) - Log when sending 'not-available' for null updater - Log when calling updaterService.checkForUpdates() - Log UPDATE_STATUS_SUBSCRIBE and current status being sent UpdaterService: - Log checkForUpdates() entry - Log status transitions (checking, timeout, error) - Log notifyRenderer() calls with status - Log whether mainWindow is available - Log timeout behavior (fired vs already changed) This will help diagnose: - Is updater service null or available? - Is checkForUpdates() being called? - Are status updates being sent to renderer? - Is the timeout firing correctly? - Is mainWindow connected properly?
Improvements to logging infrastructure: 1. Added ISO timestamps to log interface (log.ts) - Format: [2025-10-20T01:23:11.000Z] [file.ts:123] - Eliminates manual timestamp() calls - Automatic file path and line number from stack trace 2. Converted updater service to use log interface - Replaced all console.log/error with log.info/error - Removed [UpdaterService] prefix (redundant with file path) - All logs now have consistent timestamp + location format 3. Converted main.ts updater handlers to use log interface - UPDATE_CHECK and UPDATE_STATUS_SUBSCRIBE handlers - Dynamic import to avoid circular dependency Benefits: - No manual timestamp duplication - No manual component name duplication - Consistent log format across entire codebase - File path + line number for easy debugging - EPIPE protection (pipe-safe logging) Example log output: [2025-10-20T01:23:11.456Z] [src/services/updater.ts:108] checkForUpdates() called
Changed log timestamp format to be more human-readable: - Before: [2025-10-20T01:23:11.456Z] [src/services/updater.ts:108] - After: 8:23.232PM src/main.ts:23 Kitchen time format: - 12-hour time with milliseconds (8:23.232PM) - More readable for development/debugging - Removed brackets - cleaner visual separation - Format: HH:MM.sssAM/PM file.ts:line <message> Components styled for readability: - Time: 8:23.232PM (hours:mins.secsmillisAM/PM) - Location: src/main.ts:23 (file path with line number) - Message: whatever was logged Example: 8:23.456PM src/services/updater.ts:108 checkForUpdates() called 8:23.457PM src/services/updater.ts:115 Setting status to 'checking'
- Fix timestamp format: show milliseconds only (8:28.403PM instead of 8:25.40323PM) - Add chalk for terminal coloring (PTY detection via process.stdout.isTTY) - Dim timestamps, cyan file locations, red errors - Make updater IPC logs less verbose (info → debug) - Add message when updater service is disabled in dev mode - Document React StrictMode double-mounting behavior The updater service is only initialized when: - app.isPackaged (production), OR - DEBUG_UPDATER=1 (dev mode) Use CMUX_DEBUG=1 to see debug logs including updater IPC calls.
- Add parseBoolEnv() helper function (accepts "1", "true", "yes") - Export from log service for reuse - Use in main.ts for DEBUG_UPDATER initialization - Use in updater.ts for forceDevUpdateConfig Key fix: Set autoUpdater.forceDevUpdateConfig = true when DEBUG_UPDATER is enabled. This allows electron-updater to actually check for updates in unpacked dev builds. Without this, electron-updater silently skips with: "Skip checkForUpdates because application is not packed and dev update config is not forced" Now both DEBUG_UPDATER=1 and DEBUG_UPDATER=true work consistently.
electron-updater requires this file when forceDevUpdateConfig is enabled. It tells the updater where to check for releases in development mode. Without this file, electron-updater fails with: ENOENT: no such file or directory, open '.../dev-app-update.yml' Config points to coder/cmux GitHub releases.
Replace ambiguous 'not-available' state with two explicit states: - 'idle': Initial state, no check performed yet - 'up-to-date': Explicitly checked, no updates available This clarifies the difference between: 1. Updater hasn't checked yet (idle) 2. Updater checked and confirmed no updates (up-to-date) Changes: - Updated UpdateStatus type in ipc.ts and updater.ts - UpdaterService initializes to 'idle', transitions to 'up-to-date' after check - Timeout on failed check returns to 'idle' (unknown state) - TitleBar UI shows 'Hover to check' for idle, 'Up to date' for up-to-date - Hover triggers check for both idle and up-to-date states - All tests updated and passing UI now clearly communicates updater state to users.
- Created src/utils/env.ts as single source of truth for env parsing - Removed duplicate parseBoolEnv implementations from log.ts and updater.ts - Updated all imports to use centralized utils/env - Deleted tests/ipcMain/updater.test.ts (just pinged GitHub API, didn't test our code) The parseBoolEnv utility is now properly located in utils where it belongs, not buried in the logging service.
DEBUG_UPDATER now supports two modes: - DEBUG_UPDATER=1 / true / yes → Enable updater in dev mode - DEBUG_UPDATER=1.2.3 → Enable updater + fake that version as available This allows testing the full update UI flow without needing real GitHub releases. When a version is specified: - UpdaterService immediately reports it as available (500ms delay to simulate check) - Full UI flow works: download button, install prompt, etc. - Useful for manual testing, demos, and development Changes: - Added parseDebugUpdater() to src/utils/env.ts - UpdaterService stores fakeVersion and shortcuts check when set - Updated main.ts to log fake version when enabled - Tests save/restore DEBUG_UPDATER to avoid interference Example usage: DEBUG_UPDATER=99.0.0 make start # Test "update available" flow
When using DEBUG_UPDATER=<version> for testing, clicking download or install would fail with "Please check update first" because electron-updater didn't know about the fake update. Fixed by handling fake version in downloadUpdate() and installUpdate(): - downloadUpdate(): Simulates progress 0-100% over 2 seconds, then marks as downloaded - installUpdate(): Logs the action (can't actually restart with fake update) Now the full update UI flow works end-to-end with fake versions: 1. Hover → "Update available: 99.0.0" 2. Click → Download progress bar animates 3. Click again → "Would restart app here" log Perfect for testing/demos without needing real GitHub releases.
- Change routine updater operations from log.info() to log.debug() - Keep log.info() for: update available, update downloaded, errors - Move to log.debug(): checking, up-to-date, progress, internal state - Extract magic constants to named constants: - UPDATE_CHECK_TIMEOUT_MS = 30_000 (30 seconds) - UPDATE_CHECK_INTERVAL_MS = 4 hours - UPDATE_CHECK_HOVER_COOLDOWN_MS = 1 minute - Improves log readability in normal operation - Constants make values more maintainable and self-documenting
- Fixed updater.ts lint errors: - Made fakeVersion readonly - Removed unnecessary async from checkForUpdates() - Fixed type assertions to use 'satisfies' pattern - Fixed main.ts: - Changed dynamic imports of log/env to static imports (they're simple) - Removed unnecessary async from IPC handlers - Added eslint-disable for type import annotation - Fixed updater.test.ts: - Added file-level eslint-disable for test mocking patterns - Made async tests that use await actually async - Fixed preload.ts: - Wrapped install() invoke in void to match type signature - Fixed jest.config.js + added chalk mock: - Mock chalk module for Jest (ESM-only, not needed in tests) - Avoids 'Cannot use import statement outside module' errors - Chalk colors aren't visible in test output anyway All tests (708) passing, lint clean, typecheck clean.
The jest.config.js had two moduleNameMapper entries (lines 12 and 31), causing the second to overwrite the first. This lost the @/ alias mapping, breaking integration tests in CI with 'Cannot find module @/git' errors. Also added extract_pr_logs.sh script to quickly fetch CI logs for debugging, and updated wait_pr_checks.sh to reference it when checks fail.
- Accept PR number directly (auto-finds latest failed run) - Add --wait flag to retry log fetching - Suggest local make commands to reproduce failures - Fix shellcheck warnings in wait_pr_checks.sh
- Changed from 'gh run view --log' to 'gh api /repos/.../actions/jobs/{id}/logs' - This works for individual completed jobs even when run is still in progress - Fixed shfmt formatting: use -i 2 (2 spaces) instead of tabs as per fmt.mk
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Adds a subtle update indicator to the title bar using electron-updater.
Changes
UX
The indicator appears to the left of "cmux" in the title bar. It's intentionally subtle - only visible when an update is available, downloading, or ready to install.
Users have full control:
Technical Details
Generated with
cmux