fix(agent-graph): center launch stepper hud

This commit is contained in:
777genius 2026-04-16 13:40:21 +03:00
parent 345fd3e41d
commit 19463edfc9
3 changed files with 18 additions and 63 deletions

View file

@ -107,8 +107,8 @@ export function GraphControls({
return ( return (
<> <>
<div className="absolute inset-x-3 top-3 z-20 flex items-start gap-2 pointer-events-none"> <div className="pointer-events-none absolute inset-x-3 top-3 z-20 h-8">
<div className="flex shrink-0 items-center gap-0.5"> <div className="absolute left-0 top-0 flex shrink-0 items-center gap-0.5">
{onToggleSidebar ? ( {onToggleSidebar ? (
<div <div
className="pointer-events-auto flex items-center rounded-md p-0 backdrop-blur-sm" className="pointer-events-auto flex items-center rounded-md p-0 backdrop-blur-sm"
@ -165,15 +165,15 @@ export function GraphControls({
) : null} ) : null}
</div> </div>
<div className="flex min-w-0 flex-1 justify-end px-2"> <div className="absolute left-1/2 top-0 w-[min(360px,38vw)] -translate-x-1/2 px-2">
{topToolbarContent ? ( {topToolbarContent ? (
<div className="pointer-events-auto min-w-0 max-w-[min(360px,42vw)]"> <div className="pointer-events-auto min-w-0">
{topToolbarContent} {topToolbarContent}
</div> </div>
) : null} ) : null}
</div> </div>
<div className="flex shrink-0 items-center gap-0.5"> <div className="absolute right-0 top-0 flex shrink-0 items-center gap-0.5">
<div <div
className="pointer-events-auto flex items-center rounded-md p-0 backdrop-blur-sm" className="pointer-events-auto flex items-center rounded-md p-0 backdrop-blur-sm"
style={{ style={{

View file

@ -4,7 +4,6 @@ import { DISPLAY_STEPS } from '@renderer/components/team/provisioningSteps';
import { StepProgressBar } from '@renderer/components/team/StepProgressBar'; import { StepProgressBar } from '@renderer/components/team/StepProgressBar';
import { TeamProvisioningPanel } from '@renderer/components/team/TeamProvisioningPanel'; import { TeamProvisioningPanel } from '@renderer/components/team/TeamProvisioningPanel';
import { useTeamProvisioningPresentation } from '@renderer/components/team/useTeamProvisioningPresentation'; import { useTeamProvisioningPresentation } from '@renderer/components/team/useTeamProvisioningPresentation';
import { Badge } from '@renderer/components/ui/badge';
import { import {
Dialog, Dialog,
DialogContent, DialogContent,
@ -13,7 +12,6 @@ import {
DialogTitle, DialogTitle,
} from '@renderer/components/ui/dialog'; } from '@renderer/components/ui/dialog';
import { cn } from '@renderer/lib/utils'; import { cn } from '@renderer/lib/utils';
import { AlertTriangle, CheckCircle2, Loader2 } from 'lucide-react';
import type { TeamProvisioningPresentation } from '@renderer/utils/teamProvisioningPresentation'; import type { TeamProvisioningPresentation } from '@renderer/utils/teamProvisioningPresentation';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
@ -42,38 +40,23 @@ function shouldRenderLaunchHud(presentation: TeamProvisioningPresentation | null
function getToneClasses(tone: TeamProvisioningPresentation['compactTone']): { function getToneClasses(tone: TeamProvisioningPresentation['compactTone']): {
border: string; border: string;
badge: string;
icon: React.ReactNode;
iconClassName: string;
} { } {
switch (tone) { switch (tone) {
case 'error': case 'error':
return { return {
border: 'border-red-400/35 bg-[rgba(26,10,16,0.92)]', border: 'border-red-400/35 bg-[rgba(26,10,16,0.9)]',
badge: 'border-red-500/30 text-red-300',
icon: <AlertTriangle size={12} />,
iconClassName: 'text-red-400',
}; };
case 'warning': case 'warning':
return { return {
border: 'border-amber-400/35 bg-[rgba(31,18,8,0.92)]', border: 'border-amber-400/35 bg-[rgba(31,18,8,0.9)]',
badge: 'border-amber-500/30 text-amber-200',
icon: <AlertTriangle size={12} />,
iconClassName: 'text-amber-400',
}; };
case 'success': case 'success':
return { return {
border: 'border-emerald-400/35 bg-[rgba(8,24,18,0.92)]', border: 'border-emerald-400/35 bg-[rgba(8,24,18,0.9)]',
badge: 'border-emerald-500/30 text-emerald-200',
icon: <CheckCircle2 size={12} />,
iconClassName: 'text-emerald-400',
}; };
default: default:
return { return {
border: 'border-cyan-400/25 bg-[rgba(8,14,26,0.92)]', border: 'border-cyan-400/25 bg-[rgba(8,14,26,0.9)]',
badge: 'border-cyan-500/20 text-cyan-200',
icon: <Loader2 size={12} className="animate-spin" />,
iconClassName: 'text-cyan-300',
}; };
} }
} }
@ -109,14 +92,10 @@ export const GraphProvisioningHud = ({
} }
}, [presentation]); }, [presentation]);
const compactLabel = useMemo(() => { const ariaLabel = useMemo(() => {
if (!presentation?.compactDetail) { const parts = [presentation?.compactTitle, presentation?.compactDetail].filter(Boolean);
return null; return parts.join(' - ') || 'Open launch details';
} }, [presentation?.compactDetail, presentation?.compactTitle]);
return presentation.compactDetail.length > 54
? `${presentation.compactDetail.slice(0, 54)}...`
: presentation.compactDetail;
}, [presentation?.compactDetail]);
if (!shouldRender || !presentation || !tone) { if (!shouldRender || !presentation || !tone) {
return null; return null;
@ -131,44 +110,20 @@ export const GraphProvisioningHud = ({
tone.border tone.border
)} )}
onClick={() => setDetailsOpen(true)} onClick={() => setDetailsOpen(true)}
aria-label="Open launch details" aria-label={ariaLabel}
> >
<div className="flex min-w-0 items-center gap-2">
<span className={cn('shrink-0', tone.iconClassName)}>{tone.icon}</span>
<div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center gap-2">
<div className="truncate text-[11px] font-semibold text-slate-50">
{presentation.compactTitle}
</div>
<Badge variant="outline" className={cn('px-1.5 py-0 text-[10px]', tone.badge)}>
{presentation.isFailed
? 'Issue'
: presentation.hasMembersStillJoining
? 'Joining'
: presentation.isActive
? 'Live'
: 'Ready'}
</Badge>
</div>
{compactLabel ? (
<div className="mt-0.5 truncate text-[10px] leading-4 text-slate-300">
{compactLabel}
</div>
) : null}
</div>
</div>
<div <div
className="border-cyan-300/12 mt-2 overflow-hidden rounded-lg border bg-[rgba(4,10,20,0.58)] px-2 py-1.5" className="overflow-hidden rounded-lg border border-white/10 bg-[rgba(4,10,20,0.54)] px-2.5 py-1.5"
style={HUD_STEPPER_STYLE} style={HUD_STEPPER_STYLE}
> >
<StepProgressBar <StepProgressBar
steps={MINI_STEPS} steps={MINI_STEPS}
currentIndex={presentation.currentStepIndex} currentIndex={presentation.currentStepIndex}
errorIndex={errorStepIndex} errorIndex={errorStepIndex}
className="w-full" className="w-full origin-top scale-[0.9]"
/> />
</div> </div>
<span className="sr-only">{ariaLabel}</span>
</button> </button>
<Dialog open={detailsOpen} onOpenChange={setDetailsOpen}> <Dialog open={detailsOpen} onOpenChange={setDetailsOpen}>

View file

@ -118,7 +118,7 @@ describe('GraphProvisioningHud', () => {
await Promise.resolve(); await Promise.resolve();
}); });
const openButton = host.querySelector('button[aria-label="Open launch details"]'); const openButton = host.querySelector('button[aria-label]');
expect(openButton).not.toBeNull(); expect(openButton).not.toBeNull();
await act(async () => { await act(async () => {