Skip to content

refactor(desktop): Restyled and less intrusive/interupting update install#464

Closed
AviPeltz wants to merge 4 commits intomainfrom
less-annoying-autupdate
Closed

refactor(desktop): Restyled and less intrusive/interupting update install#464
AviPeltz wants to merge 4 commits intomainfrom
less-annoying-autupdate

Conversation

@AviPeltz
Copy link
Copy Markdown
Collaborator

@AviPeltz AviPeltz commented Dec 22, 2025

Description

Related Issues

Type of Change

  • Bug fix
  • New feature
  • Documentation
  • Refactor
  • Other (please describe):

Testing

Screenshots (if applicable)

Screenshot 2025-12-21 at 11 49 32 PM Screenshot 2025-12-21 at 11 52 07 PM

Additional Notes

Summary by CodeRabbit

  • New Features
    • Added a desktop update notification that alerts users when app updates are available.
    • Users can now install updates immediately or defer them with a dismiss option.
    • Implemented real-time update status tracking and display.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 22, 2025

Walkthrough

This PR introduces an event-driven auto-update architecture for the desktop application. A new TRPC router exposes auto-update operations (subscribe, getStatus, install, dismiss, check), while the refactored auto-updater emits status events via an EventEmitter instead of triggering direct UI dialogs. A new UpdateToast React component subscribes to these events to render update notifications. The MainWindow no longer registers itself with the auto-updater.

Changes

Cohort / File(s) Summary
Auto-updater Core
apps/desktop/src/main/lib/auto-updater.ts
Refactored to use event-driven pattern. Introduces autoUpdateEmitter and replaces direct dialog prompts with AUTO_UPDATE_EVENTS.STATUS_CHANGED emissions. Adds public API: getUpdateStatus(), installUpdate(), dismissUpdate(), simulateUpdateReady(), checkForUpdates(), checkForUpdatesInteractive(), setupAutoUpdater(). Removes setMainWindow() setter and centralizes state tracking (currentStatus, currentVersion, currentError, isDismissed).
TRPC Auto-update Router
apps/desktop/src/lib/trpc/routers/auto-update/index.ts, apps/desktop/src/lib/trpc/routers/index.ts
Creates new createAutoUpdateRouter() with procedures: subscribe (observable stream), getStatus (query), installAndRestart, dismiss, checkForUpdates, simulateUpdateReady (mutations). Defines public AutoUpdateEvent interface. Integrates router into main app router.
Shared Constants
apps/desktop/src/shared/constants.ts
Adds AUTO_UPDATE_EVENTS, AUTO_UPDATE_STATUS constants and AutoUpdateStatus type union.
UpdateToast Component
apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx, apps/desktop/src/renderer/components/UpdateToast/index.ts, apps/desktop/src/renderer/index.tsx
New UpdateToast React component subscribing to auto-update status via TRPC. Renders only when downloading or ready. Exposes development helper for simulating updates. Includes index barrel export and renderer integration.
Window Management
apps/desktop/src/main/windows/main.ts
Removed setMainWindow() import and call during MainWindow initialization.

Sequence Diagram

