feat(8ij.4): add LaunchSwarmDialog to TopBar as global action

Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
zenchantlive 2026-03-01 17:22:25 -08:00
parent 6b8aa408c8
commit ae7f13c3af
2 changed files with 61 additions and 2 deletions

View file

@ -1,10 +1,11 @@
'use client';
import { ReactNode } from 'react';
import { LayoutGrid, Lock, Plus, Sidebar, SidebarClose } from 'lucide-react';
import { ReactNode, useState } from 'react';
import { LayoutGrid, Lock, Plus, Sidebar, SidebarClose, Rocket } from 'lucide-react';
import { useUrlState } from '../../hooks/use-url-state';
import { useResponsive } from '../../hooks/use-responsive';
import { ThemeToggle } from './theme-toggle';
import { LaunchSwarmDialog } from '../swarm/launch-dialog';
export interface TopBarProps {
onCreateTask?: () => Promise<void> | void;
@ -15,6 +16,9 @@ export interface TopBarProps {
criticalAlerts?: number;
idleCount?: number;
busyCount?: number;
actor?: string;
onActorChange?: (name: string) => void;
projectRoot?: string;
}
interface MetricTileProps {
@ -42,6 +46,36 @@ function MetricTile({ label, value, accent = 'info' }: MetricTileProps) {
);
}
function IdentityChip({ actor, onActorChange }: { actor: string; onActorChange: (name: string) => void }) {
const [editing, setEditing] = useState(false);
if (editing) {
return (
<input
autoFocus
type="text"
value={actor}
onChange={e => onActorChange(e.target.value)}
onBlur={() => setEditing(false)}
onKeyDown={e => { if (e.key === 'Enter') setEditing(false); }}
placeholder="your name"
className="h-7 w-28 rounded-full border border-[var(--accent-info)] bg-[var(--surface-tertiary)] px-3 text-xs text-[var(--text-primary)] outline-none"
/>
);
}
return (
<button
type="button"
onClick={() => setEditing(true)}
title="Set your operator name"
className="inline-flex h-7 items-center rounded-full border border-[var(--border-subtle)] bg-[var(--surface-tertiary)] px-3 text-xs text-[var(--text-secondary)] transition-colors hover:border-[var(--accent-info)] hover:text-[var(--text-primary)]"
>
{actor || <span className="text-[var(--text-tertiary)]">your name</span>}
</button>
);
}
export function TopBar({
onCreateTask,
isCreatingTask = false,
@ -51,9 +85,13 @@ export function TopBar({
criticalAlerts = 0,
idleCount = 0,
busyCount = 0,
actor = '',
onActorChange,
projectRoot,
}: TopBarProps) {
const { leftPanel, toggleLeftPanel, rightPanel, toggleRightPanel, blockedOnly, toggleBlockedOnly } = useUrlState();
const { isDesktop } = useResponsive();
const [showLaunchSwarm, setShowLaunchSwarm] = useState(false);
return (
<header className="flex h-[var(--topbar-height)] items-center justify-between border-b border-[var(--border-strong)] bg-[var(--surface-elevated)]" data-testid="top-bar">
@ -113,6 +151,17 @@ export function TopBar({
</span>
</button>
{projectRoot && (
<button
type="button"
onClick={() => setShowLaunchSwarm(true)}
className="inline-flex items-center gap-2 rounded-xl border border-emerald-500/20 bg-emerald-500/10 px-4 py-2 text-xs font-semibold uppercase tracking-[0.11em] text-emerald-400 transition-colors hover:bg-emerald-500/20 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-info)]"
>
<Rocket className="h-3.5 w-3.5" aria-hidden="true" />
Launch Swarm
</button>
)}
<button
type="button"
onClick={() => {
@ -128,6 +177,8 @@ export function TopBar({
</>
)}
{onActorChange ? <IdentityChip actor={actor} onActorChange={onActorChange} /> : null}
<ThemeToggle />
{isDesktop ? (
@ -147,6 +198,13 @@ export function TopBar({
{taskActionMessage ?? ''}
</span>
</div>
{showLaunchSwarm && projectRoot && (
<LaunchSwarmDialog
projectRoot={projectRoot}
onSuccess={() => setShowLaunchSwarm(false)}
/>
)}
</header>
);
}

View file

@ -192,6 +192,7 @@ export function UnifiedShell({
idleCount={0}
actor={actor}
onActorChange={handleActorChange}
projectRoot={projectRoot}
/>
{!bdHealth.loading && !bdHealth.healthy ? (
<div className="border-b border-amber-500/35 bg-amber-500/12 px-4 py-2 text-xs text-amber-100">