Skip to content

feat(desktop): replace auto-update dialog with in-app toast notification#508

Merged
Kitenite merged 19 commits intomainfrom
make-auto-update-less-intrusive
Dec 26, 2025
Merged

feat(desktop): replace auto-update dialog with in-app toast notification#508
Kitenite merged 19 commits intomainfrom
make-auto-update-less-intrusive

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Dec 25, 2025

Summary

  • Replace intrusive Electron dialog with subtle toast notification in bottom-right corner
  • Add tRPC router for auto-update events with subscribe subscription and installUpdate mutation
  • Create UpdateToast component with "See changes" and "Restart" buttons

Before

Native Electron dialog that blocks interaction and feels disruptive.

After

Subtle toast notification that:

  • Shows in bottom-right corner
  • Displays version info ("Restart to use v{version}")
  • Has "See changes" button to open release notes
  • Has "Restart" button to install update
  • Can be dismissed via X button
  • Persists until user action (infinite duration)

Test plan

  • Build desktop app and trigger an update (or simulate update-downloaded event)
  • Verify toast appears in bottom-right corner
  • Test "See changes" opens release notes URL
  • Test "Restart" triggers update installation
  • Test dismiss button removes toast
  • Verify manual "Check for Updates" from menu still works

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Interactive desktop update toasts showing status, progress, version, error, and actions to view release notes, install, or dismiss.
    • Live update status stream powers UI and lets the app trigger install/dismiss actions.
  • Chores

    • Development-only menu items to simulate downloading/ready/error update states.
    • Small CSS tweak to stabilize the update-toast hover/transition.

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

Replace the intrusive Electron dialog with a subtle toast notification in the
bottom-right corner when updates are downloaded. The toast shows version info
with "See changes" (opens release notes) and "Restart" buttons.

- Add tRPC router for auto-update events and install action
- Create UpdateToast component with dismiss button
- Use EventEmitter to broadcast update events to renderer
- Remove unused setMainWindow function

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Dec 25, 2025

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Adds an event-driven auto-update system: main process emits update status events; a new TRPC autoUpdate router streams and controls updates; renderer subscribes to show persistent UpdateToast UI; setMainWindow was removed. (50 words)

Changes

Cohort / File(s) Summary
TRPC Auto-Update Router
apps/desktop/src/lib/trpc/routers/auto-update/index.ts, apps/desktop/src/lib/trpc/routers/index.ts
New createAutoUpdateRouter() integrated into AppRouter. Provides subscribe subscription (observable), getStatus query, and mutations: install, dismiss, simulateReady, simulateDownloading, simulateError.
Main auto-updater
apps/desktop/src/main/lib/auto-updater.ts
Reworked to emit AutoUpdateStatusEvent via exported autoUpdateEmitter; added getUpdateStatus(), installUpdate(), dismissUpdate(), dev simulate* helpers, centralized status state and emit logic, expanded updater event wiring; removed setMainWindow.
Main window init
apps/desktop/src/main/windows/main.ts
Removed setMainWindow(window) call during main window creation.
Renderer: UpdateToast UI & Hook
apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx, apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx, apps/desktop/src/renderer/components/UpdateToast/index.ts
New UpdateToast component and useUpdateListener hook. Hook subscribes to trpc.autoUpdate.subscribe, shows/dismisses persistent toast, and mutations are wired to open releases URL, install, and dismiss.
Screen integration
apps/desktop/src/renderer/screens/main/index.tsx
Calls useUpdateListener() in MainScreen to enable update toast behavior.
Dev menu items
apps/desktop/src/main/lib/menu.ts
Adds dev-only menu entries (gated by env.NODE_ENV === "development") to call simulateDownloading(), simulateUpdateReady(), and simulateError().
Shared types & constants
apps/desktop/src/shared/auto-update.ts, apps/desktop/src/shared/constants.ts
Adds AUTO_UPDATE_STATUS, AutoUpdateStatus type, RELEASES_URL; marks NOTIFICATION_EVENTS with as const.
Styling & minor
apps/desktop/src/renderer/globals.css, apps/desktop/src/main/lib/local-db/index.ts
Disable Sonner toast hover animation for update toasts; trivial whitespace formatting change.

Sequence Diagram(s)

