Teams UI: deploy scope selector + TeamWizard + Team page
ci / gates (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / sandbox-k8s (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / e2e (push) Has been cancelled

The "+" deploy flow now opens a scope selector (Single / Team / Company-soon /
Org-soon). Single = the existing CreateClawForm. Team = TeamWizard: pick a
baseline topology (from the catalog + role distribution), set size, auto-staff
editable claw cards (name / role / model / persona), preview the topology
graph, then POST /api/teams → land on the Team page.

Team page (/teams/[id]): topology SVG + claw roster (each links to its chat) +
a Run panel that drives the team on the durable runner with live SSE progress
(reuses the topology-run streaming). /teams list page + a Teams nav entry.

lint + typecheck + next build clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-18 13:17:30 -07:00
co-authored by Claude Opus 4.8
parent 8123a27bcf
commit bba18a4687
7 changed files with 575 additions and 7 deletions
@@ -1,17 +1,15 @@
import { CreateClawForm } from "@/components/wizard/CreateClawForm";
import { DeployWizard } from "@/components/wizard/DeployWizard";
export default function NewClawPage() {
return (
<section className="flex min-h-dvh flex-col items-center justify-center gap-6 px-4 motion-safe:animate-[fade-up_var(--duration-normal)_var(--ease-app)]">
<div className="text-center">
<h1 className="text-2xl font-semibold tracking-tight">
Create your Claw
</h1>
<h1 className="text-2xl font-semibold tracking-tight">Deploy</h1>
<p className="pt-1 text-sm text-muted-foreground">
Claws help get your work done.
A single claw, or a whole team pick your scale.
</p>
</div>
<CreateClawForm />
<DeployWizard />
</section>
);
}
@@ -0,0 +1,6 @@
import { TeamView } from "@/components/team/TeamView";
export default async function TeamPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
return <TeamView teamId={id} />;
}
@@ -0,0 +1,64 @@
"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
interface TeamSummary {
id: string;
name: string;
kind: string;
status: string;
created_at: string;
}
export default function TeamsPage() {
const [teams, setTeams] = useState<TeamSummary[]>([]);
useEffect(() => {
void (async () => {
try {
const r = await fetch("/api/teams");
if (r.ok) setTeams((await r.json()) as TeamSummary[]);
} catch {
/* ignore */
}
})();
}, []);
return (
<div className="mx-auto flex w-full max-w-3xl flex-col gap-4 p-6">
<div className="flex items-center justify-between">
<h1 className="text-xl font-semibold tracking-tight">Teams</h1>
<Link
href="/claws/new"
className="rounded-full bg-coral px-4 py-2 text-sm font-medium text-white"
>
Deploy a team
</Link>
</div>
{teams.length === 0 ? (
<p className="text-sm text-muted-foreground">
No teams yet deploy a baseline topology staffed with claws.
</p>
) : (
<ul className="flex flex-col gap-2">
{teams.map((t) => (
<li key={t.id}>
<Link
href={`/teams/${t.id}`}
className="flex items-center gap-3 rounded-lg border border-border p-3 hover:bg-muted/30"
>
<span className="text-sm font-medium text-foreground">{t.name}</span>
<span className="rounded-full bg-muted px-2 py-0.5 text-xs capitalize text-muted-foreground">
{t.kind}
</span>
<span className="text-xs text-muted-foreground">{t.status}</span>
</Link>
</li>
))}
</ul>
)}
</div>
);
}