diff --git a/auto-claude-ui/src/renderer/App.tsx b/auto-claude-ui/src/renderer/App.tsx index 4cec2f30..d1d0a727 100644 --- a/auto-claude-ui/src/renderer/App.tsx +++ b/auto-claude-ui/src/renderer/App.tsx @@ -114,6 +114,20 @@ export function App() { }; }, []); + // Listen for app updates - auto-open settings to 'updates' section when update is ready + useEffect(() => { + // When an update is downloaded and ready to install, open settings to updates section + const cleanupDownloaded = window.electronAPI.onAppUpdateDownloaded(() => { + console.log('[App] Update downloaded, opening settings to updates section'); + setSettingsInitialSection('updates'); + setIsSettingsDialogOpen(true); + }); + + return () => { + cleanupDownloaded(); + }; + }, []); + // Check if selected project needs initialization (e.g., .auto-claude folder was deleted) useEffect(() => { if (selectedProject && !selectedProject.autoBuildPath && skippedInitProjectId !== selectedProject.id) { diff --git a/auto-claude-ui/src/renderer/components/settings/AdvancedSettings.tsx b/auto-claude-ui/src/renderer/components/settings/AdvancedSettings.tsx index 2c5722f3..ae08027c 100644 --- a/auto-claude-ui/src/renderer/components/settings/AdvancedSettings.tsx +++ b/auto-claude-ui/src/renderer/components/settings/AdvancedSettings.tsx @@ -5,7 +5,9 @@ import { AlertCircle, CloudDownload, Loader2, - ExternalLink + ExternalLink, + Download, + Sparkles } from 'lucide-react'; import { Button } from '../ui/button'; import { Label } from '../ui/label'; @@ -13,7 +15,13 @@ import { Switch } from '../ui/switch'; import { Progress } from '../ui/progress'; import { cn } from '../../lib/utils'; import { SettingsSection } from './SettingsSection'; -import type { AppSettings, AutoBuildSourceUpdateCheck, AutoBuildSourceUpdateProgress } from '../../../shared/types'; +import type { + AppSettings, + AutoBuildSourceUpdateCheck, + AutoBuildSourceUpdateProgress, + AppUpdateAvailableEvent, + AppUpdateProgress +} from '../../../shared/types'; /** * Simple markdown renderer for release notes @@ -69,14 +77,22 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version const [isDownloadingUpdate, setIsDownloadingUpdate] = useState(false); const [downloadProgress, setDownloadProgress] = useState(null); + // Electron app update state + const [appUpdateInfo, setAppUpdateInfo] = useState(null); + const [isCheckingAppUpdate, setIsCheckingAppUpdate] = useState(false); + const [isDownloadingAppUpdate, setIsDownloadingAppUpdate] = useState(false); + const [appDownloadProgress, setAppDownloadProgress] = useState(null); + const [isAppUpdateDownloaded, setIsAppUpdateDownloaded] = useState(false); + // Check for updates on mount useEffect(() => { if (section === 'updates') { checkForSourceUpdates(); + checkForAppUpdates(); } }, [section]); - // Listen for download progress + // Listen for source download progress useEffect(() => { const cleanup = window.electronAPI.onAutoBuildSourceUpdateProgress((progress) => { setDownloadProgress(progress); @@ -91,6 +107,62 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version return cleanup; }, []); + // Listen for app update events + useEffect(() => { + const cleanupAvailable = window.electronAPI.onAppUpdateAvailable((info) => { + setAppUpdateInfo(info); + setIsCheckingAppUpdate(false); + }); + + const cleanupDownloaded = window.electronAPI.onAppUpdateDownloaded((info) => { + setAppUpdateInfo(info); + setIsDownloadingAppUpdate(false); + setIsAppUpdateDownloaded(true); + setAppDownloadProgress(null); + }); + + const cleanupProgress = window.electronAPI.onAppUpdateProgress((progress) => { + setAppDownloadProgress(progress); + }); + + return () => { + cleanupAvailable(); + cleanupDownloaded(); + cleanupProgress(); + }; + }, []); + + const checkForAppUpdates = async () => { + setIsCheckingAppUpdate(true); + try { + const result = await window.electronAPI.checkAppUpdate(); + if (result.success && result.data) { + setAppUpdateInfo(result.data); + } else { + // No update available + setAppUpdateInfo(null); + } + } catch (err) { + console.error('Failed to check for app updates:', err); + } finally { + setIsCheckingAppUpdate(false); + } + }; + + const handleDownloadAppUpdate = async () => { + setIsDownloadingAppUpdate(true); + try { + await window.electronAPI.downloadAppUpdate(); + } catch (err) { + console.error('Failed to download app update:', err); + setIsDownloadingAppUpdate(false); + } + }; + + const handleInstallAppUpdate = () => { + window.electronAPI.installAppUpdate(); + }; + const checkForSourceUpdates = async () => { setIsCheckingSourceUpdate(true); try { @@ -118,6 +190,97 @@ export function AdvancedSettings({ settings, onSettingsChange, section, version description="Manage Auto Claude updates" >
+ {/* Electron App Update Section */} + {(appUpdateInfo || isAppUpdateDownloaded) && ( +
+
+ +

App Update Ready

+
+ +
+
+

+ New Version +

+

+ {appUpdateInfo?.version || 'Unknown'} +

+ {appUpdateInfo?.releaseDate && ( +

+ Released {new Date(appUpdateInfo.releaseDate).toLocaleDateString()} +

+ )} +
+ {isAppUpdateDownloaded ? ( + + ) : isDownloadingAppUpdate ? ( + + ) : ( + + )} +
+ + {/* Release Notes */} + {appUpdateInfo?.releaseNotes && ( +
+ +
+ )} + + {/* Download Progress */} + {isDownloadingAppUpdate && appDownloadProgress && ( +
+
+ Downloading... + + {Math.round(appDownloadProgress.percent)}% + +
+ +

+ {(appDownloadProgress.transferred / 1024 / 1024).toFixed(2)} MB / {(appDownloadProgress.total / 1024 / 1024).toFixed(2)} MB +

+
+ )} + + {/* Downloaded Success */} + {isAppUpdateDownloaded && ( +
+ + Update downloaded! Click Install to restart and apply the update. +
+ )} + + {/* Action Buttons */} +
+ {isAppUpdateDownloaded ? ( + + ) : ( + + )} +
+
+ )} + {/* Unified Version Display with Update Check */}
diff --git a/auto-claude-ui/src/renderer/lib/mocks/settings-mock.ts b/auto-claude-ui/src/renderer/lib/mocks/settings-mock.ts index d22cca9f..6bc890d0 100644 --- a/auto-claude-ui/src/renderer/lib/mocks/settings-mock.ts +++ b/auto-claude-ui/src/renderer/lib/mocks/settings-mock.ts @@ -14,5 +14,15 @@ export const settingsMock = { saveSettings: async () => ({ success: true }), // App Info - getAppVersion: async () => '0.1.0-browser' + getAppVersion: async () => '0.1.0-browser', + + // App Update Operations (mock - no updates in browser mode) + checkAppUpdate: async () => ({ success: true, data: null }), + downloadAppUpdate: async () => ({ success: true }), + installAppUpdate: () => { console.log('[browser-mock] installAppUpdate called'); }, + + // App Update Event Listeners (no-op in browser mode) + onAppUpdateAvailable: () => () => {}, + onAppUpdateDownloaded: () => () => {}, + onAppUpdateProgress: () => () => {} }; diff --git a/auto-claude-ui/src/shared/types/ipc.ts b/auto-claude-ui/src/shared/types/ipc.ts index 3d218f3f..2c82e633 100644 --- a/auto-claude-ui/src/shared/types/ipc.ts +++ b/auto-claude-ui/src/shared/types/ipc.ts @@ -55,6 +55,7 @@ import type { ClaudeUsageSnapshot } from './agent'; import type { AppSettings, SourceEnvConfig, SourceEnvCheckResult, AutoBuildSourceUpdateCheck, AutoBuildSourceUpdateProgress } from './settings'; +import type { AppUpdateInfo, AppUpdateProgress, AppUpdateAvailableEvent, AppUpdateDownloadedEvent } from './app-update'; import type { ChangelogTask, TaskSpecContent, @@ -388,6 +389,22 @@ export interface ElectronAPI { callback: (progress: AutoBuildSourceUpdateProgress) => void ) => () => void; + // Electron app update operations + checkAppUpdate: () => Promise>; + downloadAppUpdate: () => Promise; + installAppUpdate: () => void; + + // Electron app update event listeners + onAppUpdateAvailable: ( + callback: (info: AppUpdateAvailableEvent) => void + ) => () => void; + onAppUpdateDownloaded: ( + callback: (info: AppUpdateDownloadedEvent) => void + ) => () => void; + onAppUpdateProgress: ( + callback: (progress: AppUpdateProgress) => void + ) => () => void; + // Shell operations openExternal: (url: string) => Promise;