sequenceDiagram
    participant Main as Main Process (auto-updater)
    participant Emitter as autoUpdateEmitter
    participant Router as TRPC autoUpdate Router
    participant Renderer as Renderer (useUpdateListener)
    participant Toast as UpdateToast UI

    rect rgba(238,246,255,0.8)
      Note over Main,Emitter: Main emits status events
      Main->>Emitter: emit(status: CHECKING/DOWNLOADING/READY/ERROR)
    end

    rect rgba(242,255,240,0.8)
      Emitter-->>Router: observable emits event
      Router-->>Renderer: tRPC subscription pushes event
      Renderer->>Toast: render or dismiss toast per status
    end

    alt User clicks "Install" when READY
      Toast->>Router: call install mutation
      Router->>Main: invoke installUpdate()
    end

    rect rgba(255,245,240,0.8)
      Note over Main,Renderer: Dev triggers (menu) call simulateDownloading/simulateReady/simulateError
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A hop, a ping, a status bright,

Events now chatter through the night.
Toasts appear with version news,
Click install — I'll nap and snooze. ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: replacing an Electron dialog with a toast notification, which is the primary objective.
Description check ✅ Passed The description provides a clear summary, before/after comparison, and test plan, but does not fully match the template structure with explicit sections for all required fields.

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9ba59da and 4bfd942.

📒 Files selected for processing (5)
  • apps/desktop/src/lib/trpc/routers/auto-update/index.ts
  • apps/desktop/src/main/lib/auto-updater.ts
  • apps/desktop/src/main/lib/menu.ts
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx

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.

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: 4

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

109-112: Release URL construction assumes version format with 'v' prefix.

The release URL is constructed as `${RELEASES_URL}/tag/v${info.version}` (line 111), which assumes the GitHub release tag follows the format v{version} (e.g., v1.2.3). While this is a common convention, the version from electron-updater might not always include or need the 'v' prefix.

Consider verifying the version format or making the URL construction more flexible:

 		const event: UpdateDownloadedEvent = {
 			version: info.version,
-			releaseUrl: `${RELEASES_URL}/tag/v${info.version}`,
+			releaseUrl: `${RELEASES_URL}/tag/${info.version.startsWith('v') ? '' : 'v'}${info.version}`,
 		};

Or document the assumption:

+		// Note: Assumes GitHub release tags follow the format v{version}
 		const event: UpdateDownloadedEvent = {
 			version: info.version,
 			releaseUrl: `${RELEASES_URL}/tag/v${info.version}`,
 		};
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1baa6b2 and c1a614b.

📒 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/components/UpdateToast/useUpdateListener.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
💤 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/useUpdateListener.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/index.ts
  • 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/useUpdateListener.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/index.ts
  • 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}/**/[!.]*.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/useUpdateListener.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
