Component fan-out + integration of the vibe-kanban-inspired redesign:
- Board.tsx + TaskCard.tsx: read-only Kanban (one column per backend stage,
fallback grouping by phase), toolbar (search/status/tree filters + node-filter
chip), gate columns flagged; filter logic lifted to taskFilters.ts (tested).
- MapNode.tsx + PipelineMap.tsx: DAG nodes restyled to Tailwind (Langflow-style
state-driven borders, lucide icons, count pill, loop/gate affordances).
- TaskDrawer.tsx: shadcn rebuild + Magentic-style read-only gate panel
('Answer in Slack #agent-team', no submit controls).
- TopBar.tsx: restyled brand + status chips + health dot.
- Retire theme.css; port the .md markdown block into index.css on new tokens.
- Remove dead TaskList; add Board/taskFilters tests.
Typecheck clean, 36 tests pass, build clean. Read-only — no backend changes.
125 lines
3.3 KiB
TypeScript
125 lines
3.3 KiB
TypeScript
import { cn } from "@/lib/utils";
|
||
import type { StateResponse } from "../api";
|
||
|
||
interface Props {
|
||
state: StateResponse | null;
|
||
connected: boolean;
|
||
generatedAt: string | null;
|
||
}
|
||
|
||
interface ChipDef {
|
||
key: keyof StateResponse["summary"];
|
||
label: string;
|
||
numCls: string;
|
||
bgCls: string;
|
||
}
|
||
|
||
const CHIPS: ChipDef[] = [
|
||
{
|
||
key: "active",
|
||
label: "ACTIVE",
|
||
numCls: "text-status-active",
|
||
bgCls: "bg-status-active-bg",
|
||
},
|
||
{
|
||
key: "waiting",
|
||
label: "AWAITING HUMAN",
|
||
numCls: "text-status-waiting",
|
||
bgCls: "bg-status-waiting-bg",
|
||
},
|
||
{
|
||
key: "parked",
|
||
label: "PARKED",
|
||
numCls: "text-status-parked",
|
||
bgCls: "bg-status-parked-bg",
|
||
},
|
||
{
|
||
key: "open_questions",
|
||
label: "OPEN QUESTIONS",
|
||
numCls: "text-status-waiting",
|
||
bgCls: "bg-status-waiting-bg",
|
||
},
|
||
{
|
||
key: "total",
|
||
label: "TOTAL",
|
||
numCls: "text-muted-foreground",
|
||
bgCls: "bg-muted",
|
||
},
|
||
];
|
||
|
||
// Top status bar: brand, summary chips, connection health, and snapshot clock.
|
||
export function TopBar({ state, connected, generatedAt }: Props) {
|
||
const summary = state?.summary;
|
||
const title = connected
|
||
? `Live — snapshot at ${generatedAt ?? "—"}`
|
||
: `Disconnected — last snapshot at ${generatedAt ?? "—"}`;
|
||
|
||
return (
|
||
<header
|
||
className="flex items-center gap-3 py-2 px-3 bg-card border-b border-border"
|
||
role="banner"
|
||
>
|
||
{/* Brand */}
|
||
<div className="flex items-center gap-2 shrink-0">
|
||
<span
|
||
className="w-3 h-3 rounded-full bg-gradient-to-br from-primary to-primary/50 shadow-sm"
|
||
aria-hidden
|
||
/>
|
||
<span className="text-sm font-bold text-foreground tracking-tight leading-none">
|
||
agent-team
|
||
</span>
|
||
<span className="text-xs text-muted-foreground leading-none">
|
||
pipeline
|
||
</span>
|
||
</div>
|
||
|
||
{/* Divider */}
|
||
<div className="w-px h-5 bg-border shrink-0" aria-hidden />
|
||
|
||
{/* Summary chips */}
|
||
<div
|
||
className="flex items-center gap-2 flex-1 min-w-0 overflow-x-auto"
|
||
role="list"
|
||
aria-label="Pipeline summary"
|
||
>
|
||
{CHIPS.map((c) => (
|
||
<div
|
||
key={c.key}
|
||
role="listitem"
|
||
className={cn(
|
||
"flex flex-col items-center justify-center rounded-md px-2.5 py-1 shrink-0",
|
||
c.bgCls,
|
||
)}
|
||
>
|
||
<span className={cn("text-sm font-bold leading-none tabular-nums", c.numCls)}>
|
||
{summary ? summary[c.key] : "–"}
|
||
</span>
|
||
<span className="text-[9px] font-semibold tracking-widest text-muted-foreground leading-none mt-0.5 whitespace-nowrap">
|
||
{c.label}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Health indicator */}
|
||
<div
|
||
className="flex items-center gap-2 shrink-0 ml-auto"
|
||
title={title}
|
||
aria-label={title}
|
||
>
|
||
<span
|
||
aria-hidden
|
||
className={cn(
|
||
"w-2.5 h-2.5 rounded-full",
|
||
connected
|
||
? "bg-green-500 shadow-[0_0_6px_2px_rgba(34,197,94,0.45)]"
|
||
: "bg-red-500",
|
||
)}
|
||
/>
|
||
<span className="text-xs text-muted-foreground tabular-nums">
|
||
{generatedAt ?? "—"}
|
||
</span>
|
||
</div>
|
||
</header>
|
||
);
|
||
}
|