fix(ux): remove dup Signal btn, add minimize to all feeds, strip shows recent events

- Remove duplicate Signal (telemetry) button from DAG nodes
- Add minimize (ChevronLeft) button to Epic Command Feed view, not just global feed
- TelemetryStrip now shows 8 most recently updated tasks as status-colored dots
  instead of static status counts — reflects live activity like the full feed does
- Each dot is colored by task status (blocked=red, active=amber, ready=green)
  with hover tooltip showing task id, title, and status

Co-Authored-By: Oz <oz-agent@warp.dev>
This commit is contained in:
zenchantlive 2026-03-01 18:20:49 -08:00
parent c246ceaf21
commit a0787f85de
4 changed files with 89 additions and 49 deletions

View file

@ -3002,3 +3002,35 @@ time="2026-03-01T18:17:37-08:00" level=info msg=NewConnection DisableClientMulti
time="2026-03-01T18:17:37-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1183
time="2026-03-01T18:17:37-08:00" level=info msg=ConnectionClosed connectionID=1182
time="2026-03-01T18:17:38-08:00" level=info msg=ConnectionClosed connectionID=1183
time="2026-03-01T18:18:37-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1184
time="2026-03-01T18:18:39-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1185
time="2026-03-01T18:18:39-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1186
time="2026-03-01T18:18:39-08:00" level=warning msg="Cannot read client handshake response from client 1186 (127.0.0.1:62203): read tcp 127.0.0.1:3307->127.0.0.1:62203: wsarecv: An existing connection was forcibly closed by the remote host.\nio.ReadFull(header size) failed, it may not be a valid MySQL client"
time="2026-03-01T18:18:39-08:00" level=info msg=ConnectionClosed connectionID=1186
time="2026-03-01T18:18:39-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1187
time="2026-03-01T18:18:39-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1188
time="2026-03-01T18:18:39-08:00" level=info msg=ConnectionClosed connectionID=1187
time="2026-03-01T18:18:39-08:00" level=info msg=ConnectionClosed connectionID=1188
time="2026-03-01T18:18:40-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1189
time="2026-03-01T18:18:40-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1190
time="2026-03-01T18:18:40-08:00" level=info msg=ConnectionClosed connectionID=1190
time="2026-03-01T18:18:40-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1191
time="2026-03-01T18:18:40-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1192
time="2026-03-01T18:18:40-08:00" level=info msg=ConnectionClosed connectionID=1191
time="2026-03-01T18:18:40-08:00" level=info msg=ConnectionClosed connectionID=1192
time="2026-03-01T18:18:43-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1193
time="2026-03-01T18:18:43-08:00" level=warning msg="Cannot read client handshake response from client 1193 (127.0.0.1:51203): read tcp 127.0.0.1:3307->127.0.0.1:51203: wsarecv: An existing connection was forcibly closed by the remote host.\nio.ReadFull(header size) failed, it may not be a valid MySQL client"
time="2026-03-01T18:18:43-08:00" level=info msg=ConnectionClosed connectionID=1193
time="2026-03-01T18:18:43-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1194
time="2026-03-01T18:18:43-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1195
time="2026-03-01T18:18:43-08:00" level=info msg=ConnectionClosed connectionID=1194
time="2026-03-01T18:18:43-08:00" level=info msg=ConnectionClosed connectionID=1195
time="2026-03-01T18:18:43-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1196
time="2026-03-01T18:18:43-08:00" level=warning msg="Cannot read client handshake response from client 1196 (127.0.0.1:51207): read tcp 127.0.0.1:3307->127.0.0.1:51207: wsarecv: An existing connection was forcibly closed by the remote host.\nio.ReadFull(header size) failed, it may not be a valid MySQL client"
time="2026-03-01T18:18:43-08:00" level=info msg=ConnectionClosed connectionID=1196
time="2026-03-01T18:18:43-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1197
time="2026-03-01T18:18:43-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1198
time="2026-03-01T18:18:43-08:00" level=info msg=ConnectionClosed connectionID=1197
time="2026-03-01T18:18:44-08:00" level=info msg=ConnectionClosed connectionID=1198
time="2026-03-01T18:20:06-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1199
time="2026-03-01T18:20:29-08:00" level=info msg=NewConnection DisableClientMultiStatements=false connectionID=1200

View file

@ -43,11 +43,29 @@ export function ContextualRightPanel({ epicId, taskId, swarmId, issues, projectR
if (epicId) {
return (
<SwarmCommandFeed
epicId={epicId}
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)]">Epic Command 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">
<SwarmCommandFeed
epicId={epicId}
issues={issues}
projectRoot={projectRoot}
/>
</div>
</div>
);
}

View file