**/{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/useUpdateListener.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/index.ts
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/useUpdateListener.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/index.ts
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/useUpdateListener.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/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/components/UpdateToast/useUpdateListener.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • 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/useUpdateListener.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
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/useUpdateListener.tsx
  • apps/desktop/src/renderer/screens/main/index.tsx
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/index.ts
  • 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
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/screens/main/index.tsx (1)
apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx (1)
  • useUpdateListener (10-45)
apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx (4)
apps/desktop/src/renderer/components/UpdateToast/index.ts (1)
  • UpdateToast (1-1)
apps/desktop/src/main/lib/auto-updater.ts (1)
  • installUpdate (21-23)
packages/ui/src/components/ui/sonner.tsx (1)
  • toast (40-40)
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 (16-41)
apps/desktop/src/lib/trpc/routers/auto-update/index.ts (2)
apps/desktop/src/main/lib/auto-updater.ts (3)
  • UpdateDownloadedEvent (12-15)
  • autoUpdateEmitter (17-17)
  • installUpdate (21-23)
apps/desktop/src/lib/trpc/index.ts (1)
  • router (47-47)
⏰ 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 (4)
apps/desktop/src/renderer/screens/main/index.tsx (1)

8-8: LGTM! Clean integration of update notification system.

The useUpdateListener hook is properly imported and invoked alongside the existing useAgentHookListener, following the established pattern in this component. The implementation correctly uses tRPC for IPC communication as required by the coding guidelines.

Also applies to: 67-67

apps/desktop/src/renderer/components/UpdateToast/index.ts (1)

1-2: LGTM! Proper barrel export pattern.

The index file correctly re-exports both the component and hook, following the component organization guidelines.

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

4-4: LGTM! Standard router integration.

The auto-update router is properly imported and registered following the existing pattern used for other routers in the application.

Also applies to: 30-30

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

1-41: LGTM! Excellent implementation following tRPC-electron best practices.

The router correctly uses the observable pattern from @trpc/server/observable as required by the coding guidelines for tRPC subscriptions with trpc-electron. The implementation includes proper event listener cleanup and maintains type safety throughout.

Key strengths:

  • Observable pattern correctly implemented (lines 18-36)
  • Event listeners properly removed in cleanup function (lines 31-34)
  • Type-safe event emission with AutoUpdateEvent discriminated union
  • Clean separation of concerns

As per coding guidelines: "For tRPC subscriptions with trpc-electron, ALWAYS use the observable pattern from @trpc/server/observable instead of async generators."

Comment thread apps/desktop/src/main/lib/auto-updater.ts Outdated
Comment on lines +17 to +26
const openUrl = trpc.external.openUrl.useMutation();
const installUpdate = trpc.autoUpdate.installUpdate.useMutation();

const handleSeeChanges = () => {
openUrl.mutate(releaseUrl);
};

const handleRestart = () => {
installUpdate.mutate();
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Consider adding error handling for the openUrl mutation.

The openUrl mutation (line 21) has no error handling. If opening the release URL fails, the user receives no feedback. While the installUpdate mutation is less critical (as the app will quit if successful), it would improve user experience to handle potential failures for openUrl.

🔎 Proposed error handling
 export function UpdateToast({
 	toastId,
 	version,
 	releaseUrl,
 }: UpdateToastProps) {
 	const openUrl = trpc.external.openUrl.useMutation();
 	const installUpdate = trpc.autoUpdate.installUpdate.useMutation();

 	const handleSeeChanges = () => {
-		openUrl.mutate(releaseUrl);
+		openUrl.mutate(releaseUrl, {
+			onError: (error) => {
+				console.error("Failed to open release URL:", error);
+				toast.error("Failed to open release notes");
+			},
+		});
 	};

 	const handleRestart = () => {
 		installUpdate.mutate();
 	};

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx around lines
17 to 26, the openUrl mutation is called without any error handling; update the
handler to handle success and failure by using the mutation's onError/onSuccess
callbacks or awaiting the mutateAsync promise, show user feedback on failure
(e.g., a toast or inline error text) and disable the button while loading to
prevent duplicate clicks; ensure errors are logged (console or telemetry) and
the UI re-enables the control after the operation completes.

Comment on lines +50 to +57
<button
type="button"
onClick={handleDismiss}
className="absolute -top-2 -left-2 size-5 flex items-center justify-center rounded-full bg-background border border-border text-muted-foreground hover:text-foreground transition-colors"
aria-label="Dismiss"
>
<HiMiniXMark className="size-3" />
</button>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for similar absolute positioning patterns in UI components
rg -n "absolute.*-top.*-left" --type tsx -C 2

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Read the UpdateToast.tsx file to understand the context
cat -n apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx

Repository: superset-sh/superset

Length of output: 2071


🏁 Script executed:

#!/bin/bash
# Check what toast library is being used and search for its usage patterns
rg -n "toast|Toast" apps/desktop/src/renderer/components/ --type tsx -l | head -20

Repository: superset-sh/superset

Length of output: 91


🏁 Script executed:

#!/bin/bash
# Search for dismiss button patterns (tsx type not recognized, try without -t)
rg -n "dismiss|Dismiss" --type ts -A 2 -B 2 | head -50

Repository: superset-sh/superset

Length of output: 4159


🏁 Script executed:

#!/bin/bash
# Search for absolute positioning patterns
rg -n "absolute.*-top" -A 1 -B 1 | head -50

Repository: superset-sh/superset

Length of output: 965


🏁 Script executed:

#!/bin/bash
# Search for error handling on mutations
rg -n "\.mutate\(" apps/desktop/src/renderer --type ts -A 2 | head -80

Repository: superset-sh/superset

Length of output: 6002


🏁 Script executed:

#!/bin/bash
# Search for sonner/toast imports and usage
rg -n "sonner|toast" apps/desktop/src/renderer --type ts -B 2 -A 2 | head -100

Repository: superset-sh/superset

Length of output: 11435


🏁 Script executed:

#!/bin/bash
# Check if Button component has dismiss functionality or styling patterns
rg -n "Button" apps/desktop/src/renderer/components/UpdateToast --type ts -B 2 -A 2

Repository: superset-sh/superset

Length of output: 1361


🏁 Script executed:

#!/bin/bash
# Look for error handling patterns with onError in mutations
rg -n "onError|\.catch" apps/desktop/src/renderer/components --type ts -B 2 -A 2

Repository: superset-sh/superset

Length of output: 46


Add error handling to mutations in UpdateToast component.

The openUrl.mutate() call on line 21 lacks error handling. If opening the release URL fails, the user receives no feedback. Add error handling using either an onError callback in the mutation options or by wrapping with toast.promise() like other parts of the codebase do.

🤖 Prompt for AI Agents
In apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx around lines
21 and 50-57, the openUrl.mutate() call is invoked without error handling so
failures produce no user feedback; update the mutation invocation to include
error handling by either passing an onError callback in the mutate options or
wrapping the call with toast.promise like other code paths: supply success and
error messages for the toast, handle rejected promise paths to show an error
toast, and ensure any cleanup (closing the toast UI) still occurs on error so
the user sees appropriate feedback.

Kitenite and others added 15 commits December 25, 2025 09:28
- Fix toast layout with proper container styling and min-width
- Use unstyled toast to prevent sonner wrapper interference
- Add onDismiss callback to allow re-triggering toast in dev
- Add Dev menu (dev-only) with "Simulate Update Available" option

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change "See changes" button to open the main releases page instead of
a specific release tag URL.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add AUTO_UPDATE_STATUS constants (idle/checking/downloading/ready/error)
- Show downloading state with version info in toast
- Replace X button with "Later" button for dismissing
- Change "Restart" to "Install" for clearer action
- Add dismiss functionality that hides toast for current session
- Emit current status immediately on subscription
- Add separate Dev menu items for testing both states

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Avi Peltz <AviPeltz@users.noreply.github.com>
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: 1

♻️ Duplicate comments (1)
apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx (1)

25-27: Add error handling for the openUrl mutation.

The openUrl.mutate() call lacks error handling. If opening the release URL fails, users receive no feedback. This was flagged in a previous review but appears unaddressed.

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

7-11: Consider using the AutoUpdateStatus type for stronger type safety.

The status prop is typed as a string literal union "downloading" | "ready", but the component compares against AUTO_UPDATE_STATUS.DOWNLOADING and AUTO_UPDATE_STATUS.READY. While this works, using the exported type would be more maintainable:

🔎 Proposed improvement
+import type { AutoUpdateStatus } from "shared/auto-update";
+
 interface UpdateToastProps {
 	toastId: string | number;
-	status: "downloading" | "ready";
+	status: Extract<AutoUpdateStatus, "downloading" | "ready">;
 	version?: string;
 }
apps/desktop/src/main/lib/auto-updater.ts (1)

18-18: Consider typing the EventEmitter for better type safety.

The autoUpdateEmitter is an untyped EventEmitter. TypeScript's typed event emitter pattern would provide compile-time safety for event names and payloads.

🔎 Proposed typed EventEmitter
+import { EventEmitter } from "node:events";
+import type TypedEmitter from "typed-emitter";
+
+type AutoUpdateEvents = {
+	"status-changed": (event: AutoUpdateStatusEvent) => void;
+};
+
-export const autoUpdateEmitter = new EventEmitter();
+export const autoUpdateEmitter = new EventEmitter() as TypedEmitter<AutoUpdateEvents>;

Alternatively, define a simple typed wrapper if you prefer not to add a dependency.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ed768e9 and 1d6bb98.

📒 Files selected for processing (9)
  • apps/desktop/src/lib/trpc/routers/auto-update/index.ts
  • apps/desktop/src/main/lib/auto-updater.ts
  • apps/desktop/src/main/lib/local-db/index.ts
  • apps/desktop/src/main/lib/menu.ts
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx
  • apps/desktop/src/renderer/globals.css
  • apps/desktop/src/shared/auto-update.ts
  • apps/desktop/src/shared/constants.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/shared/constants.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/desktop/src/main/lib/local-db/index.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/lib/trpc/routers/auto-update/index.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/shared/auto-update.ts
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/main/lib/auto-updater.ts
  • apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx
  • apps/desktop/src/main/lib/menu.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/shared/auto-update.ts
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/main/lib/auto-updater.ts
  • apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx
  • apps/desktop/src/main/lib/menu.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/shared/auto-update.ts
  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/main/lib/auto-updater.ts
  • apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx
  • apps/desktop/src/main/lib/menu.ts
**/{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
  • apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx
**/{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/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.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/UpdateToast.tsx
  • apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.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/UpdateToast.tsx
  • apps/desktop/src/main/lib/auto-updater.ts
  • apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx
  • apps/desktop/src/main/lib/menu.ts
**/*.{tsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx
  • apps/desktop/src/renderer/globals.css
  • apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.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/renderer/components/UpdateToast/useUpdateListener.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
  • apps/desktop/src/main/lib/menu.ts
🧬 Code graph analysis (2)
apps/desktop/src/renderer/components/UpdateToast/useUpdateListener.tsx (2)
apps/desktop/src/shared/auto-update.ts (1)
  • AUTO_UPDATE_STATUS (1-7)
apps/desktop/src/renderer/components/UpdateToast/UpdateToast.tsx (1)
  • UpdateToast (13-82)
apps/desktop/src/main/lib/menu.ts (3)
apps/api/src/env.ts (1)
  • env (4-33)
apps/web/src/env.ts (1)
  • env (5-53)
apps/desktop/src/main/lib/auto-updater.ts (2)
  • simulateDownloading (120-124)
  • simulateUpdateReady (114-118)
⏰ 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 (5)
apps/desktop/src/renderer/globals.css (1)

254-258: LGTM!

The CSS selector correctly scopes the hover animation fix to only update toasts using the :has() pseudo-class, which is well-supported in Electron's Chromium engine. This ensures other Sonner toasts retain their default behavior.

apps/desktop/src/shared/auto-update.ts (1)

1-12: LGTM!

Clean implementation of shared constants using as const for type safety. The derived AutoUpdateStatus type pattern is idiomatic TypeScript and ensures type safety across the codebase.

apps/desktop/src/main/lib/menu.ts (1)

80-95: LGTM!

The Dev menu is properly gated to development mode only, and the simulate functions provide useful testing capabilities for the new update toast flow. The double-guard (menu visibility + function-internal check) is appropriately defensive.

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

7-48: LGTM!

The hook properly manages toast lifecycle:

  • Clears toastIdRef when dismissing (line 21), addressing the previous review concern about blocking subsequent notifications
  • Dismisses existing toasts before showing new ones to prevent duplicates
  • Correctly differentiates between dismissal states (IDLE/CHECKING/ERROR) and display states (DOWNLOADING/READY)
apps/desktop/src/main/lib/auto-updater.ts (1)

126-188: LGTM!

The setupAutoUpdater function is well-structured:

  • Proper environment/platform guards
  • All relevant electron-updater events are handled with appropriate status emissions
  • interval.unref() correctly prevents the timer from blocking app exit
  • Initial update check handles both ready and not-ready app states

Comment thread apps/desktop/src/main/lib/auto-updater.ts
Kitenite and others added 2 commits December 25, 2025 21:53
When checkForUpdatesInteractive finds no updates, emit IDLE status
before showing the dialog. This prevents currentStatus from being
stuck at CHECKING, which could cause inconsistent UI state.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
In dev mode, clicking Install on a simulated update would call
quitAndInstall with no real update downloaded. Now it just logs
a message and dismisses the toast.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 1

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

127-189: LGTM! Auto-updater lifecycle properly wired to event system.

All updater lifecycle events now emit status updates instead of showing dialogs, successfully decoupling the update mechanism from the UI. The event handlers are comprehensive and include appropriate version information where available.

Optional enhancement: The download-progress handler (lines 163-167) currently only logs progress. If you want to show a progress bar in the UpdateToast, consider including progress percentage in the status event:

🔎 Proposed enhancement to include download progress

Update the AutoUpdateStatusEvent interface to include progress:

 export interface AutoUpdateStatusEvent {
   status: AutoUpdateStatus;
   version?: string;
   error?: string;
+  progress?: number; // 0-100 percentage
 }

Then update the emitStatus function signature and the progress handler:

 autoUpdater.on("download-progress", (progress) => {
   console.info(
     `[auto-updater] Download progress: ${progress.percent.toFixed(1)}%`,
   );
+  autoUpdateEmitter.emit("status-changed", { 
+    status: currentStatus, 
+    version: currentVersion,
+    progress: progress.percent 
+  });
 });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d6bb98 and e316d37.

📒 Files selected for processing (1)
  • apps/desktop/src/main/lib/auto-updater.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • 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/main/lib/auto-updater.ts
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/main/lib/auto-updater.ts
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
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/main/lib/auto-updater.ts
⏰ 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 (8)
apps/desktop/src/main/lib/auto-updater.ts (8)

1-16: LGTM! Clean event-driven architecture foundation.

The imports and interface definition properly support the new event-driven update system. The AutoUpdateStatusEvent interface is well-typed with appropriate optional fields for version and error information.


24-37: LGTM! Status emission logic is sound.

The emitStatus function correctly updates internal state and guards against re-emitting READY status for dismissed updates. The event emission pattern is clean and will integrate well with the tRPC subscription layer.


39-44: LGTM! Status query correctly respects dismissal state.

The getUpdateStatus function appropriately returns IDLE when an update has been dismissed, maintaining consistency with the event emission logic.


50-53: LGTM! Dismissal logic is clear and correct.

The function properly sets the dismissal flag and immediately notifies subscribers with IDLE status to hide the update toast.


55-65: LGTM! Background update check properly emits status events.

The function correctly resets dismissal state for new checks and emits appropriate status events for checking and error states, enabling the UI to track update progress.


67-113: LGTM! Interactive check properly emits IDLE status.

The function now correctly emits IDLE status at line 95 when no updates are available, addressing the previous review concern. The combination of status events (for toast UI) and dialogs (for interactive feedback) is appropriate.


115-125: LGTM! Development simulation helpers are well-guarded.

The simulation functions are appropriately guarded for development-only use and follow the same dismissal reset pattern as the real update checks. The test version "99.0.0-test" clearly indicates simulated state.


46-48: The isForceRunAfter parameter in quitAndInstall(false, true) does not control restart behavior.

The parameters are (isSilent, isForceRunAfter). Here, false correctly creates a non-silent installation. However, true for isForceRunAfter is intended to force the app to run after a silent install completes—but since isSilent=false, this parameter is ignored per the electron-updater documentation. The current call will install and restart as expected when users click "Restart" in UpdateToast, but the second parameter serves no purpose here and could be removed for clarity.

Likely an incorrect or invalid review comment.

Comment on lines +18 to +22
export const autoUpdateEmitter = new EventEmitter();

let currentStatus: AutoUpdateStatus = AUTO_UPDATE_STATUS.IDLE;
let currentVersion: string | undefined;
let isDismissed = false;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Consider version-specific dismissal tracking.

The isDismissed boolean flag applies to any READY status, regardless of version. If a user dismisses update v1.0.0 and a newer v1.1.0 is downloaded before the next automatic check (4-hour interval), the notification for the newer version will be suppressed.

While unlikely in production (updates rarely release within a 4-hour window), tracking dismissed versions in a Set<string> would be more robust and ensure users are notified about each distinct version.

🔎 Proposed refactor to track dismissed versions
-let isDismissed = false;
+const dismissedVersions = new Set<string>();

 function emitStatus(
   status: AutoUpdateStatus,
   version?: string,
   error?: string,
 ): void {
   currentStatus = status;
   currentVersion = version;

-  if (isDismissed && status === AUTO_UPDATE_STATUS.READY) {
+  if (version && dismissedVersions.has(version) && status === AUTO_UPDATE_STATUS.READY) {
     return;
   }

   autoUpdateEmitter.emit("status-changed", { status, version, error });
 }

 export function getUpdateStatus(): AutoUpdateStatusEvent {
-  if (isDismissed && currentStatus === AUTO_UPDATE_STATUS.READY) {
+  if (currentVersion && dismissedVersions.has(currentVersion) && currentStatus === AUTO_UPDATE_STATUS.READY) {
     return { status: AUTO_UPDATE_STATUS.IDLE };
   }
   return { status: currentStatus, version: currentVersion };
 }

 export function dismissUpdate(): void {
-  isDismissed = true;
+  if (currentVersion) {
+    dismissedVersions.add(currentVersion);
+  }
   autoUpdateEmitter.emit("status-changed", { status: AUTO_UPDATE_STATUS.IDLE });
 }

Then remove the isDismissed = false reset lines from checkForUpdates (line 59), checkForUpdatesInteractive (line 85), and simulation functions (lines 117, 123).

Committable suggestion skipped: line range outside the PR's diff.

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 (2)
apps/desktop/src/main/lib/auto-updater.ts (2)

18-22: Consider typing the EventEmitter for better type safety.

The autoUpdateEmitter lacks type annotations, so subscribers won't get compile-time validation of event names or payload shapes.

🔎 Proposed typed EventEmitter
+import type { TypedEventEmitter } from "node:events";
+
+type AutoUpdateEvents = {
+  "status-changed": [AutoUpdateStatusEvent];
+};
+
-export const autoUpdateEmitter = new EventEmitter();
+export const autoUpdateEmitter =
+  new EventEmitter() as TypedEventEmitter<AutoUpdateEvents>;

Alternatively, if the Node.js version doesn't support TypedEventEmitter, you can use a third-party library like typed-emitter or define a custom typed wrapper.


55-58: Internal state inconsistency when dismissing.

dismissUpdate emits IDLE directly but doesn't update currentStatus via emitStatus. While getUpdateStatus handles this correctly due to the isDismissed check, the internal state diverges from what's emitted. This could confuse future maintainers or cause issues if the logic evolves.

🔎 Proposed fix using emitStatus
 export function dismissUpdate(): void {
 	isDismissed = true;
-	autoUpdateEmitter.emit("status-changed", { status: AUTO_UPDATE_STATUS.IDLE });
+	currentStatus = AUTO_UPDATE_STATUS.IDLE;
+	currentVersion = undefined;
+	autoUpdateEmitter.emit("status-changed", { status: AUTO_UPDATE_STATUS.IDLE });
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e316d37 and 9ba59da.

📒 Files selected for processing (1)
  • apps/desktop/src/main/lib/auto-updater.ts
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

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

Files:

  • 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/main/lib/auto-updater.ts
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/main/lib/auto-updater.ts
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
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/main/lib/auto-updater.ts
🧬 Code graph analysis (1)
apps/desktop/src/main/lib/auto-updater.ts (3)
apps/desktop/src/shared/auto-update.ts (2)
  • AutoUpdateStatus (9-10)
  • AUTO_UPDATE_STATUS (1-7)
apps/desktop/src/main/env.main.ts (1)
  • env (12-44)
apps/desktop/src/shared/constants.ts (1)
  • 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 (8)
apps/desktop/src/main/lib/auto-updater.ts (8)

1-16: LGTM!

Imports and type definitions are well-structured. Good use of the shared AUTO_UPDATE_STATUS constants and AutoUpdateStatus type for consistency across the codebase.


24-37: LGTM!

The emitStatus function correctly centralizes status management and suppresses redundant READY notifications when dismissed. The guard at lines 32-34 ensures UI consistency.


39-53: LGTM!

Both functions are well-implemented:

  • getUpdateStatus correctly masks dismissed READY states as IDLE
  • installUpdate safely handles dev mode and uses appropriate quitAndInstall parameters (isSilent: false to show installer, isForceRunAfter: true to restart)

60-70: LGTM!

The function correctly guards against dev mode and non-macOS platforms, resets dismissal state, and properly handles errors by emitting an ERROR status with the error message.


72-118: LGTM!

Comprehensive interactive check with proper user feedback. The IDLE status emission at line 100 correctly addresses the previous review concern about status consistency when no updates are available.


120-130: LGTM!

Development-only simulation functions with proper environment guards. These enable testing the update toast UI without requiring actual update events.


168-172: Download progress is logged but not emitted to the UI.

The download-progress handler only logs but doesn't emit the progress percentage. If the UpdateToast component or future UI needs to display a progress indicator, this information isn't available via the status system.

This may be intentional if the UI only shows a static "downloading" state, but worth confirming.


174-194: LGTM!

The update-downloaded handler correctly emits the READY status with version info. The interval setup with .unref() ensures the timer won't prevent clean app shutdown, and the app-ready handling covers both immediate and deferred initialization paths.

- Add error status handling to UpdateToast component
- Show "Update failed" with error message in toast UI
- Add simulateError function for dev testing
- Add "Simulate Update Error" option to Dev menu

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Kitenite Kitenite merged commit 82d5c63 into main Dec 26, 2025
4 of 5 checks passed
@Kitenite Kitenite deleted the make-auto-update-less-intrusive branch December 26, 2025 06:20
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

Service Status
Neon Database (Neon) ⚠️

Thank you for your contribution! 🎉


Preview resources have been processed for cleanup

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