diff --git a/server/index.js b/server/index.js index f4c9e25e..d957ef58 100755 --- a/server/index.js +++ b/server/index.js @@ -76,6 +76,19 @@ const __dirname = getModuleDir(import.meta.url); // Resolving the app root once keeps every repo-level lookup below aligned across both layouts. const APP_ROOT = findAppRoot(__dirname); const installMode = fs.existsSync(path.join(APP_ROOT, '.git')) ? 'git' : 'npm'; +// Version of the code that is actually running, captured once at process +// startup. This intentionally does NOT re-read package.json per request: after +// an update replaces the files on disk, package.json reflects the NEW version +// while this long-lived process still runs the OLD code. The frontend bundle is +// rebuilt on update, so a mismatch between this value and the frontend's +// build-time version means the server was updated but not restarted. +const RUNNING_VERSION = (() => { + try { + return JSON.parse(fs.readFileSync(path.join(APP_ROOT, 'package.json'), 'utf8')).version || null; + } catch { + return null; + } +})(); const MAX_FILE_UPLOAD_SIZE_MB = 200; const MAX_FILE_UPLOAD_SIZE_BYTES = MAX_FILE_UPLOAD_SIZE_MB * 1024 * 1024; const MAX_FILE_UPLOAD_COUNT = 20; @@ -156,7 +169,8 @@ app.get('/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString(), - installMode + installMode, + version: RUNNING_VERSION }); }); diff --git a/src/components/sidebar/view/Sidebar.tsx b/src/components/sidebar/view/Sidebar.tsx index 15d96990..5e544b08 100644 --- a/src/components/sidebar/view/Sidebar.tsx +++ b/src/components/sidebar/view/Sidebar.tsx @@ -43,7 +43,7 @@ function Sidebar({ }: SidebarProps) { const { t } = useTranslation(['sidebar', 'common']); const { isPWA } = useDeviceSettings({ trackMobile: false }); - const { updateAvailable, latestVersion, currentVersion, releaseInfo, installMode } = useVersionCheck( + const { updateAvailable, restartRequired, latestVersion, currentVersion, releaseInfo, installMode } = useVersionCheck( 'siteboon', 'claudecodeui', ); @@ -224,6 +224,7 @@ function Sidebar({ onExpand={handleExpandSidebar} onShowSettings={onShowSettings} updateAvailable={updateAvailable} + restartRequired={restartRequired} onShowVersionModal={() => setShowVersionModal(true)} t={t} /> @@ -296,6 +297,7 @@ function Sidebar({ onCreateProject={() => setShowNewProject(true)} onCollapseSidebar={handleCollapseSidebar} updateAvailable={updateAvailable} + restartRequired={restartRequired} releaseInfo={releaseInfo} latestVersion={latestVersion} currentVersion={currentVersion} diff --git a/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx b/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx index 90a6338f..c4ae4300 100644 --- a/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx +++ b/src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx @@ -1,4 +1,4 @@ -import { Settings, Sparkles, PanelLeftOpen, Bug } from 'lucide-react'; +import { Settings, Sparkles, PanelLeftOpen, Bug, AlertTriangle } from 'lucide-react'; import type { TFunction } from 'i18next'; const DISCORD_INVITE_URL = 'https://discord.gg/buxwujPNRE'; @@ -16,6 +16,7 @@ type SidebarCollapsedProps = { onExpand: () => void; onShowSettings: () => void; updateAvailable: boolean; + restartRequired: boolean; onShowVersionModal: () => void; t: TFunction; }; @@ -24,6 +25,7 @@ export default function SidebarCollapsed({ onExpand, onShowSettings, updateAvailable, + restartRequired, onShowVersionModal, t, }: SidebarCollapsedProps) { @@ -75,6 +77,18 @@ export default function SidebarCollapsed({ + {/* Restart-required indicator */} + {restartRequired && ( +
+ + +
+ )} + {/* Update indicator */} {updateAvailable && (