sequenceDiagram
    participant App as App Startup
    participant AU as Auto-updater<br/>(main)
    participant Emitter as EventEmitter
    participant TRPC as TRPC Router
    participant UI as UpdateToast<br/>(renderer)

    App->>AU: setupAutoUpdater()
    activate AU
    AU->>AU: Register auto-updater<br/>lifecycle listeners
    AU->>Emitter: Emit STATUS_CHANGED<br/>(status: IDLE)
    deactivate AU

    rect rgb(200, 220, 240)
    Note over AU,UI: Periodic/User-Triggered Check
    AU->>AU: checkForUpdates()
    AU->>Emitter: Emit STATUS_CHANGED<br/>(status: CHECKING)
    Emitter-->>TRPC: subscribe() stream
    TRPC-->>UI: Status update event
    UI->>UI: Re-render
    end

    rect rgb(220, 240, 200)
    Note over AU,UI: Update Available & Download
    AU->>Emitter: Emit STATUS_CHANGED<br/>(status: DOWNLOADING)
    Emitter-->>TRPC: subscribe() stream
    TRPC-->>UI: Status update event
    UI->>UI: Show download message
    AU->>Emitter: Emit STATUS_CHANGED<br/>(status: READY)
    Emitter-->>TRPC: subscribe() stream
    TRPC-->>UI: Status update event
    UI->>UI: Show "Install" button
    end

    rect rgb(240, 220, 200)
    Note over UI,AU: User Action
    UI->>TRPC: installAndRestart mutation
    TRPC->>AU: installUpdate()
    AU->>Emitter: Emit STATUS_CHANGED<br/>(status: IDLE)
    Emitter-->>TRPC: subscribe() stream
    TRPC-->>UI: Confirm & restart
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Areas requiring extra attention:

  • auto-updater.ts: The refactoring introduces a significant behavioral shift from dialog-driven to event-driven architecture. Critical areas include the event listener setup in setupAutoUpdater(), state management (currentStatus, currentVersion, currentError, isDismissed), the removal of mainWindow dependency, and the handling of dismissed updates (idle when READY).
  • UpdateToast component: Verify proper subscription cleanup, cache invalidation on status changes, and the development-only global window helper is correctly scoped and disabled in production.
  • TRPC router integration: Ensure the subscribe observable correctly publishes the current status immediately and properly manages listener attachment/cleanup (potential for memory leaks if not handled correctly).

Possibly related PRs

  • open new window #59: Modifies apps/desktop/src/main/windows/main.ts to remove window/updater coupling and transition to window-manager-style management — directly related to the MainWindow refactoring in this PR.
  • feat(desktop): improve auto-updater with user notifications #243: Modifies the auto-updater implementation with a dialog-driven, mainWindow-backed approach — represents the opposing architectural pattern that this PR replaces with event-driven design.
  • New desktop UI #102: Introduces/modifies the app TRPC router composition (createAppRouter) — related to the new autoUpdate router integration in this PR.

Poem

🐰 Hops and updates dance in sync,
No dialogs needed—events link!
Toast appears, the UI smiles,
Emitters emit, status beguiles,
Event-driven dreams take flight!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning PR description is largely incomplete; only the Type of Change (Refactor) is checked, while Description, Related Issues, Testing, and Additional Notes sections are either empty or lack substantive details. Add a detailed description explaining the architectural shift from dialog-driven to event-driven updates, document testing steps, and explain why this change reduces interruption.
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed Title references a refactor for desktop updates to be less intrusive, which aligns with the comprehensive redesign of the auto-update system from dialog-driven to event-driven architecture.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch less-annoying-autupdate

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@AviPeltz AviPeltz marked this pull request as ready for review December 22, 2025 08:08
@AviPeltz AviPeltz changed the title refactor(desktop): Restyled and less intrusive/interupting refactor(desktop): Restyled and less intrusive/interupting update install Dec 22, 2025
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx (1)

49-55: Consider handling the ERROR status for user feedback.

The component only shows UI for DOWNLOADING and READY states. When an error occurs during update checking or downloading, the user receives no visual feedback. Consider whether displaying an error state (perhaps with a retry option) would improve user experience.

🔎 Optional: Add error state handling
 	const isDownloading = status?.status === AUTO_UPDATE_STATUS.DOWNLOADING;
 	const isReady = status?.status === AUTO_UPDATE_STATUS.READY;
+	const isError = status?.status === AUTO_UPDATE_STATUS.ERROR;

 	// Only show when downloading or ready
