feat(ui): add stability warning to ProxyStatusWidget

- Show amber warning indicator when installed version is unstable (v81+)
- Version display now shows "(unstable)" suffix with amber styling
- "Downgrade" button replaces "Update" when version is unstable
- Tooltip explains restart will downgrade to stable version
This commit is contained in:
kaitranntt
2026-01-05 12:46:35 -05:00
parent c5621dab51
commit 8a56a43989
@@ -16,6 +16,7 @@ import {
RotateCw, RotateCw,
ArrowUp, ArrowUp,
Globe, Globe,
AlertTriangle,
} from 'lucide-react'; } from 'lucide-react';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
@@ -75,6 +76,7 @@ export function ProxyStatusWidget() {
const isRunning = status?.running ?? false; const isRunning = status?.running ?? false;
const isActioning = startProxy.isPending || stopProxy.isPending; const isActioning = startProxy.isPending || stopProxy.isPending;
const hasUpdate = updateCheck?.hasUpdate ?? false; const hasUpdate = updateCheck?.hasUpdate ?? false;
const isUnstable = updateCheck?.isStable === false;
// Build remote display info // Build remote display info
const remoteDisplayHost = isRemoteMode const remoteDisplayHost = isRemoteMode
@@ -191,29 +193,35 @@ export function ProxyStatusWidget() {
{/* Control buttons when running */} {/* Control buttons when running */}
<div className="mt-2 flex items-center gap-2"> <div className="mt-2 flex items-center gap-2">
<Button <Button
variant={hasUpdate ? 'default' : 'outline'} variant={hasUpdate || isUnstable ? 'default' : 'outline'}
size="sm" size="sm"
className={cn( className={cn(
'h-7 text-xs gap-1 flex-1', 'h-7 text-xs gap-1 flex-1',
hasUpdate && isUnstable
'bg-sidebar-accent hover:bg-sidebar-accent/90 text-sidebar-accent-foreground' ? 'bg-amber-500 hover:bg-amber-600 text-white'
: hasUpdate &&
'bg-sidebar-accent hover:bg-sidebar-accent/90 text-sidebar-accent-foreground'
)} )}
onClick={handleRestart} onClick={handleRestart}
disabled={isActioning} disabled={isActioning}
title={ title={
hasUpdate isUnstable
? `Restart to update: v${updateCheck?.currentVersion} -> v${updateCheck?.latestVersion}` ? `Current v${updateCheck?.currentVersion} is unstable. Restart to downgrade to stable v${updateCheck?.maxStableVersion}`
: 'Restart CLIProxy service' : hasUpdate
? `Restart to update: v${updateCheck?.currentVersion} -> v${updateCheck?.latestVersion}`
: 'Restart CLIProxy service'
} }
> >
{isActioning ? ( {isActioning ? (
<RefreshCw className="w-3 h-3 animate-spin" /> <RefreshCw className="w-3 h-3 animate-spin" />
) : isUnstable ? (
<AlertTriangle className="w-3 h-3" />
) : hasUpdate ? ( ) : hasUpdate ? (
<ArrowUp className="w-3 h-3" /> <ArrowUp className="w-3 h-3" />
) : ( ) : (
<RotateCw className="w-3 h-3" /> <RotateCw className="w-3 h-3" />
)} )}
{hasUpdate ? 'Update' : 'Restart'} {isUnstable ? 'Downgrade' : hasUpdate ? 'Update' : 'Restart'}
</Button> </Button>
<Button <Button
variant="outline" variant="outline"
@@ -255,7 +263,20 @@ export function ProxyStatusWidget() {
{/* Version sync indicator */} {/* Version sync indicator */}
{updateCheck?.currentVersion && ( {updateCheck?.currentVersion && (
<div className="mt-2 pt-2 border-t border-muted flex items-center justify-between text-[10px] text-muted-foreground/70"> <div className="mt-2 pt-2 border-t border-muted flex items-center justify-between text-[10px] text-muted-foreground/70">
<span>v{updateCheck.currentVersion}</span> <span className="flex items-center gap-1">
{isUnstable && (
<AlertTriangle
className="w-3 h-3 text-amber-500"
title={updateCheck.stabilityMessage}
/>
)}
<span className={isUnstable ? 'text-amber-600 dark:text-amber-400' : ''}>
v{updateCheck.currentVersion}
</span>
{isUnstable && (
<span className="text-amber-600/70 dark:text-amber-400/70">(unstable)</span>
)}
</span>
{updateCheck.checkedAt && ( {updateCheck.checkedAt && (
<span title={new Date(updateCheck.checkedAt).toLocaleString()}> <span title={new Date(updateCheck.checkedAt).toLocaleString()}>
Synced {formatTimeAgo(updateCheck.checkedAt)} Synced {formatTimeAgo(updateCheck.checkedAt)}