diff --git a/apps/desktop/src/app/command-palette/index.tsx b/apps/desktop/src/app/command-palette/index.tsx index 0014e4e71f1..59f8ba0cbae 100644 --- a/apps/desktop/src/app/command-palette/index.tsx +++ b/apps/desktop/src/app/command-palette/index.tsx @@ -49,6 +49,7 @@ import { } from '@/lib/icons' import { normalize } from '@/lib/text' import { cn } from '@/lib/utils' +import { resolveVersionStatus } from '@/lib/version-status' import { $repoWorktrees } from '@/store/coding-status' import { $commandPaletteOpen, @@ -59,8 +60,16 @@ import { import { $bindings } from '@/store/keybinds' import { openPetGenerate } from '@/store/pet-generate' import { requestStartWorkSession } from '@/store/projects' +import { $connection } from '@/store/session' import { runGatewayRestart } from '@/store/system-actions' -import { applyBackendUpdate } from '@/store/updates' +import { + $backendUpdateApply, + $backendUpdateStatus, + $desktopVersion, + $updateApply, + $updateStatus, + requestActiveUpdate +} from '@/store/updates' import { canOpenNewWindow, openNewWindow } from '@/store/windows' import { luminance } from '@/themes/color' import { type ThemeMode, useTheme } from '@/themes/context' @@ -93,6 +102,8 @@ interface PaletteItem { action?: string /** Renders a trailing check: this row IS the current setting (theme, mode). */ active?: boolean + /** Muted text beside the label — state the row acts on (a version, a count). */ + detail?: string icon: IconComponent id: string /** Keep the palette open after running (live-preview pickers like theme/mode). */ @@ -242,6 +253,7 @@ const PaletteRow = memo(function PaletteRow({ > {item.label} + {item.detail && {item.detail}} {combo && } {item.to && } {item.active && } @@ -345,6 +357,35 @@ export function CommandPalette() { const [search, setSearch] = useState('') const [page, setPage] = useState(null) + // The Update row names the same install the statusbar names — same target + // selection, same resolver. Reduced to the label string: an in-flight apply + // rewrites these stores on every progress line, and only a changed string + // should rebuild the palette's groups. + const connection = useStore($connection) + const desktopVersion = useStore($desktopVersion) + const clientStatus = useStore($updateStatus) + const clientApply = useStore($updateApply) + const backendStatus = useStore($backendUpdateStatus) + const backendApply = useStore($backendUpdateApply) + + const updateVersionLabel = useMemo(() => { + const backend = connection?.mode === 'remote' + const apply = backend ? backendApply : clientApply + const status = backend ? backendStatus : clientStatus + + return resolveVersionStatus({ + applying: apply.applying || apply.stage === 'restart', + behind: status?.behind ?? 0, + copy: t.shell.statusbar, + remote: backend, + restarting: apply.stage === 'restart', + sha: status?.currentSha?.slice(0, 7) ?? null, + target: backend ? 'backend' : 'client', + updateAvailable: status?.updateAvailable, + version: backend ? status?.currentVersion : desktopVersion?.appVersion + }).label + }, [backendApply, backendStatus, clientApply, clientStatus, connection?.mode, desktopVersion?.appVersion, t]) + // cmdk's onSelect doesn't forward the triggering event — keep the last // click/keydown modifiers so session rows can honour ⌘-Enter / ⌘-click. const lastSelectMods = useRef<{ ctrlKey: boolean; metaKey: boolean; shiftKey: boolean }>({ @@ -582,11 +623,12 @@ export function CommandPalette() { run: () => void runGatewayRestart() }, { + detail: updateVersionLabel, icon: Download, id: 'cc-update-hermes', keywords: ['update', 'upgrade', 'hermes', 'version', 'system', 'restart'], label: cc.updateHermes, - run: () => void applyBackendUpdate() + run: () => requestActiveUpdate() } ] }, @@ -663,7 +705,7 @@ export function CommandPalette() { ] : []) ] - }, [contributedItems, go, settingsSectionLabel, t, worktrees]) + }, [contributedItems, go, settingsSectionLabel, t, updateVersionLabel, worktrees]) // The long, granular lists (settings fields, API keys, MCP servers, archived // chats) only surface once the user types — otherwise they'd bury the