-	if (!status || (!isDownloading && !isReady)) {
+	if (!status || (!isDownloading && !isReady && !isError)) {
 		return null;
 	}

Then add error UI in the render section if desired.

apps/desktop/src/lib/trpc/routers/auto-update/index.ts (1)

87-93: DEV-only guard is in the implementation, not the router.

The docstring says "DEV ONLY" but this endpoint is publicly accessible. The runtime guard exists in simulateUpdateReady() in auto-updater.ts, which logs a warning and returns early in production. This is acceptable since the effect is harmless in production, but consider whether the endpoint should be conditionally registered.

apps/desktop/src/main/lib/auto-updater.ts (1)

205-209: Consider exposing download progress to the UI.

Currently, download-progress only logs to console. Since the UI shows "Downloading v{version}...", exposing the progress percentage could enhance user experience. This could be done by extending the status payload or adding a separate progress field.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 301b55d and 69b3bf4.

📒 Files selected for processing (8)
  • apps/desktop/src/lib/trpc/routers/auto-update/index.ts
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/main/lib/auto-updater.ts
  • apps/desktop/src/main/windows/main.ts
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/index.ts
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/shared/constants.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/main/windows/main.ts
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid using any type in TypeScript - maintain type safety unless absolutely necessary

Files:

  • apps/desktop/src/renderer/components/UpdateToast/index.ts
  • apps/desktop/src/shared/constants.ts
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/lib/trpc/routers/auto-update/index.ts
  • apps/desktop/src/main/lib/auto-updater.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Run Biome for formatting, linting, import organization, and safe fixes at the root level using bun run lint:fix

Files:

  • apps/desktop/src/renderer/components/UpdateToast/index.ts
  • apps/desktop/src/shared/constants.ts
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/lib/trpc/routers/auto-update/index.ts
  • apps/desktop/src/main/lib/auto-updater.ts
**/{components,features}/**/*.{ts,tsx,test.ts,test.tsx,stories.tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Co-locate component dependencies (utils, hooks, constants, config, tests, stories) next to the file using them

Files:

  • apps/desktop/src/renderer/components/UpdateToast/index.ts
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
apps/desktop/src/renderer/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Never import Node.js modules in renderer process or shared code - use only in main process (src/main/)

Files:

  • apps/desktop/src/renderer/components/UpdateToast/index.ts
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
apps/desktop/src/{main,renderer,preload}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use type-safe IPC communication - define channel types in apps/desktop/src/shared/ipc-channels.ts before implementing handlers

Files:

  • apps/desktop/src/renderer/components/UpdateToast/index.ts
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/main/lib/auto-updater.ts
apps/desktop/**/*.{ts,tsx}

📄 CodeRabbit inference engine (apps/desktop/AGENTS.md)

apps/desktop/**/*.{ts,tsx}: For Electron interprocess communication, ALWAYS use tRPC as defined in src/lib/trpc
Use alias as defined in tsconfig.json when possible
Prefer zustand for state management if it makes sense. Do not use effect unless absolutely necessary.
For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators, as the library explicitly checks isObservable(result) and throws an error otherwise

Files:

  • apps/desktop/src/renderer/components/UpdateToast/index.ts
  • apps/desktop/src/shared/constants.ts
  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/lib/trpc/routers/index.ts
  • apps/desktop/src/lib/trpc/routers/auto-update/index.ts
  • apps/desktop/src/main/lib/auto-updater.ts
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use React + TailwindCSS v4 + shadcn/ui for UI development

Files:

  • apps/desktop/src/renderer/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
**/{components,features}/**/[!.]*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Organize project structure with one folder per component: ComponentName/ComponentName.tsx with index.ts barrel export

Files:

  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
**/{components,features}/**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

**/{components,features}/**/*.tsx: Nest components in parent's components/ folder if used only once, promote to highest shared parent's components/ if used 2+ times
Use one component per file - do not combine multiple components in a single file

Files:

  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
apps/desktop/src/main/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Accept object parameters in IPC handlers - do not use positional parameters in ipcMain.handle()

Files:

  • apps/desktop/src/main/lib/auto-updater.ts
🧬 Code graph analysis (4)
apps/desktop/src/renderer/index.tsx (1)
apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx (1)
  • UpdateToast (7-97)
apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx (3)
apps/desktop/src/renderer/components/UpdateToast/index.ts (1)
  • UpdateToast (1-1)
apps/desktop/src/shared/constants.ts (1)
  • AUTO_UPDATE_STATUS (55-61)
packages/ui/src/components/ui/button.tsx (1)
  • Button (60-60)
apps/desktop/src/lib/trpc/routers/index.ts (1)
apps/desktop/src/lib/trpc/routers/auto-update/index.ts (1)
  • createAutoUpdateRouter (20-95)
apps/desktop/src/main/lib/auto-updater.ts (1)
apps/desktop/src/shared/constants.ts (4)
  • AutoUpdateStatus (63-64)
  • AUTO_UPDATE_STATUS (55-61)
  • AUTO_UPDATE_EVENTS (50-52)
  • PLATFORM (4-8)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build
🔇 Additional comments (10)
apps/desktop/src/renderer/components/UpdateToast/index.ts (1)

1-1: LGTM!

Clean barrel export following the project's component organization pattern.

apps/desktop/src/renderer/index.tsx (1)

5-5: LGTM!

UpdateToast is correctly imported via the barrel file and placed within AppProviders, ensuring it has access to the tRPC context for subscription and mutations.

Also applies to: 16-16

apps/desktop/src/lib/trpc/routers/index.ts (1)

4-4: LGTM!

The auto-update router is correctly integrated into the application router. Unlike window-dependent routers, it appropriately uses an event-driven approach and doesn't require a window reference.

Also applies to: 29-29

apps/desktop/src/shared/constants.ts (1)

48-64: LGTM!

Well-structured constants following the existing patterns in this file. The status values comprehensively cover the auto-update lifecycle, and the type derivation is idiomatic TypeScript.

apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx (1)

7-31: LGTM on the tRPC integration pattern.

The hook setup correctly uses:

  • Query for initial status fetch
  • Mutations with onSuccess callbacks to invalidate the cache
  • Subscription for real-time updates with cache invalidation
  • Ref pattern to avoid effect dependency issues

This aligns well with the coding guidelines requiring tRPC for Electron IPC and the observable pattern for subscriptions.

apps/desktop/src/lib/trpc/routers/auto-update/index.ts (2)

25-54: LGTM on the subscription implementation.

The observable pattern is correctly used as required by the coding guidelines for trpc-electron subscriptions. The implementation properly:

  • Emits current status immediately on subscribe
  • Sets up the event listener for subsequent updates
  • Returns a cleanup function to remove the listener

1-11: No issues found. The router correctly imports from main process modules and executes in the main process context, as intended.

apps/desktop/src/main/lib/auto-updater.ts (3)

16-23: LGTM on the state management approach.

The module-level state variables with an EventEmitter provide a clean, centralized state management for auto-update status. This effectively decouples the UI from direct dialog interactions.


169-231: LGTM on setupAutoUpdater implementation.

The setup correctly:

  • Guards against dev/non-macOS environments
  • Configures autoDownload and autoInstallOnAppQuit
  • Sets up all lifecycle event handlers with appropriate status emissions
  • Uses interval.unref() to prevent keeping the process alive
  • Handles both ready and not-ready app states for initial check

71-73: The isForceRunAfter=true parameter is ignored when isSilent=false.

The isForceRunAfter parameter is ignored if isSilent is set to false. With isSilent=false, the installer runs in non-silent mode, showing the UI, and the autoRunAppAfterInstall setting controls whether the app runs after installation. If auto-restart after installation is required, verify that either isSilent should be true with isForceRunAfter=true, or that autoRunAppAfterInstall is properly configured for non-silent mode.

@AviPeltz AviPeltz closed this Dec 26, 2025
@Kitenite Kitenite deleted the less-annoying-autupdate branch December 28, 2025 20:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant