feat: Auto-open settings to updates section when app update is ready

When an app update is downloaded and ready to install, automatically opens the Settings dialog to the Updates section so users can easily install the update.

Changes:

- App.tsx: Listen for onAppUpdateDownloaded event and open settings

- AdvancedSettings.tsx: Add Electron app update UI with download/install buttons

- ipc.ts: Add app update methods to ElectronAPI interface

- settings-mock.ts: Add browser mocks for app update methods
This commit is contained in:
AndyMik90
2025-12-18 10:59:25 +01:00
parent 7f3cd5969d
commit 1d0566fce7
4 changed files with 208 additions and 4 deletions
+14
View File
@@ -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) {
@@ -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<AutoBuildSourceUpdateProgress | null>(null);
// Electron app update state
const [appUpdateInfo, setAppUpdateInfo] = useState<AppUpdateAvailableEvent | null>(null);
const [isCheckingAppUpdate, setIsCheckingAppUpdate] = useState(false);
const [isDownloadingAppUpdate, setIsDownloadingAppUpdate] = useState(false);
const [appDownloadProgress, setAppDownloadProgress] = useState<AppUpdateProgress | null>(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"
>
<div className="space-y-6">
{/* Electron App Update Section */}
{(appUpdateInfo || isAppUpdateDownloaded) && (
<div className="rounded-lg border-2 border-info/50 bg-info/5 p-5 space-y-4">
<div className="flex items-center gap-2 text-info">
<Sparkles className="h-5 w-5" />
<h3 className="font-semibold">App Update Ready</h3>
</div>
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-muted-foreground uppercase tracking-wider mb-1">
New Version
</p>
<p className="text-base font-medium text-foreground">
{appUpdateInfo?.version || 'Unknown'}
</p>
{appUpdateInfo?.releaseDate && (
<p className="text-xs text-muted-foreground mt-1">
Released {new Date(appUpdateInfo.releaseDate).toLocaleDateString()}
</p>
)}
</div>
{isAppUpdateDownloaded ? (
<CheckCircle2 className="h-6 w-6 text-success" />
) : isDownloadingAppUpdate ? (
<RefreshCw className="h-6 w-6 animate-spin text-info" />
) : (
<Download className="h-6 w-6 text-info" />
)}
</div>
{/* Release Notes */}
{appUpdateInfo?.releaseNotes && (
<div className="bg-background rounded-lg p-4 max-h-48 overflow-y-auto border border-border/50">
<ReleaseNotesRenderer markdown={appUpdateInfo.releaseNotes} />
</div>
)}
{/* Download Progress */}
{isDownloadingAppUpdate && appDownloadProgress && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">Downloading...</span>
<span className="text-foreground font-medium">
{Math.round(appDownloadProgress.percent)}%
</span>
</div>
<Progress value={appDownloadProgress.percent} className="h-2" />
<p className="text-xs text-muted-foreground text-right">
{(appDownloadProgress.transferred / 1024 / 1024).toFixed(2)} MB / {(appDownloadProgress.total / 1024 / 1024).toFixed(2)} MB
</p>
</div>
)}
{/* Downloaded Success */}
{isAppUpdateDownloaded && (
<div className="flex items-center gap-3 text-sm text-success bg-success/10 border border-success/30 rounded-lg p-3">
<CheckCircle2 className="h-5 w-5 shrink-0" />
<span>Update downloaded! Click Install to restart and apply the update.</span>
</div>
)}
{/* Action Buttons */}
<div className="flex gap-3">
{isAppUpdateDownloaded ? (
<Button onClick={handleInstallAppUpdate}>
<RefreshCw className="mr-2 h-4 w-4" />
Install and Restart
</Button>
) : (
<Button
onClick={handleDownloadAppUpdate}
disabled={isDownloadingAppUpdate}
>
{isDownloadingAppUpdate ? (
<>
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
Downloading...
</>
) : (
<>
<Download className="mr-2 h-4 w-4" />
Download Update
</>
)}
</Button>
)}
</div>
</div>
)}
{/* Unified Version Display with Update Check */}
<div className="rounded-lg border border-border bg-muted/50 p-5 space-y-4">
<div className="flex items-center justify-between">
@@ -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: () => () => {}
};
+17
View File
@@ -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<IPCResult<AppUpdateInfo | null>>;
downloadAppUpdate: () => Promise<IPCResult>;
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<void>;