@ -294,16 +294,6 @@ export function GraphNodeCard({ id, data, selected }: NodeProps<Node<GraphNodeDa
<Signal className="h-3 w-3" />
</button>
) : null}
{onViewTelemetry ? (
<button
type="button"
onClick={(e) => { e.stopPropagation(); onViewTelemetry(id); }}
className="rounded p-0.5 text-[var(--accent-info)]/50 transition-colors hover:text-[var(--accent-info)] hover:bg-[var(--alpha-white-low)]"
title="Live feed"
>
<Signal className="h-3 w-3" />
</button>
) : null}
</div>
<div className="flex items-center gap-1.5 flex-wrap">
{assignedArchetypes.map((archetype) => (

View file

@ -1,6 +1,7 @@
'use client';
import { ChevronRight } from 'lucide-react';
import { useMemo } from 'react';
import { Signal } from 'lucide-react';
import type { BeadIssue } from '../../lib/types';
interface TelemetryStripProps {
@ -8,53 +9,52 @@ interface TelemetryStripProps {
onMaximize: () => void;
}
interface Dot {
color: string;
glow: string;
count: number;
label: string;
function dotColor(status: BeadIssue['status']): { bg: string; glow: string } {
switch (status) {
case 'blocked': return { bg: 'var(--accent-danger)', glow: 'rgba(255,76,114,0.4)' };
case 'in_progress': return { bg: 'var(--accent-warning)', glow: 'rgba(255,178,74,0.4)' };
case 'open': return { bg: 'var(--accent-success)', glow: 'rgba(53,217,143,0.4)' };
case 'closed': return { bg: 'var(--text-tertiary)', glow: 'transparent' };
default: return { bg: 'var(--accent-info)', glow: 'rgba(125,211,252,0.3)' };
}
}
export function TelemetryStrip({ issues, onMaximize }: TelemetryStripProps) {
const tasks = issues.filter((i) => i.issue_type !== 'epic');
const blocked = tasks.filter((i) => i.status === 'blocked').length;
const active = tasks.filter((i) => i.status === 'in_progress').length;
const ready = tasks.filter((i) => i.status === 'open').length;
const done = tasks.filter((i) => i.status === 'closed').length;
const dots: Dot[] = [
{ color: 'var(--accent-danger)', glow: 'rgba(255,76,114,0.4)', count: blocked, label: 'blocked' },
{ color: 'var(--accent-warning)', glow: 'rgba(255,178,74,0.4)', count: active, label: 'active' },
{ color: 'var(--accent-success)', glow: 'rgba(53,217,143,0.4)', count: ready, label: 'ready' },
{ color: 'var(--text-tertiary)', glow: 'transparent', count: done, label: 'done' },
];
// Show the 8 most recently updated tasks as live dots
const recentTasks = useMemo(() => {
return [...issues]
.filter((i) => i.issue_type !== 'epic')
.sort((a, b) => new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime())
.slice(0, 8);
}, [issues]);
return (
<div className="flex h-full w-9 flex-shrink-0 flex-col items-center border-l border-[var(--border-subtle)] bg-[var(--surface-primary)] py-2">
<button
type="button"
onClick={onMaximize}
className="mb-3 rounded p-1 text-[var(--text-tertiary)] transition-colors hover:bg-[var(--alpha-white-low)] hover:text-[var(--text-primary)]"
className="mb-2 rounded p-1 text-[var(--accent-info)] transition-colors hover:bg-[var(--alpha-white-low)] hover:text-[var(--text-primary)]"
title="Restore live feed"
aria-label="Restore live feed"
>
<ChevronRight className="h-3.5 w-3.5" />
<Signal className="h-3.5 w-3.5" />
</button>
<div className="flex flex-col items-center gap-3">
{dots.map((dot) => (
<div key={dot.label} className="flex flex-col items-center gap-0.5" title={`${dot.count} ${dot.label}`}>
<span
className="h-2.5 w-2.5 rounded-full transition-all"
style={{
backgroundColor: dot.color,
boxShadow: dot.count > 0 ? `0 0 6px 1px ${dot.glow}` : 'none',
opacity: dot.count > 0 ? 1 : 0.25,
}}
/>
<span className="font-mono text-[8px] text-[var(--text-tertiary)]">{dot.count}</span>
</div>
))}
<div className="flex flex-1 flex-col items-center gap-2 overflow-hidden">
{recentTasks.map((task) => {
const { bg, glow } = dotColor(task.status);
return (
<div key={task.id} className="flex flex-col items-center" title={`${task.id}: ${task.title} (${task.status})`}>
<span
className="h-2 w-2 rounded-full"
style={{
backgroundColor: bg,
boxShadow: `0 0 5px 1px ${glow}`,
}}
/>
</div>
);
})}
</div>
</div>
);