diff --git a/apps/frontend/src/renderer/components/Sidebar.tsx b/apps/frontend/src/renderer/components/Sidebar.tsx
index 5114b549..b7a55588 100644
--- a/apps/frontend/src/renderer/components/Sidebar.tsx
+++ b/apps/frontend/src/renderer/components/Sidebar.tsx
@@ -50,6 +50,7 @@ import { AddProjectModal } from './AddProjectModal';
import { GitSetupModal } from './GitSetupModal';
import { RateLimitIndicator } from './RateLimitIndicator';
import { ClaudeCodeStatusBadge } from './ClaudeCodeStatusBadge';
+import { UpdateBanner } from './UpdateBanner';
import type { Project, AutoBuildVersionInfo, GitStatus, ProjectEnvConfig } from '../../shared/types';
export type SidebarView = 'kanban' | 'terminals' | 'roadmap' | 'context' | 'ideation' | 'github-issues' | 'gitlab-issues' | 'github-prs' | 'gitlab-merge-requests' | 'changelog' | 'insights' | 'worktrees' | 'agent-tools';
@@ -328,6 +329,9 @@ export function Sidebar({
{/* Rate Limit Indicator - shows when Claude is rate limited */}
+ {/* Update Banner - shows when app update is available */}
+
+
{/* Bottom section with Settings, Help, and New Task */}
{/* Claude Code Status Badge */}
diff --git a/apps/frontend/src/renderer/components/UpdateBanner.tsx b/apps/frontend/src/renderer/components/UpdateBanner.tsx
new file mode 100644
index 00000000..c7b2ad23
--- /dev/null
+++ b/apps/frontend/src/renderer/components/UpdateBanner.tsx
@@ -0,0 +1,261 @@
+import { useState, useEffect, useCallback, useRef } from "react";
+import { useTranslation } from "react-i18next";
+import { Download, X, RefreshCw } from "lucide-react";
+import { Button } from "./ui/button";
+import { cn } from "../lib/utils";
+import type { AppUpdateAvailableEvent, AppUpdateProgress } from "../../shared/types";
+
+// Poll for updates every 5 minutes
+const UPDATE_CHECK_INTERVAL_MS = 5 * 60 * 1000;
+
+interface UpdateBannerProps {
+ className?: string;
+}
+
+/**
+ * Inline update notification banner for the sidebar.
+ * Shows when a new application update is available and provides
+ * quick access to download/install or dismiss.
+ */
+export function UpdateBanner({ className }: UpdateBannerProps) {
+ const { t } = useTranslation(["navigation", "common"]);
+ const [updateInfo, setUpdateInfo] = useState
(null);
+ const [isDismissed, setIsDismissed] = useState(false);
+ const [isDownloading, setIsDownloading] = useState(false);
+ const [downloadProgress, setDownloadProgress] = useState(null);
+ const [isDownloaded, setIsDownloaded] = useState(false);
+ const [downloadError, setDownloadError] = useState(null);
+
+ // Ref to track current version for stable callbacks
+ const currentVersionRef = useRef(null);
+
+ // Check for updates
+ const checkForUpdate = useCallback(async () => {
+ try {
+ if (!window.electronAPI?.checkAppUpdate) {
+ return;
+ }
+
+ const result = await window.electronAPI.checkAppUpdate();
+ if (result.success && result.data) {
+ const newVersion = result.data.version;
+ // New update available - show banner (unless same version already dismissed)
+ if (currentVersionRef.current !== newVersion) {
+ setIsDismissed(false);
+ // Reset downloaded state when a newer version is found
+ setIsDownloaded(false);
+ currentVersionRef.current = newVersion;
+ }
+ setUpdateInfo({
+ version: newVersion,
+ releaseNotes: result.data.releaseNotes,
+ releaseDate: result.data.releaseDate,
+ });
+ }
+ } catch (err) {
+ // Silent failure - update check is non-critical
+ }
+ }, []);
+
+ // Check if there's already a downloaded update on mount
+ useEffect(() => {
+ const checkDownloaded = async () => {
+ try {
+ if (!window.electronAPI?.getDownloadedAppUpdate) {
+ return;
+ }
+ const result = await window.electronAPI.getDownloadedAppUpdate();
+ if (result.success && result.data) {
+ currentVersionRef.current = result.data.version;
+ setUpdateInfo({
+ version: result.data.version,
+ releaseNotes: result.data.releaseNotes,
+ releaseDate: result.data.releaseDate,
+ });
+ setIsDownloaded(true);
+ }
+ } catch {
+ // Silent failure
+ }
+ };
+ checkDownloaded();
+ }, []);
+
+ // Initial check and periodic polling
+ useEffect(() => {
+ checkForUpdate();
+
+ const interval = setInterval(() => {
+ checkForUpdate();
+ }, UPDATE_CHECK_INTERVAL_MS);
+
+ return () => clearInterval(interval);
+ }, [checkForUpdate]);
+
+ // Listen for push notifications about updates
+ useEffect(() => {
+ if (!window.electronAPI?.onAppUpdateAvailable) {
+ return;
+ }
+
+ const cleanup = window.electronAPI.onAppUpdateAvailable((info) => {
+ // New update notification - reset dismiss state if new version
+ if (currentVersionRef.current !== info.version) {
+ setIsDismissed(false);
+ currentVersionRef.current = info.version;
+ }
+ setUpdateInfo(info);
+ setIsDownloading(false);
+ setIsDownloaded(false);
+ setDownloadProgress(null);
+ setDownloadError(null);
+ });
+
+ return cleanup;
+ }, []);
+
+ // Listen for download progress
+ useEffect(() => {
+ if (!window.electronAPI?.onAppUpdateProgress) {
+ return;
+ }
+
+ const cleanup = window.electronAPI.onAppUpdateProgress((progress) => {
+ setDownloadProgress(progress);
+ });
+
+ return cleanup;
+ }, []);
+
+ // Listen for download completed
+ useEffect(() => {
+ if (!window.electronAPI?.onAppUpdateDownloaded) {
+ return;
+ }
+
+ const cleanup = window.electronAPI.onAppUpdateDownloaded(() => {
+ setIsDownloading(false);
+ setIsDownloaded(true);
+ setDownloadProgress(null);
+ });
+
+ return cleanup;
+ }, []);
+
+ // Handle update and restart
+ const handleUpdate = async () => {
+ if (isDownloaded) {
+ // Already downloaded - just install
+ window.electronAPI?.installAppUpdate?.();
+ return;
+ }
+
+ // Start download
+ setIsDownloading(true);
+ setDownloadError(null);
+
+ try {
+ if (!window.electronAPI?.downloadAppUpdate) {
+ setDownloadError(t("navigation:updateBanner.downloadError"));
+ setIsDownloading(false);
+ return;
+ }
+ const result = await window.electronAPI.downloadAppUpdate();
+ if (!result.success) {
+ setDownloadError(result.error || t("navigation:updateBanner.downloadError"));
+ setIsDownloading(false);
+ }
+ } catch (error) {
+ setDownloadError(t("navigation:updateBanner.downloadError"));
+ setIsDownloading(false);
+ }
+ };
+
+ // Handle dismiss
+ const handleDismiss = () => {
+ setIsDismissed(true);
+ };
+
+ // Don't render if no update or dismissed
+ if (!updateInfo || isDismissed) {
+ return null;
+ }
+
+ return (
+
+ {/* Header with version and dismiss */}
+
+
+
+
+ {t("navigation:updateBanner.title")}
+
+
+
+
+
+
+
+ {/* Version info */}
+
+ {t("navigation:updateBanner.version", { version: updateInfo.version })}
+
+
+ {/* Download progress */}
+ {isDownloading && downloadProgress && (
+
+
+ {t("navigation:updateBanner.downloading")}
+ {Math.round(downloadProgress.percent)}%
+
+
+
+ )}
+
+ {/* Error message */}
+ {downloadError && (
+
{downloadError}
+ )}
+
+ {/* Action button */}
+
+ {isDownloading ? (
+ <>
+
+ {t("navigation:updateBanner.downloading")}
+ >
+ ) : isDownloaded ? (
+ <>
+
+ {t("navigation:updateBanner.installAndRestart")}
+ >
+ ) : (
+ <>
+
+ {t("navigation:updateBanner.updateAndRestart")}
+ >
+ )}
+
+
+ );
+}
diff --git a/apps/frontend/src/shared/i18n/locales/en/navigation.json b/apps/frontend/src/shared/i18n/locales/en/navigation.json
index 37323518..c5409543 100644
--- a/apps/frontend/src/shared/i18n/locales/en/navigation.json
+++ b/apps/frontend/src/shared/i18n/locales/en/navigation.json
@@ -30,6 +30,15 @@
"messages": {
"initializeToCreateTasks": "Initialize Auto Claude to create tasks"
},
+ "updateBanner": {
+ "title": "Update Available",
+ "version": "Version {{version}} is ready",
+ "updateAndRestart": "Update and Restart",
+ "installAndRestart": "Install and Restart",
+ "downloading": "Downloading...",
+ "dismiss": "Dismiss",
+ "downloadError": "Failed to download update"
+ },
"claudeCode": {
"checking": "Checking Claude Code...",
"upToDate": "Claude Code is up to date",
diff --git a/apps/frontend/src/shared/i18n/locales/fr/navigation.json b/apps/frontend/src/shared/i18n/locales/fr/navigation.json
index 1a9327f8..19646a2f 100644
--- a/apps/frontend/src/shared/i18n/locales/fr/navigation.json
+++ b/apps/frontend/src/shared/i18n/locales/fr/navigation.json
@@ -30,6 +30,15 @@
"messages": {
"initializeToCreateTasks": "Initialisez Auto Claude pour créer des tâches"
},
+ "updateBanner": {
+ "title": "Mise à jour disponible",
+ "version": "Version {{version}} est prête",
+ "updateAndRestart": "Mettre à jour et redémarrer",
+ "installAndRestart": "Installer et redémarrer",
+ "downloading": "Téléchargement...",
+ "dismiss": "Ignorer",
+ "downloadError": "Échec du téléchargement de la mise à jour"
+ },
"claudeCode": {
"checking": "Vérification de Claude Code...",
"upToDate": "Claude Code est à jour",