feat(ux): consolidate Launch Swarm + telemetry UX with minimized strip

- Removed broken LaunchSwarmDialog (formula-based) from TopBar/LeftPanel
- All Rocket buttons (TopBar, LeftPanel, DAG nodes, social cards) now open
  AssignmentPanel (archetype-based) which actually works
- Every Rocket clears taskId first so assignMode && !taskId condition passes
- Conversation button priority: taskId always shows conversation, not assign panel
- Added TelemetryStrip: minimized right sidebar with status dots when non-telemetry
  panel (conversation/assignment) is active
- Live feed has minimize button → restores last taskId or assignMode
- DAG nodes: Signal icon → restores telemetry feed
- Social button on DAG nodes: single router.push to avoid race (setView + setTaskId)
- Fixed social card message button: opens right panel with drawer:closed (no popup)

Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
zenchantlive 2026-03-01 18:17:58 -08:00
parent 65d69ecbbc
commit c246ceaf21
165 changed files with 13730 additions and 1132 deletions

View file

@ -111,25 +111,25 @@ export function formatRelativeTime(timestamp: string): string {
function getAgentTone(status: AgentStatus): AgentTone {
const tones: Record<AgentStatus, AgentTone> = {
active: {
cardClass: 'bg-[#173126]',
cardClass: 'bg-[var(--status-ready)]',
labelClass: 'text-[#7CB97A]',
ringClass: 'ring-[#7CB97A]/45',
glowClass: 'bg-[#7CB97A]/30',
},
stale: {
cardClass: 'bg-[#322817]',
cardClass: 'bg-[var(--status-in-progress)]',
labelClass: 'text-[#D4A574]',
ringClass: 'ring-[#D4A574]/45',
glowClass: 'bg-[#D4A574]/30',
},
stuck: {
cardClass: 'bg-[#341a1f]',
cardClass: 'bg-[var(--status-blocked)]',
labelClass: 'text-[#C97A7A]',
ringClass: 'ring-[#C97A7A]/45',
glowClass: 'bg-[#C97A7A]/30',
},
dead: {
cardClass: 'bg-[#2b232b]',
cardClass: 'bg-[var(--surface-primary)]',
labelClass: 'text-[#A78A94]',
ringClass: 'ring-[#A78A94]/40',
glowClass: 'bg-[#A78A94]/25',
@ -147,84 +147,84 @@ export function getEventTone(kind: string): EventTone {
label: 'Created',
labelClass: 'text-[#7CB97A]',
dotClass: 'bg-[#7CB97A]',
cardClass: 'bg-[#182f25]',
cardClass: 'bg-[var(--status-ready)]',
idClass: 'text-[#9ACB98]',
},
opened: {
label: 'Opened',
labelClass: 'text-[#7CB97A]',
dotClass: 'bg-[#7CB97A]',
cardClass: 'bg-[#182f25]',
cardClass: 'bg-[var(--status-ready)]',
idClass: 'text-[#9ACB98]',
},
closed: {
label: 'Closed',
labelClass: 'text-[#D4A574]',
dotClass: 'bg-[#D4A574]',
cardClass: 'bg-[#332716]',
cardClass: 'bg-[var(--status-in-progress)]',
idClass: 'text-[#DAB891]',
},
reopened: {
label: 'Reopened',
labelClass: 'text-[#5B95E8]',
dotClass: 'bg-[#5B95E8]',
cardClass: 'bg-[#1b2b43]',
cardClass: 'bg-[var(--surface-primary)]',
idClass: 'text-[#8DB4EF]',
},
status_changed: {
label: 'Status changed',
labelClass: 'text-[#D4A574]',
dotClass: 'bg-[#D4A574]',
cardClass: 'bg-[#2f2518]',
cardClass: 'bg-[var(--status-in-progress)]',
idClass: 'text-[#DAB891]',
},
priority_changed: {
label: 'Priority changed',
labelClass: 'text-[#D4A574]',
dotClass: 'bg-[#D4A574]',
cardClass: 'bg-[#2f2518]',
cardClass: 'bg-[var(--status-in-progress)]',
idClass: 'text-[#DAB891]',
},
assignee_changed: {
label: 'Assigned',
labelClass: 'text-[#D4A574]',
dotClass: 'bg-[#D4A574]',
cardClass: 'bg-[#2f2518]',
cardClass: 'bg-[var(--status-in-progress)]',
idClass: 'text-[#DAB891]',
},
dependency_added: {
label: 'Dependency added',
labelClass: 'text-[#D4A574]',
dotClass: 'bg-[#D4A574]',
cardClass: 'bg-[#2f2518]',
cardClass: 'bg-[var(--status-in-progress)]',
idClass: 'text-[#DAB891]',
},
dependency_removed: {
label: 'Dependency removed',
labelClass: 'text-[#C97A7A]',
dotClass: 'bg-[#C97A7A]',
cardClass: 'bg-[#321b21]',
cardClass: 'bg-[var(--status-blocked)]',
idClass: 'text-[#D9A9A9]',
},
heartbeat: {
label: 'Heartbeat',
labelClass: 'text-[#5BA8A0]',
dotClass: 'bg-[#5BA8A0]',
cardClass: 'bg-[#173034]',
cardClass: 'bg-[var(--surface-primary)]',
idClass: 'text-[#8BC9C1]',
},
commented: {
label: 'Commented',
labelClass: 'text-[#5BA8A0]',
dotClass: 'bg-[#5BA8A0]',
cardClass: 'bg-[#173034]',
cardClass: 'bg-[var(--surface-primary)]',
idClass: 'text-[#8BC9C1]',
},
comment_added: {
label: 'Commented',
labelClass: 'text-[#5BA8A0]',
dotClass: 'bg-[#5BA8A0]',
cardClass: 'bg-[#173034]',
cardClass: 'bg-[var(--surface-primary)]',
idClass: 'text-[#8BC9C1]',
},
};
@ -234,7 +234,7 @@ export function getEventTone(kind: string): EventTone {
label: normalized.replace(/_/g, ' '),
labelClass: 'text-[#5BA8A0]',
dotClass: 'bg-[#5BA8A0]',
cardClass: 'bg-[#173034]',
cardClass: 'bg-[var(--surface-primary)]',
idClass: 'text-[#8BC9C1]',
}
);
@ -299,7 +299,7 @@ export function ActivityPanel({ issues, collapsed = false, projectRoot }: Activi
const activeAgents = agentRoster.filter(a => a.status === 'active').length;
if (collapsed) {
return (
<div className="flex flex-col items-center gap-6 py-6 h-full bg-[linear-gradient(180deg,rgba(0,0,0,0.2),rgba(0,0,0,0.36))] shadow-[inset_10px_0_22px_-20px_rgba(0,0,0,0.9)]">
<div className="flex flex-col items-center gap-6 py-6 h-full bg-[var(--surface-secondary)]">
{/* Collapsed Agent Icons with ZFC Rings */}
<div className="flex flex-col gap-4">
{agentRoster.slice(0, 6).map(agent => (
@ -316,7 +316,7 @@ export function ActivityPanel({ issues, collapsed = false, projectRoot }: Activi
agent.status === 'stale' ? 'ring-[#D4A574]/45' :
agent.status === 'stuck' ? 'ring-[#C97A7A]/45' : 'ring-[#A78A94]/40'
)}>
<AvatarFallback className="text-[10px] font-bold bg-[#1a1a1a] text-text-muted">
<AvatarFallback className="text-[10px] font-bold bg-[var(--surface-primary)] text-text-muted">
{getInitials(agent.name)}
</AvatarFallback>
</Avatar>
@ -340,9 +340,9 @@ export function ActivityPanel({ issues, collapsed = false, projectRoot }: Activi
}
return (
<div className="flex flex-col h-full bg-[#070f19] backdrop-blur-xl">
<div className="flex flex-col h-full bg-[var(--surface-secondary)]">
{/* AGENT ROSTER SECTION */}
<div className="flex-shrink-0 p-4 bg-[#0b1625] shadow-[0_16px_24px_-24px_rgba(0,0,0,0.9)]">
<div className="flex-shrink-0 p-4 bg-[var(--surface-primary)] border-b border-[var(--border-subtle)]">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse shadow-[0_0_8px_#10b981]" />
@ -368,7 +368,7 @@ export function ActivityPanel({ issues, collapsed = false, projectRoot }: Activi
getAgentTone(agent.status).glowClass
)} />
<Avatar className={cn("h-8 w-8 relative z-10 ring-1", getAgentTone(agent.status).ringClass)}>
<AvatarFallback className="text-[10px] font-bold bg-[#252525]">
<AvatarFallback className="text-[10px] font-bold bg-[var(--surface-primary)]">
{getInitials(agent.name)}
</AvatarFallback>
</Avatar>

View file

@ -1,6 +1,7 @@
'use client';
import React from 'react';
import { ChevronLeft } from 'lucide-react';
import type { BeadIssue } from '../../lib/types';
import { ActivityPanel } from './activity-panel';
import { SwarmCommandFeed } from './swarm-command-feed';
@ -15,9 +16,11 @@ export interface ContextualRightPanelProps {
swarmId?: string | null;
issues: BeadIssue[];
projectRoot: string;
actor?: string;
onMinimize?: () => void;
}
export function ContextualRightPanel({ epicId, taskId, swarmId, issues, projectRoot }: ContextualRightPanelProps) {
export function ContextualRightPanel({ epicId, taskId, swarmId, issues, projectRoot, actor, onMinimize }: ContextualRightPanelProps) {
const { setTaskId } = useUrlState();
// Task conversation takes priority — user explicitly clicked the conversation icon
@ -32,6 +35,7 @@ export function ContextualRightPanel({ epicId, taskId, swarmId, issues, projectR
id={taskId}
issue={selectedIssue}
projectRoot={projectRoot}
actor={actor}
onIssueUpdated={async () => {}}
/>
);
@ -58,10 +62,28 @@ export function ContextualRightPanel({ epicId, taskId, swarmId, issues, projectR
// Fallback to Global feed
return (
<ActivityPanel
issues={issues}
projectRoot={projectRoot}
/>
<div className="flex h-full flex-col overflow-hidden bg-[var(--surface-primary)]">
{onMinimize && (
<div className="flex shrink-0 items-center justify-between border-b border-[var(--border-subtle)] px-3 py-2">
<span className="text-[10px] font-semibold uppercase tracking-[0.1em] text-[var(--text-tertiary)]">Live Activity Feed</span>
<button
type="button"
onClick={onMinimize}
className="rounded p-1 text-[var(--text-tertiary)] transition-colors hover:bg-[var(--alpha-white-low)] hover:text-[var(--text-primary)]"
aria-label="Minimize to telemetry"
title="Minimize to telemetry"
>
<ChevronLeft className="h-3.5 w-3.5" />
</button>
</div>
)}
<div className="min-h-0 flex-1 overflow-hidden">
<ActivityPanel
issues={issues}
projectRoot={projectRoot}
/>
</div>
</div>
);
}

View file

@ -52,7 +52,24 @@ export function SwarmCommandFeed({ epicId, issues, projectRoot }: SwarmCommandFe
return entries;
}, [contextBeads, archetypes]);
// 3. Subscribe to real-time activity, filtering ONLY for this epic's children
// 3. Load historical activity filtered to this epic's children
useEffect(() => {
if (contextBeadIds.size === 0) return;
async function loadHistory() {
try {
const res = await fetch(`/api/activity?projectRoot=${encodeURIComponent(projectRoot)}`);
if (!res.ok) return;
const data = (await res.json()) as ActivityEvent[];
const filtered = data.filter(e => e?.beadId && contextBeadIds.has(e.beadId));
setActivities(filtered.slice(0, 100));
} catch {
// ignore
}
}
void loadHistory();
}, [projectRoot, contextBeadIds]);
// 4. Subscribe to real-time activity, filtering ONLY for this epic's children
useEffect(() => {
const source = new EventSource(`/api/events?projectRoot=${encodeURIComponent(projectRoot)}`);
@ -76,9 +93,9 @@ export function SwarmCommandFeed({ epicId, issues, projectRoot }: SwarmCommandFe
}, [projectRoot, contextBeadIds]);
return (
<div className="flex flex-col h-full bg-[#050a10] border-l border-[var(--ui-border-soft)]">
<div className="flex flex-col h-full bg-[var(--surface-secondary)] border-l border-[var(--ui-border-soft)]">
{/* SQUAD ROSTER SECTION */}
<div className="flex-shrink-0 p-4 bg-[#0a111a] shadow-[0_16px_24px_-24px_rgba(0,0,0,0.9)] z-10">
<div className="flex-shrink-0 p-4 bg-[var(--surface-primary)] shadow-[0_16px_24px_-24px_rgba(0,0,0,0.9)] z-10">
<div className="flex items-center justify-between mb-4">
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse shadow-[0_0_8px_#10b981]" />
@ -96,7 +113,7 @@ export function SwarmCommandFeed({ epicId, issues, projectRoot }: SwarmCommandFe
) : (
<div className="grid grid-cols-1 gap-2">
{rosterEntries.map((agent, i) => (
<div key={i} className="flex gap-3 p-2.5 bg-[#0f1824] border border-[var(--ui-border-soft)] rounded-xl items-center shadow-lg transition-all hover:border-[var(--ui-accent-info)]/30">
<div key={i} className="flex gap-3 p-2.5 bg-[var(--surface-elevated)] border border-[var(--ui-border-soft)] rounded-xl items-center shadow-lg transition-all hover:border-[var(--ui-accent-info)]/30">
<div className="relative">
<div className="absolute -inset-0.5 rounded-full blur-[2px] opacity-70 bg-emerald-500/20" />
<Avatar className="h-9 w-9 relative z-10 ring-2 ring-emerald-500/40">