fix(team-ui): polish launch diagnostics controls
This commit is contained in:
parent
7b99a3713b
commit
7e6ebce093
8 changed files with 411 additions and 432 deletions
|
|
@ -206,10 +206,8 @@ export const PluginsPanel = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="rounded-md border border-amber-500/30 bg-amber-500/5 px-4 py-3 text-sm text-amber-300">
|
<div className="rounded-md border border-amber-500/30 bg-amber-500/5 px-4 py-3 text-sm text-amber-300">
|
||||||
In the multimodel runtime, plugins are currently guaranteed only for Anthropic
|
Plugin support is currently guaranteed for Anthropic (Claude) sessions only.
|
||||||
sessions. We are actively building broader plugin support for all agents, including
|
We're working to support plugins across all agents.
|
||||||
both universal plugins and agent-specific plugins.
|
|
||||||
{capability.reason ? ` ${capability.reason}` : ''}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})()}
|
})()}
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,8 @@ export const SkipPermissionsCheckbox: React.FC<SkipPermissionsCheckboxProps> = (
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<Info className="mt-0.5 size-3.5 shrink-0 text-blue-400" />
|
<Info className="mt-0.5 size-3.5 shrink-0 text-blue-400" />
|
||||||
<p>
|
<p>
|
||||||
Unleash Claude's full power — no interruptions asking for permission. Autonomous
|
Autonomous mode: team tools execute without confirmation. Be cautious with untrusted
|
||||||
mode — all tools execute without confirmation. Be cautious with untrusted code.
|
code.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -57,7 +57,7 @@ export const SkipPermissionsCheckbox: React.FC<SkipPermissionsCheckboxProps> = (
|
||||||
>
|
>
|
||||||
<div className="flex items-start gap-2">
|
<div className="flex items-start gap-2">
|
||||||
<Info className="mt-0.5 size-3.5 shrink-0 text-blue-400" />
|
<Info className="mt-0.5 size-3.5 shrink-0 text-blue-400" />
|
||||||
<p>Manual mode — you'll approve or deny each tool call in real-time.</p>
|
<p>Manual mode: you'll approve or deny each tool call in real time.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1043,7 +1043,7 @@ export const MemberCard = memo(function MemberCard({
|
||||||
<MemberRuntimeTelemetryStrip runtimeEntry={runtimeEntry} scale={runtimeTelemetryScale} />
|
<MemberRuntimeTelemetryStrip runtimeEntry={runtimeEntry} scale={runtimeTelemetryScale} />
|
||||||
) : null}
|
) : null}
|
||||||
<div className="pointer-events-none absolute inset-0 z-10 rounded transition-colors group-hover:bg-white/5" />
|
<div className="pointer-events-none absolute inset-0 z-10 rounded transition-colors group-hover:bg-white/5" />
|
||||||
<div className="relative z-20 flex items-center gap-2.5">
|
<div className="relative z-20 grid grid-cols-[auto_minmax(0,1fr)_auto] items-center gap-x-2.5 gap-y-1">
|
||||||
<div className="relative shrink-0">
|
<div className="relative shrink-0">
|
||||||
<div
|
<div
|
||||||
className="rounded-full border-2 p-px"
|
className="rounded-full border-2 p-px"
|
||||||
|
|
@ -1166,6 +1166,7 @@ export const MemberCard = memo(function MemberCard({
|
||||||
<MemberLaunchDiagnosticsButton
|
<MemberLaunchDiagnosticsButton
|
||||||
payload={launchDiagnosticsPayload}
|
payload={launchDiagnosticsPayload}
|
||||||
className="size-auto rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200"
|
className="size-auto rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200"
|
||||||
|
attention
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|
@ -1201,336 +1202,348 @@ export const MemberCard = memo(function MemberCard({
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{launchFailureReason ? (
|
</div>
|
||||||
<div
|
<div className="flex shrink-0 items-center gap-2.5 justify-self-end">
|
||||||
data-testid="member-launch-failure-reason"
|
{showLaunchBadge ? (
|
||||||
className="mt-1 min-w-0 whitespace-pre-wrap break-words text-[10px] font-medium leading-snug text-red-300/90"
|
<span
|
||||||
title={rawLaunchFailureReason}
|
className="flex shrink-0 items-center gap-1"
|
||||||
|
title={runtimeEntry?.runtimeDiagnostic}
|
||||||
>
|
>
|
||||||
<span>
|
{launchVisualState === 'starting_stale' ? (
|
||||||
{renderLinkifiedText(launchFailureReason, {
|
<AlertTriangle
|
||||||
linkClassName: 'underline underline-offset-2 hover:text-red-200',
|
className="size-3.5 shrink-0 text-amber-400"
|
||||||
stopPropagation: true,
|
aria-label={launchBadgeLabel}
|
||||||
getLinkLabel: getLaunchFailureLinkLabel,
|
/>
|
||||||
})}
|
) : (
|
||||||
</span>
|
<SyncedLoader2
|
||||||
|
className="size-3.5 shrink-0 text-[var(--color-text-muted)]"
|
||||||
|
aria-label={launchBadgeLabel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="shrink-0 px-1.5 py-0.5 text-[10px] font-normal leading-none text-[var(--color-text-muted)]"
|
||||||
|
>
|
||||||
|
{launchBadgeLabel}
|
||||||
|
</Badge>
|
||||||
|
{canRelaunchOpenCode ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={
|
||||||
|
retryingLaunch ? restartActionBusyLabel : restartActionIdleLabel
|
||||||
|
}
|
||||||
|
className="rounded p-1 text-amber-300 transition-colors hover:bg-amber-500/10 hover:text-amber-200 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
disabled={retryingLaunch}
|
||||||
|
onClick={handleRestartMember}
|
||||||
|
>
|
||||||
|
{retryingLaunch ? (
|
||||||
|
<SyncedLoader2 className="size-3.5" />
|
||||||
|
) : (
|
||||||
|
<RotateCcw className="size-3.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{retryLaunchError ??
|
||||||
|
(retryingLaunch ? restartActionBusyLabel : restartActionIdleLabel)}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
) : showFailedLaunchBadge ? (
|
||||||
|
<span className="flex shrink-0 items-center gap-1">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="flex shrink-0 items-center gap-1">
|
||||||
|
<AlertTriangle className="size-3.5 shrink-0 text-red-400" />
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="shrink-0 bg-red-500/15 px-1.5 py-0.5 text-[10px] font-normal leading-none text-red-400"
|
||||||
|
>
|
||||||
|
{displayPresenceLabel}
|
||||||
|
</Badge>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">{spawnError ?? 'Spawn failed'}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
{showCopyDiagnostics ? (
|
||||||
|
<MemberLaunchDiagnosticsButton
|
||||||
|
payload={launchDiagnosticsPayload}
|
||||||
|
className="size-auto rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200"
|
||||||
|
attention
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{canSkipFailedLaunch ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={skippingLaunch ? 'Skipping teammate' : 'Skip for this launch'}
|
||||||
|
className="rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
disabled={skippingLaunch || retryingLaunch}
|
||||||
|
onClick={handleSkipFailedLaunch}
|
||||||
|
>
|
||||||
|
{skippingLaunch ? (
|
||||||
|
<SyncedLoader2 className="size-3.5" />
|
||||||
|
) : (
|
||||||
|
<Ban className="size-3.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{skipLaunchError ??
|
||||||
|
(skippingLaunch ? 'Skipping teammate...' : 'Skip for this launch')}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
{canRetryLaunch ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={
|
||||||
|
retryingLaunch ? restartActionBusyLabel : restartActionIdleLabel
|
||||||
|
}
|
||||||
|
className="rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
disabled={retryingLaunch || skippingLaunch}
|
||||||
|
onClick={handleRestartMember}
|
||||||
|
>
|
||||||
|
{retryingLaunch ? (
|
||||||
|
<SyncedLoader2 className="size-3.5" />
|
||||||
|
) : (
|
||||||
|
<RotateCcw className="size-3.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{retryLaunchError ??
|
||||||
|
(retryingLaunch ? `${restartActionBusyLabel}...` : restartActionIdleLabel)}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
) : showSkippedLaunchBadge ? (
|
||||||
|
<span className="flex shrink-0 items-center gap-1">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="flex shrink-0 items-center gap-1">
|
||||||
|
<Ban className="size-3.5 shrink-0 text-zinc-400" />
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="shrink-0 bg-zinc-500/15 px-1.5 py-0.5 text-[10px] font-normal leading-none text-zinc-300"
|
||||||
|
>
|
||||||
|
{displayPresenceLabel}
|
||||||
|
</Badge>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{spawnEntry?.skipReason ?? 'Skipped for this launch'}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
{canRetryLaunch ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={
|
||||||
|
retryingLaunch ? restartActionBusyLabel : restartActionIdleLabel
|
||||||
|
}
|
||||||
|
className="rounded p-1 text-zinc-300 transition-colors hover:bg-zinc-500/10 hover:text-zinc-100 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
disabled={retryingLaunch}
|
||||||
|
onClick={handleRestartMember}
|
||||||
|
>
|
||||||
|
{retryingLaunch ? (
|
||||||
|
<SyncedLoader2 className="size-3.5" />
|
||||||
|
) : (
|
||||||
|
<RotateCcw className="size-3.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{retryLaunchError ??
|
||||||
|
(retryingLaunch ? `${restartActionBusyLabel}...` : restartActionIdleLabel)}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
) : showRuntimeAdvisoryBadge ? (
|
||||||
|
<span className="flex shrink-0 items-center gap-1">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="flex shrink-0 items-center gap-1">
|
||||||
|
<AlertTriangle
|
||||||
|
className={`size-3.5 shrink-0 ${
|
||||||
|
runtimeAdvisoryTone === 'error' ? 'text-red-400' : 'text-amber-400'
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className={`shrink-0 px-1.5 py-0.5 text-[10px] font-normal leading-none ${
|
||||||
|
runtimeAdvisoryTone === 'error'
|
||||||
|
? 'bg-red-500/15 text-red-300'
|
||||||
|
: 'bg-amber-500/15 text-amber-300'
|
||||||
|
}`}
|
||||||
|
title={runtimeAdvisoryTitle}
|
||||||
|
>
|
||||||
|
{runtimeAdvisoryLabel}
|
||||||
|
</Badge>
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{runtimeAdvisoryTitle ?? runtimeAdvisoryLabel}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
{canRelaunchRuntimeAdvisoryOpenCode ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={
|
||||||
|
retryingLaunch ? restartActionBusyLabel : restartActionIdleLabel
|
||||||
|
}
|
||||||
|
className="rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200 disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
disabled={retryingLaunch}
|
||||||
|
onClick={handleRestartMember}
|
||||||
|
>
|
||||||
|
{retryingLaunch ? (
|
||||||
|
<SyncedLoader2 className="size-3.5" />
|
||||||
|
) : (
|
||||||
|
<RotateCcw className="size-3.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{retryLaunchError ??
|
||||||
|
(retryingLaunch ? `${restartActionBusyLabel}...` : restartActionIdleLabel)}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
{showRuntimeAdvisoryDiagnostics ? (
|
||||||
|
<MemberLaunchDiagnosticsButton
|
||||||
|
payload={launchDiagnosticsPayload}
|
||||||
|
className="size-auto rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200"
|
||||||
|
attention
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
|
) : !activityTask ? (
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className={`shrink-0 px-1.5 py-0.5 text-[10px] font-normal leading-none ${isRemoved ? 'bg-zinc-600 text-zinc-300' : 'text-[var(--color-text-muted)]'}`}
|
||||||
|
title={isRemoved ? 'This member has been removed' : activityTitle}
|
||||||
|
>
|
||||||
|
{isRemoved ? 'removed' : displayPresenceLabel}
|
||||||
|
</Badge>
|
||||||
|
) : null}
|
||||||
|
{showStartingSkeleton ? (
|
||||||
|
<div className="shrink-0" aria-hidden="true">
|
||||||
|
<div
|
||||||
|
className="skeleton-shimmer h-[18px] w-[62px] rounded-full border"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'var(--skeleton-base-dim)',
|
||||||
|
borderColor: 'var(--color-border)',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
className="skeleton-shimmer mx-1 mt-1 h-[2px] w-10 rounded-full"
|
||||||
|
style={{ backgroundColor: 'var(--skeleton-base)' }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
className="shrink-0"
|
||||||
|
title={totalTasks > 0 ? `${completed}/${totalTasks} completed` : undefined}
|
||||||
|
>
|
||||||
|
<Badge
|
||||||
|
variant="secondary"
|
||||||
|
className="shrink-0 px-1.5 py-0.5 text-[10px] font-normal leading-none"
|
||||||
|
>
|
||||||
|
{member.taskCount} {member.taskCount === 1 ? 'task' : 'tasks'}
|
||||||
|
</Badge>
|
||||||
|
{totalTasks > 0 && (
|
||||||
|
<div className="mx-0.5 mt-0.5 h-[2px] rounded-full bg-[var(--color-border)]">
|
||||||
|
<div
|
||||||
|
className="h-full rounded-full bg-emerald-500 transition-all duration-500"
|
||||||
|
style={{ width: `${progressPercent}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* NOTE: lead context bar disabled — usage formula is inaccurate */}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!isRemoved && (
|
||||||
|
<div className="flex shrink-0 items-center gap-0.5">
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded p-1 text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-surface)] hover:text-[var(--color-text)]"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onSendMessage?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MessageSquare size={13} />
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">Send message</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="rounded p-1 text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-surface)] hover:text-[var(--color-text)]"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onAssignTask?.();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Plus size={13} />
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">Assign task</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{canRestoreMember ? (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={restoringMember ? 'Restoring teammate' : 'Restore teammate'}
|
||||||
|
className="rounded p-1 text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-surface)] hover:text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-60"
|
||||||
|
disabled={restoringMember}
|
||||||
|
onClick={handleRestoreMember}
|
||||||
|
>
|
||||||
|
{restoringMember ? (
|
||||||
|
<SyncedLoader2 className="size-3.5" />
|
||||||
|
) : (
|
||||||
|
<Undo2 className="size-3.5" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{restoreMemberError ?? (restoringMember ? 'Restoring teammate...' : 'Restore')}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{showLaunchBadge ? (
|
{launchFailureReason ? (
|
||||||
<span
|
|
||||||
className="flex shrink-0 items-center gap-1"
|
|
||||||
title={runtimeEntry?.runtimeDiagnostic}
|
|
||||||
>
|
|
||||||
{launchVisualState === 'starting_stale' ? (
|
|
||||||
<AlertTriangle
|
|
||||||
className="size-3.5 shrink-0 text-amber-400"
|
|
||||||
aria-label={launchBadgeLabel}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<SyncedLoader2
|
|
||||||
className="size-3.5 shrink-0 text-[var(--color-text-muted)]"
|
|
||||||
aria-label={launchBadgeLabel}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className="shrink-0 px-1.5 py-0.5 text-[10px] font-normal leading-none text-[var(--color-text-muted)]"
|
|
||||||
>
|
|
||||||
{launchBadgeLabel}
|
|
||||||
</Badge>
|
|
||||||
{canRelaunchOpenCode ? (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={retryingLaunch ? restartActionBusyLabel : restartActionIdleLabel}
|
|
||||||
className="rounded p-1 text-amber-300 transition-colors hover:bg-amber-500/10 hover:text-amber-200 disabled:cursor-not-allowed disabled:opacity-60"
|
|
||||||
disabled={retryingLaunch}
|
|
||||||
onClick={handleRestartMember}
|
|
||||||
>
|
|
||||||
{retryingLaunch ? (
|
|
||||||
<SyncedLoader2 className="size-3.5" />
|
|
||||||
) : (
|
|
||||||
<RotateCcw className="size-3.5" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">
|
|
||||||
{retryLaunchError ??
|
|
||||||
(retryingLaunch ? restartActionBusyLabel : restartActionIdleLabel)}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
) : showFailedLaunchBadge ? (
|
|
||||||
<span className="flex shrink-0 items-center gap-1">
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<span className="flex shrink-0 items-center gap-1">
|
|
||||||
<AlertTriangle className="size-3.5 shrink-0 text-red-400" />
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className="shrink-0 bg-red-500/15 px-1.5 py-0.5 text-[10px] font-normal leading-none text-red-400"
|
|
||||||
>
|
|
||||||
{displayPresenceLabel}
|
|
||||||
</Badge>
|
|
||||||
</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">{spawnError ?? 'Spawn failed'}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
{showCopyDiagnostics ? (
|
|
||||||
<MemberLaunchDiagnosticsButton
|
|
||||||
payload={launchDiagnosticsPayload}
|
|
||||||
className="size-auto rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
{canSkipFailedLaunch ? (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={skippingLaunch ? 'Skipping teammate' : 'Skip for this launch'}
|
|
||||||
className="rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200 disabled:cursor-not-allowed disabled:opacity-60"
|
|
||||||
disabled={skippingLaunch || retryingLaunch}
|
|
||||||
onClick={handleSkipFailedLaunch}
|
|
||||||
>
|
|
||||||
{skippingLaunch ? (
|
|
||||||
<SyncedLoader2 className="size-3.5" />
|
|
||||||
) : (
|
|
||||||
<Ban className="size-3.5" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">
|
|
||||||
{skipLaunchError ??
|
|
||||||
(skippingLaunch ? 'Skipping teammate...' : 'Skip for this launch')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
{canRetryLaunch ? (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={retryingLaunch ? restartActionBusyLabel : restartActionIdleLabel}
|
|
||||||
className="rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200 disabled:cursor-not-allowed disabled:opacity-60"
|
|
||||||
disabled={retryingLaunch || skippingLaunch}
|
|
||||||
onClick={handleRestartMember}
|
|
||||||
>
|
|
||||||
{retryingLaunch ? (
|
|
||||||
<SyncedLoader2 className="size-3.5" />
|
|
||||||
) : (
|
|
||||||
<RotateCcw className="size-3.5" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">
|
|
||||||
{retryLaunchError ??
|
|
||||||
(retryingLaunch ? `${restartActionBusyLabel}...` : restartActionIdleLabel)}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
) : showSkippedLaunchBadge ? (
|
|
||||||
<span className="flex shrink-0 items-center gap-1">
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<span className="flex shrink-0 items-center gap-1">
|
|
||||||
<Ban className="size-3.5 shrink-0 text-zinc-400" />
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className="shrink-0 bg-zinc-500/15 px-1.5 py-0.5 text-[10px] font-normal leading-none text-zinc-300"
|
|
||||||
>
|
|
||||||
{displayPresenceLabel}
|
|
||||||
</Badge>
|
|
||||||
</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">
|
|
||||||
{spawnEntry?.skipReason ?? 'Skipped for this launch'}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
{canRetryLaunch ? (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={retryingLaunch ? restartActionBusyLabel : restartActionIdleLabel}
|
|
||||||
className="rounded p-1 text-zinc-300 transition-colors hover:bg-zinc-500/10 hover:text-zinc-100 disabled:cursor-not-allowed disabled:opacity-60"
|
|
||||||
disabled={retryingLaunch}
|
|
||||||
onClick={handleRestartMember}
|
|
||||||
>
|
|
||||||
{retryingLaunch ? (
|
|
||||||
<SyncedLoader2 className="size-3.5" />
|
|
||||||
) : (
|
|
||||||
<RotateCcw className="size-3.5" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">
|
|
||||||
{retryLaunchError ??
|
|
||||||
(retryingLaunch ? `${restartActionBusyLabel}...` : restartActionIdleLabel)}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
) : showRuntimeAdvisoryBadge ? (
|
|
||||||
<span className="flex shrink-0 items-center gap-1">
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<span className="flex shrink-0 items-center gap-1">
|
|
||||||
<AlertTriangle
|
|
||||||
className={`size-3.5 shrink-0 ${
|
|
||||||
runtimeAdvisoryTone === 'error' ? 'text-red-400' : 'text-amber-400'
|
|
||||||
}`}
|
|
||||||
/>
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className={`shrink-0 px-1.5 py-0.5 text-[10px] font-normal leading-none ${
|
|
||||||
runtimeAdvisoryTone === 'error'
|
|
||||||
? 'bg-red-500/15 text-red-300'
|
|
||||||
: 'bg-amber-500/15 text-amber-300'
|
|
||||||
}`}
|
|
||||||
title={runtimeAdvisoryTitle}
|
|
||||||
>
|
|
||||||
{runtimeAdvisoryLabel}
|
|
||||||
</Badge>
|
|
||||||
</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">
|
|
||||||
{runtimeAdvisoryTitle ?? runtimeAdvisoryLabel}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
{canRelaunchRuntimeAdvisoryOpenCode ? (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={retryingLaunch ? restartActionBusyLabel : restartActionIdleLabel}
|
|
||||||
className="rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200 disabled:cursor-not-allowed disabled:opacity-60"
|
|
||||||
disabled={retryingLaunch}
|
|
||||||
onClick={handleRestartMember}
|
|
||||||
>
|
|
||||||
{retryingLaunch ? (
|
|
||||||
<SyncedLoader2 className="size-3.5" />
|
|
||||||
) : (
|
|
||||||
<RotateCcw className="size-3.5" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">
|
|
||||||
{retryLaunchError ??
|
|
||||||
(retryingLaunch ? `${restartActionBusyLabel}...` : restartActionIdleLabel)}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
|
||||||
{showRuntimeAdvisoryDiagnostics ? (
|
|
||||||
<MemberLaunchDiagnosticsButton
|
|
||||||
payload={launchDiagnosticsPayload}
|
|
||||||
className="size-auto rounded p-1 text-red-300 transition-colors hover:bg-red-500/10 hover:text-red-200"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
) : !activityTask ? (
|
|
||||||
<Badge
|
|
||||||
variant="secondary"
|
|
||||||
className={`shrink-0 px-1.5 py-0.5 text-[10px] font-normal leading-none ${isRemoved ? 'bg-zinc-600 text-zinc-300' : 'text-[var(--color-text-muted)]'}`}
|
|
||||||
title={isRemoved ? 'This member has been removed' : activityTitle}
|
|
||||||
>
|
|
||||||
{isRemoved ? 'removed' : displayPresenceLabel}
|
|
||||||
</Badge>
|
|
||||||
) : null}
|
|
||||||
{showStartingSkeleton ? (
|
|
||||||
<div className="shrink-0" aria-hidden="true">
|
|
||||||
<div
|
|
||||||
className="skeleton-shimmer h-[18px] w-[62px] rounded-full border"
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'var(--skeleton-base-dim)',
|
|
||||||
borderColor: 'var(--color-border)',
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="skeleton-shimmer mx-1 mt-1 h-[2px] w-10 rounded-full"
|
|
||||||
style={{ backgroundColor: 'var(--skeleton-base)' }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div
|
<div
|
||||||
className="shrink-0"
|
data-testid="member-launch-failure-reason"
|
||||||
title={totalTasks > 0 ? `${completed}/${totalTasks} completed` : undefined}
|
className="col-span-2 col-start-2 min-w-0 whitespace-pre-wrap break-words text-[10px] font-medium leading-snug text-red-300/90"
|
||||||
|
title={rawLaunchFailureReason}
|
||||||
>
|
>
|
||||||
<Badge
|
<span>
|
||||||
variant="secondary"
|
{renderLinkifiedText(launchFailureReason, {
|
||||||
className="shrink-0 px-1.5 py-0.5 text-[10px] font-normal leading-none"
|
linkClassName: 'underline underline-offset-2 hover:text-red-200',
|
||||||
>
|
stopPropagation: true,
|
||||||
{member.taskCount} {member.taskCount === 1 ? 'task' : 'tasks'}
|
getLinkLabel: getLaunchFailureLinkLabel,
|
||||||
</Badge>
|
})}
|
||||||
{totalTasks > 0 && (
|
</span>
|
||||||
<div className="mx-0.5 mt-0.5 h-[2px] rounded-full bg-[var(--color-border)]">
|
|
||||||
<div
|
|
||||||
className="h-full rounded-full bg-emerald-500 transition-all duration-500"
|
|
||||||
style={{ width: `${progressPercent}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{/* NOTE: lead context bar disabled — usage formula is inaccurate */}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{!isRemoved && (
|
|
||||||
<div className="flex shrink-0 items-center gap-0.5">
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="rounded p-1 text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-surface)] hover:text-[var(--color-text)]"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onSendMessage?.();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MessageSquare size={13} />
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">Send message</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="rounded p-1 text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-surface)] hover:text-[var(--color-text)]"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onAssignTask?.();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Plus size={13} />
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">Assign task</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{canRestoreMember ? (
|
|
||||||
<Tooltip>
|
|
||||||
<TooltipTrigger asChild>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
aria-label={restoringMember ? 'Restoring teammate' : 'Restore teammate'}
|
|
||||||
className="rounded p-1 text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-surface)] hover:text-[var(--color-text)] disabled:cursor-not-allowed disabled:opacity-60"
|
|
||||||
disabled={restoringMember}
|
|
||||||
onClick={handleRestoreMember}
|
|
||||||
>
|
|
||||||
{restoringMember ? (
|
|
||||||
<SyncedLoader2 className="size-3.5" />
|
|
||||||
) : (
|
|
||||||
<Undo2 className="size-3.5" />
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent side="bottom">
|
|
||||||
{restoreMemberError ?? (restoringMember ? 'Restoring teammate...' : 'Restore')}
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1549,7 +1562,7 @@ export const MemberCard = memo(function MemberCard({
|
||||||
>
|
>
|
||||||
<TooltipTrigger asChild>{cardContent}</TooltipTrigger>
|
<TooltipTrigger asChild>{cardContent}</TooltipTrigger>
|
||||||
<TooltipContent
|
<TooltipContent
|
||||||
side="top"
|
side="left"
|
||||||
align="start"
|
align="start"
|
||||||
sideOffset={8}
|
sideOffset={8}
|
||||||
className="border-blue-400/20 bg-[var(--color-surface)] p-3 shadow-xl shadow-black/30"
|
className="border-blue-400/20 bg-[var(--color-surface)] p-3 shadow-xl shadow-black/30"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { useState } from 'react';
|
||||||
|
|
||||||
import { Button } from '@renderer/components/ui/button';
|
import { Button } from '@renderer/components/ui/button';
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@renderer/components/ui/tooltip';
|
||||||
|
import { cn } from '@renderer/lib/utils';
|
||||||
import {
|
import {
|
||||||
formatMemberLaunchDiagnosticsPayload,
|
formatMemberLaunchDiagnosticsPayload,
|
||||||
type MemberLaunchDiagnosticsPayload,
|
type MemberLaunchDiagnosticsPayload,
|
||||||
|
|
@ -13,6 +14,7 @@ interface MemberLaunchDiagnosticsButtonProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
size?: 'icon' | 'sm';
|
size?: 'icon' | 'sm';
|
||||||
|
attention?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MemberLaunchDiagnosticsButton = ({
|
export const MemberLaunchDiagnosticsButton = ({
|
||||||
|
|
@ -20,6 +22,7 @@ export const MemberLaunchDiagnosticsButton = ({
|
||||||
label,
|
label,
|
||||||
className,
|
className,
|
||||||
size = label ? 'sm' : 'icon',
|
size = label ? 'sm' : 'icon',
|
||||||
|
attention = false,
|
||||||
}: MemberLaunchDiagnosticsButtonProps): React.JSX.Element => {
|
}: MemberLaunchDiagnosticsButtonProps): React.JSX.Element => {
|
||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
|
@ -45,7 +48,7 @@ export const MemberLaunchDiagnosticsButton = ({
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size={size}
|
size={size}
|
||||||
className={className}
|
className={cn(className, attention && !copied && 'member-launch-diagnostics-pulse')}
|
||||||
title={tooltip}
|
title={tooltip}
|
||||||
aria-label={tooltip}
|
aria-label={tooltip}
|
||||||
onClick={copyDiagnostics}
|
onClick={copyDiagnostics}
|
||||||
|
|
|
||||||
|
|
@ -1056,6 +1056,44 @@ a[href],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes member-diagnostics-attention-fill {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
background-color: rgba(239, 68, 68, 0.1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background-color: rgba(239, 68, 68, 0.22);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes member-diagnostics-attention-ring {
|
||||||
|
0% {
|
||||||
|
opacity: 0.7;
|
||||||
|
transform: scale(0.92);
|
||||||
|
}
|
||||||
|
70%,
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(1.38);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-launch-diagnostics-pulse {
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
animation: member-diagnostics-attention-fill 1.7s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.member-launch-diagnostics-pulse::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -2px;
|
||||||
|
border: 1px solid rgba(248, 113, 113, 0.55);
|
||||||
|
border-radius: inherit;
|
||||||
|
pointer-events: none;
|
||||||
|
animation: member-diagnostics-attention-ring 1.7s ease-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
/* Skeleton-style shimmer for waiting members: a translucent light sweep */
|
/* Skeleton-style shimmer for waiting members: a translucent light sweep */
|
||||||
.member-waiting-shimmer {
|
.member-waiting-shimmer {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
@ -1653,6 +1691,8 @@ a[href],
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
.kanban-comment-badge-pulse,
|
.kanban-comment-badge-pulse,
|
||||||
|
.member-launch-diagnostics-pulse,
|
||||||
|
.member-launch-diagnostics-pulse::after,
|
||||||
.message-composer-orbit-path,
|
.message-composer-orbit-path,
|
||||||
.message-composer-orbit-glow {
|
.message-composer-orbit-glow {
|
||||||
animation: none;
|
animation: none;
|
||||||
|
|
|
||||||
|
|
@ -8,97 +8,6 @@
|
||||||
/>
|
/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" type="image/png" href="./favicon.png" />
|
<link rel="icon" type="image/png" href="./favicon.png" />
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/01.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/02.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/03.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/04.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/05.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/06.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/07.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/08.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/09.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/10.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/11.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/12.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<link
|
|
||||||
rel="preload"
|
|
||||||
as="image"
|
|
||||||
type="image/png"
|
|
||||||
href="./assets/participant-avatars/13.png"
|
|
||||||
fetchpriority="high"
|
|
||||||
/>
|
|
||||||
<title>Agent Teams AI</title>
|
<title>Agent Teams AI</title>
|
||||||
<style>
|
<style>
|
||||||
/* Splash: animated gradient background */
|
/* Splash: animated gradient background */
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import React, { act } from 'react';
|
import React, { act } from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
import type { CliInstallationStatus } from '@shared/types';
|
import type { CliInstallationStatus } from '@shared/types';
|
||||||
|
|
@ -214,7 +215,7 @@ describe('PluginsPanel effective runtime status', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(host.textContent).not.toContain(
|
expect(host.textContent).not.toContain(
|
||||||
'In the multimodel runtime, plugins currently apply only to Anthropic sessions.'
|
'Plugin support is currently guaranteed for Anthropic (Claude) sessions only.'
|
||||||
);
|
);
|
||||||
expect(host.textContent).not.toContain('Codex bootstrap placeholder');
|
expect(host.textContent).not.toContain('Codex bootstrap placeholder');
|
||||||
|
|
||||||
|
|
@ -224,7 +225,7 @@ describe('PluginsPanel effective runtime status', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('explains that broader plugin support for all agents is actively being built when Codex plugins are not supported yet', async () => {
|
it('explains that plugin support is guaranteed only for Anthropic sessions when Codex plugins are not supported yet', async () => {
|
||||||
const host = document.createElement('div');
|
const host = document.createElement('div');
|
||||||
document.body.appendChild(host);
|
document.body.appendChild(host);
|
||||||
const root = createRoot(host);
|
const root = createRoot(host);
|
||||||
|
|
@ -257,12 +258,10 @@ describe('PluginsPanel effective runtime status', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(host.textContent).toContain(
|
expect(host.textContent).toContain(
|
||||||
'plugins are currently guaranteed only for Anthropic sessions'
|
"Plugin support is currently guaranteed for Anthropic (Claude) sessions only. We're working to support plugins across all agents."
|
||||||
);
|
);
|
||||||
expect(host.textContent).toContain(
|
expect(host.textContent).not.toContain('multimodel runtime');
|
||||||
'We are actively building broader plugin support for all agents'
|
expect(host.textContent).not.toContain('Codex bootstrap placeholder');
|
||||||
);
|
|
||||||
expect(host.textContent).toContain('universal plugins and agent-specific plugins');
|
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
root.unmount();
|
root.unmount();
|
||||||
|
|
|
||||||
|
|
@ -73,11 +73,20 @@ vi.mock('@renderer/components/ui/tooltip', () => ({
|
||||||
React.createElement(React.Fragment, null, children),
|
React.createElement(React.Fragment, null, children),
|
||||||
TooltipContent: ({
|
TooltipContent: ({
|
||||||
children,
|
children,
|
||||||
|
side,
|
||||||
|
align,
|
||||||
className,
|
className,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
side?: string;
|
||||||
|
align?: string;
|
||||||
className?: string;
|
className?: string;
|
||||||
}) => React.createElement('div', { className, 'data-testid': 'tooltip-content' }, children),
|
}) =>
|
||||||
|
React.createElement(
|
||||||
|
'div',
|
||||||
|
{ className, 'data-align': align, 'data-side': side, 'data-testid': 'tooltip-content' },
|
||||||
|
children
|
||||||
|
),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('@renderer/hooks/useTheme', () => ({
|
vi.mock('@renderer/hooks/useTheme', () => ({
|
||||||
|
|
@ -866,6 +875,10 @@ describe('MemberCard starting-state visuals', () => {
|
||||||
expect(
|
expect(
|
||||||
host.querySelector('[data-testid="tooltip-root"][data-delay-duration="0"]')
|
host.querySelector('[data-testid="tooltip-root"][data-delay-duration="0"]')
|
||||||
).not.toBeNull();
|
).not.toBeNull();
|
||||||
|
const runtimeTooltipContent = Array.from(
|
||||||
|
host.querySelectorAll('[data-testid="tooltip-content"]')
|
||||||
|
).find((content) => content.className.includes('border-blue-400/20'));
|
||||||
|
expect(runtimeTooltipContent?.getAttribute('data-side')).toBe('left');
|
||||||
expect(host.querySelector('[data-testid="tooltip-root"]')?.getAttribute('data-open')).toBe(
|
expect(host.querySelector('[data-testid="tooltip-root"]')?.getAttribute('data-open')).toBe(
|
||||||
'false'
|
'false'
|
||||||
);
|
);
|
||||||
|
|
@ -1148,6 +1161,7 @@ describe('MemberCard starting-state visuals', () => {
|
||||||
|
|
||||||
const button = host.querySelector('[aria-label="Copy diagnostics"]') as HTMLButtonElement;
|
const button = host.querySelector('[aria-label="Copy diagnostics"]') as HTMLButtonElement;
|
||||||
expect(button).not.toBeNull();
|
expect(button).not.toBeNull();
|
||||||
|
expect(button.className).toContain('member-launch-diagnostics-pulse');
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
button.click();
|
button.click();
|
||||||
|
|
@ -1163,6 +1177,7 @@ describe('MemberCard starting-state visuals', () => {
|
||||||
expect(payload.runId).toBe('run-42');
|
expect(payload.runId).toBe('run-42');
|
||||||
expect(payload.livenessKind).toBe('not_found');
|
expect(payload.livenessKind).toBe('not_found');
|
||||||
expect(payload.processCommand).toContain('--token [redacted]');
|
expect(payload.processCommand).toContain('--token [redacted]');
|
||||||
|
expect(button.className).not.toContain('member-launch-diagnostics-pulse');
|
||||||
|
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
root.unmount();
|
root.unmount();
|
||||||
|
|
@ -1233,6 +1248,8 @@ describe('MemberCard starting-state visuals', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const failureReason = host.querySelector('[data-testid="member-launch-failure-reason"]');
|
const failureReason = host.querySelector('[data-testid="member-launch-failure-reason"]');
|
||||||
|
expect(failureReason?.className).toContain('col-start-2');
|
||||||
|
expect(failureReason?.className).toContain('col-span-2');
|
||||||
expect(failureReason?.textContent).toContain('Insufficient credits');
|
expect(failureReason?.textContent).toContain('Insufficient credits');
|
||||||
expect(failureReason?.textContent).toContain('OpenRouter credits');
|
expect(failureReason?.textContent).toContain('OpenRouter credits');
|
||||||
expect(failureReason?.textContent).not.toContain('Latest assistant message');
|
expect(failureReason?.textContent).not.toContain('Latest assistant message');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue