From ada91fb195118fe88e543266c3c696162588103f Mon Sep 17 00:00:00 2001 From: StillKnotKnown <192589389+StillKnotKnown@users.noreply.github.com> Date: Thu, 8 Jan 2026 16:01:16 +0200 Subject: [PATCH] feat: add Claude Code changelog link to version notifiers (#820) * feat: add Claude Code changelog link to version notifiers Add link to Claude Code Changelog in both: - Claude Code CLI status badge popover - App Update Notification dialog The link opens https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md in external browser, allowing users to check what's new in Claude Code. Also converts AppUpdateNotification to use i18n translations. Fixes #817 * refactor: improve AppUpdateNotification code quality - Extract CLAUDE_CODE_CHANGELOG_URL to named constant - Remove unused "common" namespace from useTranslation hook --------- Co-authored-by: StillKnotKnown --- .../components/AppUpdateNotification.tsx | 124 ++++++---- .../components/ClaudeCodeStatusBadge.tsx | 213 ++++++++++-------- .../src/shared/i18n/locales/en/dialogs.json | 15 ++ .../shared/i18n/locales/en/navigation.json | 2 + .../src/shared/i18n/locales/fr/dialogs.json | 15 ++ .../shared/i18n/locales/fr/navigation.json | 2 + 6 files changed, 230 insertions(+), 141 deletions(-) diff --git a/apps/frontend/src/renderer/components/AppUpdateNotification.tsx b/apps/frontend/src/renderer/components/AppUpdateNotification.tsx index 2238aee3..7bd00f30 100644 --- a/apps/frontend/src/renderer/components/AppUpdateNotification.tsx +++ b/apps/frontend/src/renderer/components/AppUpdateNotification.tsx @@ -1,24 +1,20 @@ -import { useState, useEffect, useMemo } from 'react'; -import { - Download, - RefreshCw, - CheckCircle2, - AlertCircle -} from 'lucide-react'; -import { Button } from './ui/button'; -import { Progress } from './ui/progress'; +import { useState, useEffect, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Download, RefreshCw, CheckCircle2, AlertCircle, ExternalLink } from "lucide-react"; +import { Button } from "./ui/button"; +import { Progress } from "./ui/progress"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, - DialogTitle -} from './ui/dialog'; -import type { - AppUpdateAvailableEvent, - AppUpdateProgress -} from '../../shared/types'; + DialogTitle, +} from "./ui/dialog"; +import type { AppUpdateAvailableEvent, AppUpdateProgress } from "../../shared/types"; + +const CLAUDE_CODE_CHANGELOG_URL = + "https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md"; /** * Simple markdown renderer for release notes @@ -28,23 +24,32 @@ function ReleaseNotesRenderer({ markdown }: { markdown: string }) { const html = useMemo(() => { const result = markdown // Escape HTML - .replace(/&/g, '&') - .replace(//g, '>') + .replace(/&/g, "&") + .replace(//g, ">") // Headers (### Header ->

) - .replace(/^### (.+)$/gm, '

$1

') - .replace(/^## (.+)$/gm, '

$1

') + .replace( + /^### (.+)$/gm, + '

$1

' + ) + .replace( + /^## (.+)$/gm, + '

$1

' + ) // Bold (**text** -> ) .replace(/\*\*([^*]+)\*\*/g, '$1') // Inline code (`code` -> ) .replace(/`([^`]+)`/g, '$1') // List items (- item ->
  • ) - .replace(/^- (.+)$/gm, '
  • $1
  • ') + .replace( + /^- (.+)$/gm, + "
  • $1
  • " + ) // Wrap consecutive list items .replace(/(]*>.*?<\/li>\n?)+/g, '
      $&
    ') // Line breaks for remaining lines .replace(/\n\n/g, '
    ') - .replace(/\n/g, '
    '); + .replace(/\n/g, "
    "); return result; }, [markdown]); @@ -62,6 +67,7 @@ function ReleaseNotesRenderer({ markdown }: { markdown: string }) { * Shows when a new app version is available and handles download/install workflow */ export function AppUpdateNotification() { + const { t } = useTranslation(["dialogs"]); const [isOpen, setIsOpen] = useState(false); const [updateInfo, setUpdateInfo] = useState(null); const [downloadProgress, setDownloadProgress] = useState(null); @@ -109,12 +115,14 @@ export function AppUpdateNotification() { try { const result = await window.electronAPI.downloadAppUpdate(); if (!result.success) { - setDownloadError(result.error || 'Failed to download update'); + setDownloadError( + result.error || t("dialogs:appUpdate.downloadError", "Failed to download update") + ); setIsDownloading(false); } } catch (error) { - console.error('Failed to download app update:', error); - setDownloadError('Failed to download update'); + console.error("Failed to download app update:", error); + setDownloadError(t("dialogs:appUpdate.downloadError", "Failed to download update")); setIsDownloading(false); } }; @@ -137,10 +145,13 @@ export function AppUpdateNotification() { - App Update Available + {t("dialogs:appUpdate.title", "App Update Available")} - A new version of Auto Claude is ready to download + {t( + "dialogs:appUpdate.description", + "A new version of Auto Claude is ready to download" + )} @@ -150,14 +161,13 @@ export function AppUpdateNotification() {

    - New Version -

    -

    - {updateInfo.version} + {t("dialogs:appUpdate.newVersion", "New Version")}

    +

    {updateInfo.version}

    {updateInfo.releaseDate && (

    - Released {new Date(updateInfo.releaseDate).toLocaleDateString()} + {t("dialogs:appUpdate.released", "Released")}{" "} + {new Date(updateInfo.releaseDate).toLocaleDateString()}

    )}
    @@ -178,18 +188,36 @@ export function AppUpdateNotification() {
    )} + {/* Claude Code Changelog Link */} + + {/* Download Progress */} {isDownloading && downloadProgress && (
    - Downloading... + + {t("dialogs:appUpdate.downloading", "Downloading...")} + {Math.round(downloadProgress.percent)}%

    - {(downloadProgress.transferred / 1024 / 1024).toFixed(2)} MB / {(downloadProgress.total / 1024 / 1024).toFixed(2)} MB + {(downloadProgress.transferred / 1024 / 1024).toFixed(2)} MB /{" "} + {(downloadProgress.total / 1024 / 1024).toFixed(2)} MB

    )} @@ -206,39 +234,39 @@ export function AppUpdateNotification() { {isDownloaded && (
    - Update downloaded successfully! Click Install to restart and apply the update. + + {t( + "dialogs:appUpdate.updateDownloaded", + "Update downloaded successfully! Click Install to restart and apply the update." + )} +
    )} - {isDownloaded ? ( ) : ( - diff --git a/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx b/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx index 726982fa..11a98bbf 100644 --- a/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx +++ b/apps/frontend/src/renderer/components/ClaudeCodeStatusBadge.tsx @@ -1,17 +1,18 @@ -import { useState, useEffect, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; -import { Terminal, Check, AlertTriangle, X, Loader2, Download, RefreshCw, ExternalLink } from 'lucide-react'; -import { Button } from './ui/button'; +import { useState, useEffect, useCallback } from "react"; +import { useTranslation } from "react-i18next"; import { - Popover, - PopoverContent, - PopoverTrigger -} from './ui/popover'; -import { - Tooltip, - TooltipContent, - TooltipTrigger -} from './ui/tooltip'; + Terminal, + Check, + AlertTriangle, + X, + Loader2, + Download, + RefreshCw, + ExternalLink, +} from "lucide-react"; +import { Button } from "./ui/button"; +import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover"; +import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { AlertDialog, AlertDialogAction, @@ -20,16 +21,16 @@ import { AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, - AlertDialogTitle -} from './ui/alert-dialog'; -import { cn } from '../lib/utils'; -import type { ClaudeCodeVersionInfo } from '../../shared/types/cli'; + AlertDialogTitle, +} from "./ui/alert-dialog"; +import { cn } from "../lib/utils"; +import type { ClaudeCodeVersionInfo } from "../../shared/types/cli"; interface ClaudeCodeStatusBadgeProps { className?: string; } -type StatusType = 'loading' | 'installed' | 'outdated' | 'not-found' | 'error'; +type StatusType = "loading" | "installed" | "outdated" | "not-found" | "error"; // Check every 24 hours const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; @@ -39,8 +40,8 @@ const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000; * Shows installation status and provides quick access to install/update. */ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) { - const { t } = useTranslation(['common', 'navigation']); - const [status, setStatus] = useState('loading'); + const { t } = useTranslation(["common", "navigation"]); + const [status, setStatus] = useState("loading"); const [versionInfo, setVersionInfo] = useState(null); const [isInstalling, setIsInstalling] = useState(false); const [lastChecked, setLastChecked] = useState(null); @@ -51,7 +52,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) const checkVersion = useCallback(async () => { try { if (!window.electronAPI?.checkClaudeCodeVersion) { - setStatus('error'); + setStatus("error"); return; } @@ -62,18 +63,18 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) setLastChecked(new Date()); if (!result.data.installed) { - setStatus('not-found'); + setStatus("not-found"); } else if (result.data.isOutdated) { - setStatus('outdated'); + setStatus("outdated"); } else { - setStatus('installed'); + setStatus("installed"); } } else { - setStatus('error'); + setStatus("error"); } } catch (err) { - console.error('Failed to check Claude Code version:', err); - setStatus('error'); + console.error("Failed to check Claude Code version:", err); + setStatus("error"); } }, []); @@ -107,7 +108,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) }, 5000); } } catch (err) { - console.error('Failed to install Claude Code:', err); + console.error("Failed to install Claude Code:", err); } finally { setIsInstalling(false); } @@ -115,7 +116,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) // Handle install/update button click const handleInstall = () => { - if (status === 'outdated') { + if (status === "outdated") { // Show warning for updates since it will close running Claude sessions setShowUpdateWarning(true); } else { @@ -127,30 +128,30 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) // Get status indicator color const getStatusColor = () => { switch (status) { - case 'installed': - return 'bg-green-500'; - case 'outdated': - return 'bg-yellow-500'; - case 'not-found': - case 'error': - return 'bg-destructive'; + case "installed": + return "bg-green-500"; + case "outdated": + return "bg-yellow-500"; + case "not-found": + case "error": + return "bg-destructive"; default: - return 'bg-muted-foreground'; + return "bg-muted-foreground"; } }; // Get status icon const getStatusIcon = () => { switch (status) { - case 'loading': + case "loading": return ; - case 'installed': + case "installed": return ; - case 'outdated': + case "outdated": return ; - case 'not-found': + case "not-found": return ; - case 'error': + case "error": return ; } }; @@ -158,16 +159,16 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) // Get tooltip text const getTooltipText = () => { switch (status) { - case 'loading': - return t('navigation:claudeCode.checking', 'Checking Claude Code...'); - case 'installed': - return t('navigation:claudeCode.upToDate', 'Claude Code is up to date'); - case 'outdated': - return t('navigation:claudeCode.updateAvailable', 'Claude Code update available'); - case 'not-found': - return t('navigation:claudeCode.notInstalled', 'Claude Code not installed'); - case 'error': - return t('navigation:claudeCode.error', 'Error checking Claude Code'); + case "loading": + return t("navigation:claudeCode.checking", "Checking Claude Code..."); + case "installed": + return t("navigation:claudeCode.upToDate", "Claude Code is up to date"); + case "outdated": + return t("navigation:claudeCode.updateAvailable", "Claude Code update available"); + case "not-found": + return t("navigation:claudeCode.notInstalled", "Claude Code not installed"); + case "error": + return t("navigation:claudeCode.error", "Error checking Claude Code"); } }; @@ -180,36 +181,36 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) variant="ghost" size="sm" className={cn( - 'w-full justify-start gap-2 text-xs', - status === 'not-found' || status === 'error' ? 'text-destructive' : '', - status === 'outdated' ? 'text-yellow-600 dark:text-yellow-500' : '', + "w-full justify-start gap-2 text-xs", + status === "not-found" || status === "error" ? "text-destructive" : "", + status === "outdated" ? "text-yellow-600 dark:text-yellow-500" : "", className )} >
    - +
    Claude Code - {status === 'outdated' && ( + {status === "outdated" && ( - {t('common:update', 'Update')} + {t("common:update", "Update")} )} - {status === 'not-found' && ( + {status === "not-found" && ( - {t('common:install', 'Install')} + {t("common:install", "Install")} )} - - {getTooltipText()} - + {getTooltipText()} @@ -223,33 +224,37 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps)

    Claude Code CLI

    {getStatusIcon()} - {status === 'installed' && t('navigation:claudeCode.installed', 'Installed')} - {status === 'outdated' && t('navigation:claudeCode.outdated', 'Update available')} - {status === 'not-found' && t('navigation:claudeCode.missing', 'Not installed')} - {status === 'loading' && t('navigation:claudeCode.checking', 'Checking...')} - {status === 'error' && t('navigation:claudeCode.error', 'Error')} + {status === "installed" && t("navigation:claudeCode.installed", "Installed")} + {status === "outdated" && t("navigation:claudeCode.outdated", "Update available")} + {status === "not-found" && t("navigation:claudeCode.missing", "Not installed")} + {status === "loading" && t("navigation:claudeCode.checking", "Checking...")} + {status === "error" && t("navigation:claudeCode.error", "Error")}

    {/* Version info */} - {versionInfo && status !== 'loading' && ( + {versionInfo && status !== "loading" && (
    {versionInfo.installed && (
    - {t('navigation:claudeCode.current', 'Current')}: + + {t("navigation:claudeCode.current", "Current")}: + {versionInfo.installed}
    )} - {versionInfo.latest && versionInfo.latest !== 'unknown' && ( + {versionInfo.latest && versionInfo.latest !== "unknown" && (
    - {t('navigation:claudeCode.latest', 'Latest')}: + + {t("navigation:claudeCode.latest", "Latest")}: + {versionInfo.latest}
    )} {lastChecked && (
    - {t('navigation:claudeCode.lastChecked', 'Last checked')}: + {t("navigation:claudeCode.lastChecked", "Last checked")}: {lastChecked.toLocaleTimeString()}
    )} @@ -258,7 +263,7 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) {/* Actions */}
    - {(status === 'not-found' || status === 'outdated') && ( + {(status === "not-found" || status === "outdated") && ( )}
    @@ -293,10 +297,32 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) variant="link" size="sm" className="w-full text-xs text-muted-foreground gap-1" - onClick={() => window.electronAPI?.openExternal?.('https://claude.ai/code')} - aria-label={t('navigation:claudeCode.learnMoreAriaLabel', 'Learn more about Claude Code (opens in new window)')} + onClick={() => window.electronAPI?.openExternal?.("https://claude.ai/code")} + aria-label={t( + "navigation:claudeCode.learnMoreAriaLabel", + "Learn more about Claude Code (opens in new window)" + )} > - {t('navigation:claudeCode.learnMore', 'Learn more about Claude Code')} + {t("navigation:claudeCode.learnMore", "Learn more about Claude Code")} +
    @@ -307,18 +333,19 @@ export function ClaudeCodeStatusBadge({ className }: ClaudeCodeStatusBadgeProps) - {t('navigation:claudeCode.updateWarningTitle', 'Update Claude Code?')} + {t("navigation:claudeCode.updateWarningTitle", "Update Claude Code?")} - {t('navigation:claudeCode.updateWarningDescription', 'Updating will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding.')} + {t( + "navigation:claudeCode.updateWarningDescription", + "Updating will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding." + )} - - {t('common:cancel', 'Cancel')} - + {t("common:cancel", "Cancel")} - {t('navigation:claudeCode.updateAnyway', 'Update Anyway')} + {t("navigation:claudeCode.updateAnyway", "Update Anyway")} diff --git a/apps/frontend/src/shared/i18n/locales/en/dialogs.json b/apps/frontend/src/shared/i18n/locales/en/dialogs.json index 6a3d4682..cd51b4fb 100644 --- a/apps/frontend/src/shared/i18n/locales/en/dialogs.json +++ b/apps/frontend/src/shared/i18n/locales/en/dialogs.json @@ -133,5 +133,20 @@ "cancel": "Cancel", "remove": "Remove", "error": "Failed to remove project" + }, + "appUpdate": { + "title": "App Update Available", + "description": "A new version of Auto Claude is ready to download", + "newVersion": "New Version", + "released": "Released", + "downloading": "Downloading...", + "downloadUpdate": "Download Update", + "installAndRestart": "Install and Restart", + "installLater": "Install Later", + "remindMeLater": "Remind Me Later", + "updateDownloaded": "Update downloaded successfully! Click Install to restart and apply the update.", + "downloadError": "Failed to download update", + "claudeCodeChangelog": "View Claude Code Changelog", + "claudeCodeChangelogAriaLabel": "View Claude Code Changelog (opens in new window)" } } diff --git a/apps/frontend/src/shared/i18n/locales/en/navigation.json b/apps/frontend/src/shared/i18n/locales/en/navigation.json index c161c856..7171545b 100644 --- a/apps/frontend/src/shared/i18n/locales/en/navigation.json +++ b/apps/frontend/src/shared/i18n/locales/en/navigation.json @@ -44,6 +44,8 @@ "lastChecked": "Last checked", "learnMore": "Learn more about Claude Code", "learnMoreAriaLabel": "Learn more about Claude Code (opens in new window)", + "viewChangelog": "View Claude Code Changelog", + "viewChangelogAriaLabel": "View Claude Code Changelog (opens in new window)", "updateWarningTitle": "Update Claude Code?", "updateWarningDescription": "Updating will close all running Claude Code sessions. Any unsaved work in those sessions may be lost. Make sure to save your work before proceeding.", "updateAnyway": "Update Anyway" diff --git a/apps/frontend/src/shared/i18n/locales/fr/dialogs.json b/apps/frontend/src/shared/i18n/locales/fr/dialogs.json index e2bf6648..08d8c56f 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/dialogs.json +++ b/apps/frontend/src/shared/i18n/locales/fr/dialogs.json @@ -133,5 +133,20 @@ "cancel": "Annuler", "remove": "Retirer", "error": "Échec de la suppression du projet" + }, + "appUpdate": { + "title": "Mise à jour de l'application disponible", + "description": "Une nouvelle version d'Auto Claude est prête à être téléchargée", + "newVersion": "Nouvelle version", + "released": "Publiée", + "downloading": "Téléchargement...", + "downloadUpdate": "Télécharger la mise à jour", + "installAndRestart": "Installer et redémarrer", + "installLater": "Installer plus tard", + "remindMeLater": "Me rappeler plus tard", + "updateDownloaded": "Mise à jour téléchargée avec succès ! Cliquez sur Installer pour redémarrer et appliquer la mise à jour.", + "downloadError": "Échec du téléchargement de la mise à jour", + "claudeCodeChangelog": "Voir le journal des modifications Claude Code", + "claudeCodeChangelogAriaLabel": "Voir le journal des modifications Claude Code (s'ouvre dans une nouvelle fenêtre)" } } diff --git a/apps/frontend/src/shared/i18n/locales/fr/navigation.json b/apps/frontend/src/shared/i18n/locales/fr/navigation.json index 2f47585f..c6b932d2 100644 --- a/apps/frontend/src/shared/i18n/locales/fr/navigation.json +++ b/apps/frontend/src/shared/i18n/locales/fr/navigation.json @@ -44,6 +44,8 @@ "lastChecked": "Dernière vérification", "learnMore": "En savoir plus sur Claude Code", "learnMoreAriaLabel": "En savoir plus sur Claude Code (s'ouvre dans une nouvelle fenêtre)", + "viewChangelog": "Voir le journal des modifications Claude Code", + "viewChangelogAriaLabel": "Voir le journal des modifications Claude Code (s'ouvre dans une nouvelle fenêtre)", "updateWarningTitle": "Mettre à jour Claude Code ?", "updateWarningDescription": "La mise à jour fermera toutes les sessions Claude Code en cours. Tout travail non sauvegardé dans ces sessions pourrait être perdu. Assurez-vous de sauvegarder votre travail avant de continuer.", "updateAnyway": "Mettre à jour quand même"