mirror of
https://github.com/tiennm99/ccs.git
synced 2026-09-02 16:19:27 +00:00
fix(ui): address PR-Agent round 2 on health redesign
PR-Agent flagged two more real regressions vs the original health page: 1. Stale timestamp (HealthStatusRibbon): formatRelativeTime reads Date.now() during render, but nothing forces a re-render after mount so the 'last scan' label froze and never advanced to '1 minute ago', '2 hours ago', etc. Restore the per-second tick (matching the original health.tsx behavior) so the label behaves like a real relative timestamp. 2. Hidden copy action (HealthPriorityCard): the fix-copy button was gated by group-hover, making it invisible to keyboard users and unreachable on mobile (no hover state). Make it always visible with an aria-label so screen-reader and touch users can copy the fix command.
This commit is contained in:
@@ -127,14 +127,20 @@ export function HealthPriorityCard({ check }: HealthPriorityCardProps) {
|
|||||||
{(check.fix || check.fixable) && (
|
{(check.fix || check.fixable) && (
|
||||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
|
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
|
||||||
{check.fix && (
|
{check.fix && (
|
||||||
<div className="flex-1 flex items-center gap-2 h-10 px-3 rounded-full bg-background/50 border border-border/40 group/fix">
|
<div className="flex-1 flex items-center gap-2 h-10 px-3 rounded-full bg-background/50 border border-border/40">
|
||||||
<Terminal className="w-3.5 h-3.5 text-muted-foreground" />
|
<Terminal className="w-3.5 h-3.5 text-muted-foreground" />
|
||||||
<code className="text-xs font-mono flex-1 truncate">{check.fix}</code>
|
<code className="text-xs font-mono flex-1 truncate">{check.fix}</code>
|
||||||
|
{/*
|
||||||
|
* Always-visible copy button. The previous design hid it behind
|
||||||
|
* group-hover, which excluded keyboard users and touch-only
|
||||||
|
* devices (no hover state on mobile = effectively unreachable).
|
||||||
|
*/}
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={copyFix}
|
onClick={copyFix}
|
||||||
className="h-6 w-6 rounded-full opacity-0 group-hover/fix:opacity-100 transition-opacity"
|
aria-label={t('health.copy') ?? 'Copy fix command'}
|
||||||
|
className="h-6 w-6 rounded-full text-muted-foreground hover:text-foreground"
|
||||||
>
|
>
|
||||||
<Copy className="w-3 h-3" />
|
<Copy className="w-3 h-3" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
import { Cpu, RefreshCw, Terminal, Copy } from 'lucide-react';
|
import { Cpu, RefreshCw, Terminal, Copy } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
@@ -41,6 +42,18 @@ export function HealthStatusRibbon({
|
|||||||
}: HealthStatusRibbonProps) {
|
}: HealthStatusRibbonProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// formatRelativeTime reads Date.now() during render. Without a ticking
|
||||||
|
// re-render, the "last scan" label freezes at whatever it showed on mount
|
||||||
|
// and never advances to "1 minute ago", "2 hours ago", etc. — even though
|
||||||
|
// the underlying lastScan timestamp is stale. Force a re-render every
|
||||||
|
// second; the cost is negligible (one tiny component) and the UX win is
|
||||||
|
// a label that actually behaves like a relative timestamp.
|
||||||
|
const [, setTick] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => setTick((n) => n + 1), 1000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const copyDoctorCommand = () => {
|
const copyDoctorCommand = () => {
|
||||||
navigator.clipboard.writeText('ccs doctor');
|
navigator.clipboard.writeText('ccs doctor');
|
||||||
toast.success(t('health.copied'));
|
toast.success(t('health.copied'));
|
||||||
|
|||||||
Reference in New Issue
Block a user