fix: polish sidebar and masthead chrome, scroll setup preflight (#1522)

Remove redundant nav and label dots, move desktop masthead status to animated left rails, and let AuthCanvas scroll long preflight content on small viewports.
This commit is contained in:
Anso
2026-06-29 20:29:06 -04:00
committed by GitHub
parent da73ee0717
commit 322d337d20
8 changed files with 51 additions and 59 deletions
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Bell } from 'lucide-react';
import { MastheadRail } from '@/components/ui/MastheadRail';
import type { Stats, SystemStats, NotificationItem, HealthLevel } from './types';
import { deriveHealth } from './deriveHealth';
import { countVisibleUnread } from '@/lib/notificationVisibility';
@@ -15,24 +16,21 @@ interface HealthStatusBarProps {
metricsStale?: boolean;
}
const healthConfig: Record<HealthLevel, { label: string; dotClass: string; textClass: string; railClass: string; tintClass: string }> = {
const healthConfig: Record<HealthLevel, { label: string; textClass: string; railClass: string; tintClass: string }> = {
healthy: {
label: 'Healthy',
dotClass: 'bg-success shadow-[0_0_0_3px_color-mix(in_oklch,var(--success)_20%,transparent)]',
textClass: 'text-stat-value',
railClass: 'bg-brand',
tintClass: 'from-brand/[0.06] via-transparent to-transparent',
},
degraded: {
label: 'Degraded',
dotClass: 'bg-warning shadow-[0_0_0_3px_color-mix(in_oklch,var(--warning)_22%,transparent)]',
textClass: 'text-warning',
railClass: 'bg-warning',
tintClass: 'from-warning/[0.06] via-transparent to-transparent',
},
critical: {
label: 'Critical',
dotClass: 'bg-destructive shadow-[0_0_0_3px_color-mix(in_oklch,var(--destructive)_24%,transparent)]',
textClass: 'text-destructive',
railClass: 'bg-destructive',
tintClass: 'from-destructive/[0.06] via-transparent to-transparent',
@@ -92,14 +90,10 @@ export function HealthStatusBar({
className={`relative overflow-hidden rounded-lg border border-card-border border-t-card-border-top bg-card shadow-card-bevel transition-colors`}
>
<div className={`pointer-events-none absolute inset-0 bg-gradient-to-r ${config.tintClass}`} />
<div className={`absolute inset-y-0 left-0 w-[3px] ${config.railClass}`} />
<MastheadRail variant={level === 'healthy' ? 'shimmer' : 'glow'} className={config.railClass} />
<div className="relative grid grid-cols-[auto_1fr_auto] items-center gap-6 py-5 pl-7 pr-6">
{/* State column */}
<div className="flex items-center gap-4">
<span
aria-hidden="true"
className={`h-2.5 w-2.5 rounded-full ${config.dotClass} ${level === 'healthy' ? '' : 'animate-[pulse_2.4s_ease-in-out_infinite]'}`}
/>
<div className="flex flex-col gap-1">
<span className={`font-heading text-3xl leading-none tracking-tight ${config.textClass}`}>
{config.label}
@@ -1,5 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Bell } from 'lucide-react';
import { MastheadRail } from '@/components/ui/MastheadRail';
type FleetHealth = 'healthy' | 'degraded' | 'critical';
interface FleetMastheadProps {
@@ -37,24 +38,21 @@ function useTicker(intervalMs: number): number {
return now;
}
const healthConfig: Record<FleetHealth, { label: string; dotClass: string; textClass: string; railClass: string; tintClass: string }> = {
const healthConfig: Record<FleetHealth, { label: string; textClass: string; railClass: string; tintClass: string }> = {
healthy: {
label: 'The fleet',
dotClass: 'bg-success shadow-[0_0_0_3px_color-mix(in_oklch,var(--success)_20%,transparent)]',
textClass: 'text-stat-value',
railClass: 'bg-brand',
tintClass: 'from-brand/[0.06] via-transparent to-transparent',
},
degraded: {
label: 'The fleet',
dotClass: 'bg-warning shadow-[0_0_0_3px_color-mix(in_oklch,var(--warning)_22%,transparent)]',
textClass: 'text-warning',
railClass: 'bg-warning',
tintClass: 'from-warning/[0.06] via-transparent to-transparent',
},
critical: {
label: 'The fleet',
dotClass: 'bg-destructive shadow-[0_0_0_3px_color-mix(in_oklch,var(--destructive)_24%,transparent)]',
textClass: 'text-destructive',
railClass: 'bg-destructive',
tintClass: 'from-destructive/[0.06] via-transparent to-transparent',
@@ -102,13 +100,9 @@ export function FleetMasthead({
return (
<div className="relative overflow-hidden rounded-lg border border-card-border border-t-card-border-top bg-card shadow-card-bevel transition-colors mb-4">
<div className={`pointer-events-none absolute inset-0 bg-gradient-to-r ${config.tintClass}`} />
<div className={`absolute inset-y-0 left-0 w-[3px] ${config.railClass}`} />
<MastheadRail variant={level === 'healthy' ? 'shimmer' : 'glow'} className={config.railClass} />
<div className="relative grid grid-cols-[auto_1fr_auto] items-center gap-6 py-5 pl-7 pr-6">
<div className="flex items-center gap-4">
<span
aria-hidden="true"
className={`h-2.5 w-2.5 rounded-full ${config.dotClass} ${level === 'healthy' ? '' : 'animate-[pulse_2.4s_ease-in-out_infinite]'}`}
/>
<div className="flex flex-col gap-1">
<span className={`font-heading text-3xl leading-none tracking-tight ${config.textClass}`}>
{config.label}
@@ -93,13 +93,6 @@ export function SettingsSidebar({ currentSection, onSectionChange, dirtyFlags, o
className="absolute inset-y-0 left-0 w-[2px] rounded-sm bg-brand"
/>
)}
<span
aria-hidden="true"
className={cn(
'h-1 w-1 shrink-0 rounded-full',
isActive ? 'bg-brand' : 'bg-stat-subtitle/40',
)}
/>
<span className="flex-1 truncate text-left">{item.label}</span>
{isDirty && (
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-warning" />
+1 -17
View File
@@ -3,7 +3,6 @@ import { GitBranch, Loader2, AlertCircle } from 'lucide-react';
import type { CheckStatus } from '@/types/imageUpdates';
import { Cursor, CursorContainer, CursorFollow, CursorProvider } from '@/components/animate-ui/primitives/animate/cursor';
import { Checkbox } from '@/components/ui/checkbox';
import { LabelDot } from '@/components/LabelPill';
import type { Label } from '@/components/label-types';
import { cn } from '@/lib/utils';
import { sidebarRowActive, sidebarRowBase, sidebarRowCheckboxSlot } from './sidebar-styles';
@@ -47,18 +46,13 @@ function RowTooltip({ trigger, label }: { trigger: ReactNode; label: string }) {
);
}
const MAX_VISIBLE_LABELS = 3;
export function StackRow(props: StackRowProps) {
const {
file, displayName, status, running, total, isBusy, isActive, labels,
file, displayName, status, running, total, isBusy, isActive,
hasUpdate, checkStatus, lastError, hasGitPending, onSelect, kebabSlot,
bulkMode = false, isSelected = false, onToggleSelect,
} = props;
const visibleLabels = labels.slice(0, MAX_VISIBLE_LABELS);
const overflowCount = Math.max(0, labels.length - MAX_VISIBLE_LABELS);
const handleClick = () => {
if (bulkMode) onToggleSelect?.(file);
else onSelect(file);
@@ -110,16 +104,6 @@ export function StackRow(props: StackRowProps) {
{/* Stack name */}
<span className="flex-1 truncate font-mono text-sm min-w-0">{displayName}</span>
{/* Trailing: label dots (max 3 + overflow count) */}
{visibleLabels.length > 0 && (
<span className="flex items-center gap-0.5 shrink-0">
{visibleLabels.map(l => <LabelDot key={l.id} color={l.color} />)}
{overflowCount > 0 && (
<span className="font-mono text-[10px] leading-3 text-stat-icon">+{overflowCount}</span>
)}
</span>
)}
{/* Fixed trailing icon slot: update dot > check-failed > git pending */}
<span className="w-3.5 h-3.5 flex items-center justify-center shrink-0">
{hasUpdate ? (
@@ -93,13 +93,13 @@ describe('StackRow', () => {
expect(screen.queryByText('UP')).not.toBeInTheDocument();
});
it('renders label indicators', () => {
it('does not render trailing label color dots when labels are assigned', () => {
const labels: Label[] = [
{ id: 1, node_id: 0, name: 'prod', color: 'teal' },
{ id: 2, node_id: 0, name: 'media', color: 'blue' },
];
const { container } = render(<StackRow {...base({ labels })} />);
expect(container.querySelectorAll('[style*="--label-"]')).toHaveLength(2);
expect(container.querySelectorAll('[style*="--label-"]')).toHaveLength(0);
});
// ── Image-update check status indicator ────────────────────────────────
@@ -0,0 +1,23 @@
import { cn } from '@/lib/utils';
export type MastheadRailVariant = 'shimmer' | 'glow';
interface MastheadRailProps {
variant: MastheadRailVariant;
className?: string;
}
export function MastheadRail({ variant, className }: MastheadRailProps) {
return (
<div
aria-hidden
className={cn('absolute inset-y-0 left-0 w-[3px] overflow-hidden', className)}
>
{variant === 'shimmer' ? (
<div className="masthead-rail-shimmer absolute inset-x-0 h-1/3 bg-gradient-to-b from-transparent via-white/25 to-transparent" />
) : (
<div className="masthead-rail-glow absolute inset-0" />
)}
</div>
);
}
+9 -15
View File
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import { cn } from '@/lib/utils';
import { MastheadRail } from '@/components/ui/MastheadRail';
export type MastheadTone = 'live' | 'idle' | 'warn' | 'error';
@@ -14,6 +15,7 @@ export interface PageMastheadProps {
kicker?: string;
state: string;
tone: MastheadTone;
/** When true with tone `live`, the left rail shimmers; otherwise the rail glows subtly. */
pulsing?: boolean;
metadata?: MastheadMetadataItem[];
/** Optional meta line under the state word (e.g. a one-line posture summary). */
@@ -29,27 +31,27 @@ export interface PageMastheadProps {
}
const toneConfig: Record<MastheadTone, {
dotClass: string;
railClass: string;
stateTextClass: string;
tintClass: string;
}> = {
live: {
dotClass: 'bg-brand shadow-[0_0_0_3px_color-mix(in_oklch,var(--brand)_22%,transparent)]',
railClass: 'bg-brand',
stateTextClass: 'text-stat-value',
tintClass: 'from-brand/[0.06] via-transparent to-transparent',
},
idle: {
dotClass: 'bg-stat-subtitle',
railClass: 'bg-stat-subtitle',
stateTextClass: 'text-stat-title',
tintClass: 'from-transparent via-transparent to-transparent',
},
warn: {
dotClass: 'bg-warning shadow-[0_0_0_3px_color-mix(in_oklch,var(--warning)_22%,transparent)]',
railClass: 'bg-warning',
stateTextClass: 'text-warning',
tintClass: 'from-warning/[0.06] via-transparent to-transparent',
},
error: {
dotClass: 'bg-destructive shadow-[0_0_0_3px_color-mix(in_oklch,var(--destructive)_24%,transparent)]',
railClass: 'bg-destructive',
stateTextClass: 'text-destructive',
tintClass: 'from-destructive/[0.06] via-transparent to-transparent',
},
@@ -74,7 +76,7 @@ export function PageMasthead({
size = 'default',
}: PageMastheadProps) {
const config = toneConfig[tone];
const shouldPulse = pulsing && (tone === 'live' || tone === 'warn');
const railVariant = tone === 'live' && pulsing ? 'shimmer' : 'glow';
return (
<div
@@ -84,17 +86,9 @@ export function PageMasthead({
)}
>
<div className={cn('pointer-events-none absolute inset-0 bg-gradient-to-r', config.tintClass)} />
<div className="absolute inset-y-0 left-0 w-[3px] bg-brand" />
<MastheadRail variant={railVariant} className={config.railClass} />
<div className="relative grid grid-cols-[1fr_auto] items-center gap-6 py-5 pl-7 pr-6">
<div className="flex min-w-0 items-center gap-4">
<span
aria-hidden="true"
className={cn(
'h-2.5 w-2.5 shrink-0 rounded-full',
config.dotClass,
shouldPulse && 'animate-[pulse_2.4s_ease-in-out_infinite]',
)}
/>
<div className="flex min-w-0 flex-col gap-1">
{kicker ? (
<span className="font-mono text-[10px] leading-3 uppercase tracking-[0.18em] text-stat-subtitle">
+10
View File
@@ -716,6 +716,10 @@ body {
0% { transform: translateY(-100%); }
100% { transform: translateY(200%); }
}
@keyframes masthead-rail-glow {
0%, 100% { opacity: 0.55; box-shadow: inset 0 0 0 0 transparent; }
50% { opacity: 1; box-shadow: inset 0 0 10px 0 oklch(1 0 0 / 0.12); }
}
/* ─────────────────────────────────────────────────────────────
ANIMATION UTILITIES
@@ -738,6 +742,12 @@ body {
.animate-shimmer {
animation: shimmer 4.5s ease-in-out infinite alternate;
}
.masthead-rail-shimmer {
animation: shimmer 11s ease-in-out infinite alternate;
}
.masthead-rail-glow {
animation: masthead-rail-glow 5.5s ease-in-out infinite alternate;
}
/* Stagger delay helpers */
.animate-delay-50 { animation-delay: 50ms; }