Files
paperclip/ui/src/pages/DesignGuide.tsx
T
Dotta 2e74d32871 PAP-10430: split Issue-to-Task copy migration (#7651)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - The board UI is the operator surface where users create, assign,
monitor, and review work items.
> - The product language is moving toward "tasks" for user-facing work
items while the internal API and database still use "issues".
> - PR #7543 bundled this copy migration with broader
information-architecture work, which made the branch too large for
Greptile review.
> - This pull request peels the Issue-to-Task copy migration into a
smaller, independently reviewable change.
> - The benefit is clearer user-facing terminology, less agent confusion
via the Paperclip skill note, and a smaller PR that Greptile can review.

## Linked Issues or Issue Description

Refs #7645
Refs #7543
Refs PAP-10430

This PR was split out of #7543 so the Issue-to-Task copy migration can
be reviewed separately and the remaining IA PR can fall under Greptile's
file limit.

## What Changed

- Preserves Scott Tong's original `PAP-57` copy-only commit, with author
and co-author credit intact, to rename user-facing "Issues" copy to
"Tasks" across the UI while keeping routes/API/internal symbols as
`issue`.
- Updates onboarding and release-smoke browser selectors from `Create &
Open Issue` to `Create & Open Task`.
- Adds a terminology note to `skills/paperclip/SKILL.md` clarifying that
task and issue refer to the same Paperclip work item.
- Resolves the only cherry-pick conflict by keeping current search
artifacts support and changing visible search copy to "tasks".

## Verification

- `pnpm --filter @paperclipai/ui build` passed.
- `NODE_ENV=test pnpm exec vitest run
ui/src/components/IssuesList.test.tsx ui/src/components/Sidebar.test.tsx
ui/src/components/NewIssueDialog.test.tsx
ui/src/pages/IssueDetail.test.tsx` passed: 4 files, 66 tests.
- `git diff --check origin/master...HEAD` passed.
- Diff is 80 files, below Greptile's 100-file limit.
- Before/after UI copy examples: "Issues" -> "Tasks", "New Issue" ->
"New Task", "Create & Open Issue" -> "Create & Open Task".

## Risks

- Medium copy-risk: this intentionally changes user-facing terminology
broadly while keeping internal issue identifiers and routes unchanged.
- Some docs and APIs still say `issue`; the skill note clarifies this so
agents do not treat task and issue as separate entities.
- Browser-level visual validation is expected from CI because this local
container is missing usable browser dependencies.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

Scott Tong authored the original `PAP-57` copy migration, assisted by
Claude Opus 4.8 and Paperclip agents per the preserved commit metadata.
Codex / GPT-5-class coding agent with shell, GitHub CLI, and repository
access performed the PR split, conflict resolution, skill note, and
verification.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: scotttong <scott.tong@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-06-06 10:10:01 -05:00

1624 lines
66 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useState } from "react";
import {
BookOpen,
Bot,
Check,
ChevronDown,
CircleDot,
Command as CommandIcon,
DollarSign,
Hexagon,
History,
Inbox,
LayoutDashboard,
ListTodo,
Mail,
Plus,
Search,
Settings,
Target,
Trash2,
Upload,
User,
Zap,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Checkbox } from "@/components/ui/checkbox";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
CardContent,
CardFooter,
} from "@/components/ui/card";
import {
Dialog,
DialogTrigger,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from "@/components/ui/dialog";
import {
Tooltip,
TooltipTrigger,
TooltipContent,
} from "@/components/ui/tooltip";
import {
Select,
SelectTrigger,
SelectValue,
SelectContent,
SelectItem,
} from "@/components/ui/select";
import {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuCheckboxItem,
DropdownMenuShortcut,
} from "@/components/ui/dropdown-menu";
import {
Popover,
PopoverTrigger,
PopoverContent,
} from "@/components/ui/popover";
import {
Sheet,
SheetTrigger,
SheetContent,
SheetHeader,
SheetTitle,
SheetDescription,
SheetFooter,
} from "@/components/ui/sheet";
import {
Collapsible,
CollapsibleTrigger,
CollapsibleContent,
} from "@/components/ui/collapsible";
import { ScrollArea } from "@/components/ui/scroll-area";
import {
Command,
CommandInput,
CommandList,
CommandGroup,
CommandItem,
CommandEmpty,
CommandSeparator,
} from "@/components/ui/command";
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from "@/components/ui/breadcrumb";
import {
Avatar,
AvatarFallback,
AvatarGroup,
AvatarGroupCount,
} from "@/components/ui/avatar";
import { StatusBadge, AgentStatusBadge, AgentStatusCapsule } from "@/components/StatusBadge";
import { StatusIcon } from "@/components/StatusIcon";
import { PriorityIcon } from "@/components/PriorityIcon";
import { EntityRow } from "@/components/EntityRow";
import { EmptyState } from "@/components/EmptyState";
import { MetricCard } from "@/components/MetricCard";
import { FilterBar, type FilterValue } from "@/components/FilterBar";
import { InlineEditor } from "@/components/InlineEditor";
import { PageSkeleton } from "@/components/PageSkeleton";
import { Identity } from "@/components/Identity";
import { IssueReferencePill } from "@/components/IssueReferencePill";
import { MembershipAction } from "@/components/MembershipAction";
import { IssueOutputSection } from "@/components/issue-output/IssueOutputSection";
import {
EnvInputsList,
ExternalSourcesList,
RequiredSkillsList,
StepSkillPlan,
StepSourcePolicy,
TeamCard,
TeamHierarchyPreview,
TeamRow,
} from "@/pages/TeamCatalog";
import {
currentInstalledState,
onboardingTeams,
optionalTeam,
outOfDateInstalledState,
sampleSkillPreparations,
sampleTeam,
warnTeam,
} from "@/pages/TeamCatalog.fixtures";
import type { IssueWorkProduct } from "@paperclipai/shared";
/* ------------------------------------------------------------------ */
/* Sample data for the Issue Output surface showcase */
/* ------------------------------------------------------------------ */
function sampleOutput(
id: string,
attachmentId: string,
contentType: string,
filename: string,
opts: { byteSize: number; isPrimary?: boolean; createdAt: string },
): IssueWorkProduct {
const contentPath = `/api/attachments/${attachmentId}/content`;
return {
id,
companyId: "demo-company",
projectId: null,
issueId: "demo-issue",
executionWorkspaceId: null,
runtimeServiceId: null,
type: "artifact",
provider: "paperclip",
externalId: null,
title: filename,
url: null,
status: "active",
reviewState: "none",
isPrimary: Boolean(opts.isPrimary),
healthStatus: "unknown",
summary: null,
createdByRunId: null,
createdAt: new Date(opts.createdAt),
updatedAt: new Date(opts.createdAt),
metadata: {
attachmentId,
contentType,
byteSize: opts.byteSize,
contentPath,
openPath: contentPath,
downloadPath: `${contentPath}?download=1`,
originalFilename: filename,
},
} as IssueWorkProduct;
}
const DESIGN_GUIDE_OUTPUTS: IssueWorkProduct[] = [
sampleOutput("wp-vid", "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", "video/mp4", "q3-summary.mp4", {
byteSize: 19_293_798,
isPrimary: true,
createdAt: "2026-05-30T12:00:00Z",
}),
sampleOutput("wp-pdf", "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb", "application/pdf", "talking-points.pdf", {
byteSize: 421_888,
createdAt: "2026-05-30T11:52:00Z",
}),
];
const DESIGN_GUIDE_DEGRADED_OUTPUTS: IssueWorkProduct[] = [
{
...sampleOutput("wp-broken", "cccccccc-cccc-4ccc-8ccc-cccccccccccc", "video/mp4", "corrupt-output.mp4", {
byteSize: 0,
isPrimary: true,
createdAt: "2026-05-30T12:01:00Z",
}),
// Strip the path metadata so it fails the shared artifact schema.
metadata: { attachmentId: "cccccccc-cccc-4ccc-8ccc-cccccccccccc", contentType: "video/mp4" },
} as IssueWorkProduct,
];
/* ------------------------------------------------------------------ */
/* Section wrapper */
/* ------------------------------------------------------------------ */
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
<section className="space-y-4">
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
{title}
</h3>
<Separator />
{children}
</section>
);
}
function SubSection({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="space-y-3">
<h4 className="text-sm font-medium">{title}</h4>
{children}
</div>
);
}
// Onboarding seam (design §6 + §12.5): the TeamCard tile in its "Pick a starter
// team" 3-col grid, with the first defaultInstall tile selected.
function TeamCardShowcase() {
const [selectedId, setSelectedId] = useState(onboardingTeams[0]?.id ?? null);
return (
<div className="grid max-w-2xl gap-4 md:grid-cols-2 lg:grid-cols-3">
{onboardingTeams.map((team) => (
<TeamCard
key={team.id}
team={team}
selected={team.id === selectedId}
onSelect={() => setSelectedId(team.id)}
/>
))}
</div>
);
}
/* ------------------------------------------------------------------ */
/* Color swatch */
/* ------------------------------------------------------------------ */
function Swatch({ name, cssVar }: { name: string; cssVar: string }) {
return (
<div className="flex items-center gap-3">
<div
className="h-8 w-8 rounded-md border border-border shrink-0"
style={{ backgroundColor: `var(${cssVar})` }}
/>
<div>
<p className="text-xs font-mono">{cssVar}</p>
<p className="text-xs text-muted-foreground">{name}</p>
</div>
</div>
);
}
/* ------------------------------------------------------------------ */
/* Page */
/* ------------------------------------------------------------------ */
export function DesignGuide() {
const [status, setStatus] = useState("todo");
const [priority, setPriority] = useState("medium");
const [selectValue, setSelectValue] = useState("in_progress");
const [menuChecked, setMenuChecked] = useState(true);
const [collapsibleOpen, setCollapsibleOpen] = useState(false);
const [inlineText, setInlineText] = useState("Click to edit this text");
const [inlineTitle, setInlineTitle] = useState("Editable Title");
const [inlineDesc, setInlineDesc] = useState(
"This is an editable description. Click to edit it — the textarea auto-sizes to fit the content without layout shift."
);
const [filters, setFilters] = useState<FilterValue[]>([
{ key: "status", label: "Status", value: "Active" },
{ key: "priority", label: "Priority", value: "High" },
]);
const [allowExternal, setAllowExternal] = useState(false);
const [allowUnpinned, setAllowUnpinned] = useState(false);
const [allowLocalPath, setAllowLocalPath] = useState(false);
return (
<div className="space-y-10 max-w-4xl">
{/* Page header */}
<div>
<h2 className="text-xl font-bold">Design Guide</h2>
<p className="text-sm text-muted-foreground mt-1">
Every component, style, and pattern used across Paperclip.
</p>
</div>
{/* ============================================================ */}
{/* COVERAGE */}
{/* ============================================================ */}
<Section title="Component Coverage">
<p className="text-sm text-muted-foreground">
This page should be updated when new UI primitives or app-level patterns ship.
</p>
<div className="grid gap-6 md:grid-cols-2">
<SubSection title="UI primitives">
<div className="flex flex-wrap gap-2">
{[
"avatar", "badge", "breadcrumb", "button", "card", "checkbox", "collapsible",
"command", "dialog", "dropdown-menu", "input", "label", "popover", "scroll-area",
"select", "separator", "sheet", "skeleton", "tabs", "textarea", "tooltip",
].map((name) => (
<Badge key={name} variant="outline" className="font-mono text-[10px]">
{name}
</Badge>
))}
</div>
</SubSection>
<SubSection title="App components">
<div className="flex flex-wrap gap-2">
{[
"StatusBadge", "StatusIcon", "PriorityIcon", "EntityRow", "EmptyState", "MetricCard",
"FilterBar", "InlineEditor", "PageSkeleton", "Identity", "CommentThread", "MarkdownEditor",
"PropertiesPanel", "Sidebar", "CommandPalette",
].map((name) => (
<Badge key={name} variant="ghost" className="font-mono text-[10px]">
{name}
</Badge>
))}
</div>
</SubSection>
</div>
</Section>
{/* ============================================================ */}
{/* COLORS */}
{/* ============================================================ */}
<Section title="Colors">
<SubSection title="Core">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<Swatch name="Background" cssVar="--background" />
<Swatch name="Foreground" cssVar="--foreground" />
<Swatch name="Card" cssVar="--card" />
<Swatch name="Primary" cssVar="--primary" />
<Swatch name="Primary foreground" cssVar="--primary-foreground" />
<Swatch name="Secondary" cssVar="--secondary" />
<Swatch name="Muted" cssVar="--muted" />
<Swatch name="Muted foreground" cssVar="--muted-foreground" />
<Swatch name="Accent" cssVar="--accent" />
<Swatch name="Destructive" cssVar="--destructive" />
<Swatch name="Border" cssVar="--border" />
<Swatch name="Ring" cssVar="--ring" />
</div>
</SubSection>
<SubSection title="Sidebar">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<Swatch name="Sidebar" cssVar="--sidebar" />
<Swatch name="Sidebar border" cssVar="--sidebar-border" />
</div>
</SubSection>
<SubSection title="Chart">
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
<Swatch name="Chart 1" cssVar="--chart-1" />
<Swatch name="Chart 2" cssVar="--chart-2" />
<Swatch name="Chart 3" cssVar="--chart-3" />
<Swatch name="Chart 4" cssVar="--chart-4" />
<Swatch name="Chart 5" cssVar="--chart-5" />
</div>
</SubSection>
</Section>
{/* ============================================================ */}
{/* TYPOGRAPHY */}
{/* ============================================================ */}
<Section title="Typography">
<div className="space-y-3">
<h2 className="text-xl font-bold">Page Title text-xl font-bold</h2>
<h2 className="text-lg font-semibold">Section Title text-lg font-semibold</h2>
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
Section Heading text-sm font-semibold uppercase tracking-wide
</h3>
<p className="text-sm font-medium">Card Title text-sm font-medium</p>
<p className="text-sm font-semibold">Card Title Alt text-sm font-semibold</p>
<p className="text-sm">Body text text-sm</p>
<p className="text-sm text-muted-foreground">
Muted description text-sm text-muted-foreground
</p>
<p className="text-xs text-muted-foreground">
Tiny label text-xs text-muted-foreground
</p>
<p className="text-sm font-mono text-muted-foreground">
Mono identifier text-sm font-mono text-muted-foreground
</p>
<p className="text-2xl font-bold">Large stat text-2xl font-bold</p>
<p className="font-mono text-xs">Log/code text font-mono text-xs</p>
</div>
</Section>
{/* ============================================================ */}
{/* SPACING & RADIUS */}
{/* ============================================================ */}
<Section title="Radius">
<div className="flex items-end gap-4 flex-wrap">
{[
["sm", "var(--radius-sm)"],
["md", "var(--radius-md)"],
["lg", "var(--radius-lg)"],
["xl", "var(--radius-xl)"],
["full", "9999px"],
].map(([label, radius]) => (
<div key={label} className="flex flex-col items-center gap-1">
<div
className="h-12 w-12 bg-primary"
style={{ borderRadius: radius }}
/>
<span className="text-xs text-muted-foreground">{label}</span>
</div>
))}
</div>
</Section>
{/* ============================================================ */}
{/* BUTTONS */}
{/* ============================================================ */}
<Section title="Buttons">
<SubSection title="Variants">
<div className="flex items-center gap-2 flex-wrap">
<Button variant="default">Default</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>
<Button variant="link">Link</Button>
</div>
</SubSection>
<SubSection title="Sizes">
<div className="flex items-center gap-2 flex-wrap">
<Button size="xs">Extra Small</Button>
<Button size="sm">Small</Button>
<Button size="default">Default</Button>
<Button size="lg">Large</Button>
</div>
</SubSection>
<SubSection title="Icon buttons">
<div className="flex items-center gap-2 flex-wrap">
<Button variant="ghost" size="icon-xs"><Search /></Button>
<Button variant="ghost" size="icon-sm"><Search /></Button>
<Button variant="outline" size="icon"><Search /></Button>
<Button variant="outline" size="icon-lg"><Search /></Button>
</div>
</SubSection>
<SubSection title="With icons">
<div className="flex items-center gap-2 flex-wrap">
<Button><Plus /> New Task</Button>
<Button variant="outline"><Upload /> Upload</Button>
<Button variant="destructive"><Trash2 /> Delete</Button>
<Button size="sm"><Plus /> Add</Button>
</div>
</SubSection>
<SubSection title="States">
<div className="flex items-center gap-2 flex-wrap">
<Button disabled>Disabled</Button>
<Button variant="outline" disabled>Disabled Outline</Button>
</div>
</SubSection>
</Section>
{/* ============================================================ */}
{/* BADGES */}
{/* ============================================================ */}
<Section title="Badges">
<SubSection title="Variants">
<div className="flex items-center gap-2 flex-wrap">
<Badge variant="default">Default</Badge>
<Badge variant="secondary">Secondary</Badge>
<Badge variant="outline">Outline</Badge>
<Badge variant="destructive">Destructive</Badge>
<Badge variant="ghost">Ghost</Badge>
</div>
</SubSection>
</Section>
{/* ============================================================ */}
{/* STATUS BADGES & ICONS */}
{/* ============================================================ */}
<Section title="Status System">
<SubSection title="StatusBadge (all statuses)">
<div className="flex items-center gap-2 flex-wrap">
{[
"active", "running", "paused", "idle", "archived", "planned",
"achieved", "completed", "failed", "timed_out", "succeeded", "error",
"pending_approval", "backlog", "todo", "in_progress", "in_review", "blocked",
"done", "terminated", "cancelled", "pending", "revision_requested",
"approved", "rejected",
].map((s) => (
<StatusBadge key={s} status={s} />
))}
</div>
</SubSection>
<SubSection title="StatusIcon (interactive)">
<div className="flex items-center gap-3 flex-wrap">
{["backlog", "todo", "in_progress", "in_review", "done", "cancelled", "blocked"].map(
(s) => (
<div key={s} className="flex items-center gap-1.5">
<StatusIcon status={s} />
<span className="text-xs text-muted-foreground">{s}</span>
</div>
)
)}
</div>
<div className="flex items-center gap-2 mt-2">
<StatusIcon status={status} onChange={setStatus} />
<span className="text-sm">Click the icon to change status (current: {status})</span>
</div>
</SubSection>
<SubSection title="PriorityIcon (interactive)">
<div className="flex items-center gap-3 flex-wrap">
{["critical", "high", "medium", "low"].map((p) => (
<div key={p} className="flex items-center gap-1.5">
<PriorityIcon priority={p} />
<span className="text-xs text-muted-foreground">{p}</span>
</div>
))}
</div>
<div className="flex items-center gap-2 mt-2">
<PriorityIcon priority={priority} onChange={setPriority} />
<span className="text-sm">Click the icon to change (current: {priority})</span>
</div>
</SubSection>
<SubSection title="Agent status (capsule + chip)">
<p className="text-xs text-muted-foreground mb-3 max-w-prose">
The agents section uses a brand heartbeat capsule (8×16) plus a brand
<code className="mx-1">.task-chip</code>. Four states only: idle (gray),
running (blue, pulses), paused (amber), error (red, blinks). Motion
honors <code>prefers-reduced-motion</code>.
</p>
<div className="flex items-center gap-6 flex-wrap">
{(["idle", "running", "paused", "error"] as const).map((label) => (
<div key={label} className="flex items-center gap-2">
<AgentStatusCapsule status={label} />
<AgentStatusBadge status={label} />
</div>
))}
</div>
</SubSection>
<SubSection title="Run invocation badges">
<div className="flex items-center gap-2 flex-wrap">
{[
["timer", "bg-blue-100 text-blue-700 dark:bg-blue-900/50 dark:text-blue-300"],
["assignment", "bg-violet-100 text-violet-700 dark:bg-violet-900/50 dark:text-violet-300"],
["on_demand", "bg-cyan-100 text-cyan-700 dark:bg-cyan-900/50 dark:text-cyan-300"],
["automation", "bg-muted text-muted-foreground"],
].map(([label, cls]) => (
<span key={label} className={`rounded-full px-1.5 py-0.5 text-[10px] font-medium ${cls}`}>
{label}
</span>
))}
</div>
</SubSection>
<SubSection title="IssueReferencePill">
<p className="text-xs text-muted-foreground">
Used wherever a task is referenced in markdown, the Related Work tab, and activity summaries.
Pass <code className="font-mono">status</code> to show the target issue&apos;s state at a glance.
Use <code className="font-mono">strikethrough</code> for &quot;removed&quot; contexts.
</p>
<div className="flex items-center gap-2 flex-wrap">
<IssueReferencePill issue={{ id: "demo-1", identifier: "PAP-123", title: "Identifier only — no status yet" }} />
<IssueReferencePill issue={{ id: "demo-2", identifier: "PAP-456", title: "With in_progress status", status: "in_progress" }} />
<IssueReferencePill issue={{ id: "demo-3", identifier: "PAP-789", title: "Done status", status: "done" }} />
<IssueReferencePill issue={{ id: "demo-4", identifier: "PAP-101", title: "Blocked status", status: "blocked" }} />
<IssueReferencePill strikethrough issue={{ id: "demo-5", identifier: "PAP-202", title: "Removed (strikethrough)", status: "todo" }} />
</div>
</SubSection>
</Section>
{/* ============================================================ */}
{/* FORM ELEMENTS */}
{/* ============================================================ */}
<Section title="Form Elements">
<div className="grid gap-6 md:grid-cols-2">
<SubSection title="Input">
<Input placeholder="Default input" />
<Input placeholder="Disabled input" disabled className="mt-2" />
</SubSection>
<SubSection title="Textarea">
<Textarea placeholder="Write something..." />
</SubSection>
<SubSection title="Checkbox & Label">
<div className="space-y-3">
<div className="flex items-center gap-2">
<Checkbox id="check1" defaultChecked />
<Label htmlFor="check1">Checked item</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox id="check2" />
<Label htmlFor="check2">Unchecked item</Label>
</div>
<div className="flex items-center gap-2">
<Checkbox id="check3" disabled />
<Label htmlFor="check3">Disabled item</Label>
</div>
</div>
</SubSection>
<SubSection title="Inline Editor">
<div className="space-y-4">
<div>
<p className="text-xs text-muted-foreground mb-1">Title (single-line)</p>
<InlineEditor
value={inlineTitle}
onSave={setInlineTitle}
as="h2"
className="text-xl font-bold"
/>
</div>
<div>
<p className="text-xs text-muted-foreground mb-1">Body text (single-line)</p>
<InlineEditor
value={inlineText}
onSave={setInlineText}
as="p"
className="text-sm"
/>
</div>
<div>
<p className="text-xs text-muted-foreground mb-1">Description (multiline, auto-sizing)</p>
<InlineEditor
value={inlineDesc}
onSave={setInlineDesc}
as="p"
className="text-sm text-muted-foreground"
placeholder="Add a description..."
multiline
/>
</div>
</div>
</SubSection>
</div>
</Section>
{/* ============================================================ */}
{/* SELECT */}
{/* ============================================================ */}
<Section title="Select">
<div className="grid gap-6 md:grid-cols-2">
<SubSection title="Default size">
<Select value={selectValue} onValueChange={setSelectValue}>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="backlog">Backlog</SelectItem>
<SelectItem value="todo">Todo</SelectItem>
<SelectItem value="in_progress">In Progress</SelectItem>
<SelectItem value="in_review">In Review</SelectItem>
<SelectItem value="done">Done</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">Current value: {selectValue}</p>
</SubSection>
<SubSection title="Small trigger">
<Select defaultValue="high">
<SelectTrigger size="sm" className="w-full">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="critical">Critical</SelectItem>
<SelectItem value="high">High</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="low">Low</SelectItem>
</SelectContent>
</Select>
</SubSection>
</div>
</Section>
{/* ============================================================ */}
{/* DROPDOWN MENU */}
{/* ============================================================ */}
<Section title="Dropdown Menu">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm">
Quick Actions
<ChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-56">
<DropdownMenuItem>
<Check className="h-4 w-4" />
Mark as done
<DropdownMenuShortcut>D</DropdownMenuShortcut>
</DropdownMenuItem>
<DropdownMenuItem>
<BookOpen className="h-4 w-4" />
Open docs
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuCheckboxItem
checked={menuChecked}
onCheckedChange={(value) => setMenuChecked(value === true)}
>
Watch task
</DropdownMenuCheckboxItem>
<DropdownMenuItem variant="destructive">
<Trash2 className="h-4 w-4" />
Delete task
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</Section>
{/* ============================================================ */}
{/* POPOVER */}
{/* ============================================================ */}
<Section title="Popover">
<Popover>
<PopoverTrigger asChild>
<Button variant="outline" size="sm">Open Popover</Button>
</PopoverTrigger>
<PopoverContent className="space-y-2">
<p className="text-sm font-medium">Agent heartbeat</p>
<p className="text-xs text-muted-foreground">
Last run succeeded 24s ago. Next timer run in 9m.
</p>
<Button size="xs">Wake now</Button>
</PopoverContent>
</Popover>
</Section>
{/* ============================================================ */}
{/* COLLAPSIBLE */}
{/* ============================================================ */}
<Section title="Collapsible">
<Collapsible open={collapsibleOpen} onOpenChange={setCollapsibleOpen} className="space-y-2">
<CollapsibleTrigger asChild>
<Button variant="outline" size="sm">
{collapsibleOpen ? "Hide" : "Show"} advanced filters
</Button>
</CollapsibleTrigger>
<CollapsibleContent className="rounded-md border border-border p-3">
<div className="space-y-2">
<Label htmlFor="owner-filter">Owner</Label>
<Input id="owner-filter" placeholder="Filter by agent name" />
</div>
</CollapsibleContent>
</Collapsible>
</Section>
{/* ============================================================ */}
{/* SHEET */}
{/* ============================================================ */}
<Section title="Sheet">
<Sheet>
<SheetTrigger asChild>
<Button variant="outline" size="sm">Open Side Panel</Button>
</SheetTrigger>
<SheetContent side="right">
<SheetHeader>
<SheetTitle>Task Properties</SheetTitle>
<SheetDescription>Edit metadata without leaving the current page.</SheetDescription>
</SheetHeader>
<div className="space-y-4 px-4">
<div className="space-y-1">
<Label htmlFor="sheet-title">Title</Label>
<Input id="sheet-title" defaultValue="Improve onboarding docs" />
</div>
<div className="space-y-1">
<Label htmlFor="sheet-description">Description</Label>
<Textarea id="sheet-description" defaultValue="Capture setup pitfalls and screenshots." />
</div>
</div>
<SheetFooter>
<Button variant="outline">Cancel</Button>
<Button>Save</Button>
</SheetFooter>
</SheetContent>
</Sheet>
</Section>
{/* ============================================================ */}
{/* SCROLL AREA */}
{/* ============================================================ */}
<Section title="Scroll Area">
<ScrollArea className="h-36 rounded-md border border-border">
<div className="space-y-2 p-3">
{Array.from({ length: 12 }).map((_, i) => (
<div key={i} className="rounded-md border border-border p-2 text-sm">
Heartbeat run #{i + 1}: completed successfully
</div>
))}
</div>
</ScrollArea>
</Section>
{/* ============================================================ */}
{/* COMMAND */}
{/* ============================================================ */}
<Section title="Command (CMDK)">
<div className="rounded-md border border-border">
<Command>
<CommandInput placeholder="Type a command or search..." />
<CommandList>
<CommandEmpty>No results found.</CommandEmpty>
<CommandGroup heading="Pages">
<CommandItem>
<LayoutDashboard className="h-4 w-4" />
Dashboard
</CommandItem>
<CommandItem>
<CircleDot className="h-4 w-4" />
Tasks
</CommandItem>
</CommandGroup>
<CommandSeparator />
<CommandGroup heading="Actions">
<CommandItem>
<CommandIcon className="h-4 w-4" />
Open command palette
</CommandItem>
<CommandItem>
<Plus className="h-4 w-4" />
Create new task
</CommandItem>
</CommandGroup>
</CommandList>
</Command>
</div>
</Section>
{/* ============================================================ */}
{/* BREADCRUMB */}
{/* ============================================================ */}
<Section title="Breadcrumb">
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink href="#">Projects</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbLink href="#">Paperclip App</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>Task List</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</Section>
{/* ============================================================ */}
{/* CARDS */}
{/* ============================================================ */}
<Section title="Cards">
<SubSection title="Standard Card">
<Card>
<CardHeader>
<CardTitle>Card Title</CardTitle>
<CardDescription>Card description with supporting text.</CardDescription>
</CardHeader>
<CardContent>
<p className="text-sm">Card content goes here. This is the main body area.</p>
</CardContent>
<CardFooter className="gap-2">
<Button size="sm">Action</Button>
<Button variant="outline" size="sm">Cancel</Button>
</CardFooter>
</Card>
</SubSection>
<SubSection title="Metric Cards">
<div className="grid md:grid-cols-2 xl:grid-cols-4 gap-4">
<MetricCard icon={Bot} value={12} label="Active Agents" description="+3 this week" />
<MetricCard icon={CircleDot} value={48} label="Open Tasks" />
<MetricCard icon={DollarSign} value="$1,234" label="Monthly Cost" description="Under budget" />
<MetricCard icon={Zap} value="99.9%" label="Uptime" />
</div>
</SubSection>
</Section>
{/* ============================================================ */}
{/* TABS */}
{/* ============================================================ */}
<Section title="Tabs">
<SubSection title="Default (pill) variant">
<Tabs defaultValue="overview">
<TabsList>
<TabsTrigger value="overview">Overview</TabsTrigger>
<TabsTrigger value="runs">Runs</TabsTrigger>
<TabsTrigger value="config">Config</TabsTrigger>
<TabsTrigger value="costs">Costs</TabsTrigger>
</TabsList>
<TabsContent value="overview">
<p className="text-sm text-muted-foreground py-4">Overview tab content.</p>
</TabsContent>
<TabsContent value="runs">
<p className="text-sm text-muted-foreground py-4">Runs tab content.</p>
</TabsContent>
<TabsContent value="config">
<p className="text-sm text-muted-foreground py-4">Config tab content.</p>
</TabsContent>
<TabsContent value="costs">
<p className="text-sm text-muted-foreground py-4">Costs tab content.</p>
</TabsContent>
</Tabs>
</SubSection>
<SubSection title="Line variant">
<Tabs defaultValue="summary">
<TabsList variant="line">
<TabsTrigger value="summary">Summary</TabsTrigger>
<TabsTrigger value="details">Details</TabsTrigger>
<TabsTrigger value="comments">Comments</TabsTrigger>
</TabsList>
<TabsContent value="summary">
<p className="text-sm text-muted-foreground py-4">Summary content with underline tabs.</p>
</TabsContent>
<TabsContent value="details">
<p className="text-sm text-muted-foreground py-4">Details content.</p>
</TabsContent>
<TabsContent value="comments">
<p className="text-sm text-muted-foreground py-4">Comments content.</p>
</TabsContent>
</Tabs>
</SubSection>
</Section>
{/* ============================================================ */}
{/* ENTITY ROWS */}
{/* ============================================================ */}
<Section title="Entity Rows">
<div className="border border-border rounded-md">
<EntityRow
leading={
<>
<StatusIcon status="in_progress" />
<PriorityIcon priority="high" />
</>
}
identifier="PAP-001"
title="Implement authentication flow"
subtitle="Assigned to Agent Alpha"
trailing={<StatusBadge status="in_progress" />}
onClick={() => {}}
/>
<EntityRow
leading={
<>
<StatusIcon status="done" />
<PriorityIcon priority="medium" />
</>
}
identifier="PAP-002"
title="Set up CI/CD pipeline"
subtitle="Completed 2 days ago"
trailing={<StatusBadge status="done" />}
onClick={() => {}}
/>
<EntityRow
leading={
<>
<StatusIcon status="todo" />
<PriorityIcon priority="low" />
</>
}
identifier="PAP-003"
title="Write API documentation"
trailing={<StatusBadge status="todo" />}
onClick={() => {}}
/>
<EntityRow
leading={
<>
<StatusIcon status="blocked" />
<PriorityIcon priority="critical" />
</>
}
identifier="PAP-004"
title="Deploy to production"
subtitle="Blocked by PAP-001"
trailing={<StatusBadge status="blocked" />}
selected
/>
</div>
<SubSection title="Membership action">
<div className="border border-border rounded-md">
<EntityRow
title="Joined resource"
subtitle="Hover or focus the row to reveal the reserved action slot."
className="group"
trailing={
<MembershipAction
state="joined"
resourceName="Joined resource"
onJoin={() => {}}
onLeave={() => {}}
/>
}
/>
<EntityRow
title="Left resource"
subtitle="Persistent action with dimmed row content."
className="group text-foreground/55"
trailing={
<MembershipAction
state="left"
resourceName="Left resource"
onJoin={() => {}}
onLeave={() => {}}
/>
}
/>
<EntityRow
title="Leaving resource"
subtitle="Disabled while the optimistic mutation is pending."
className="group text-foreground/55"
trailing={
<MembershipAction
state="left"
pending
pendingState="left"
resourceName="Leaving resource"
onJoin={() => {}}
onLeave={() => {}}
/>
}
/>
<EntityRow
title="Joining resource"
subtitle="The target state is visible immediately while the server confirms."
className="group"
trailing={
<MembershipAction
state="joined"
pending
pendingState="joined"
resourceName="Joining resource"
onJoin={() => {}}
onLeave={() => {}}
/>
}
/>
</div>
</SubSection>
</Section>
{/* ============================================================ */}
{/* FILTER BAR */}
{/* ============================================================ */}
<Section title="Filter Bar">
<FilterBar
filters={filters}
onRemove={(key) => setFilters((f) => f.filter((x) => x.key !== key))}
onClear={() => setFilters([])}
/>
{filters.length === 0 && (
<Button
variant="outline"
size="sm"
onClick={() =>
setFilters([
{ key: "status", label: "Status", value: "Active" },
{ key: "priority", label: "Priority", value: "High" },
])
}
>
Reset filters
</Button>
)}
</Section>
{/* ============================================================ */}
{/* AVATARS */}
{/* ============================================================ */}
<Section title="Avatars">
<SubSection title="Sizes">
<div className="flex items-center gap-3">
<Avatar size="sm"><AvatarFallback>SM</AvatarFallback></Avatar>
<Avatar><AvatarFallback>DF</AvatarFallback></Avatar>
<Avatar size="lg"><AvatarFallback>LG</AvatarFallback></Avatar>
</div>
</SubSection>
<SubSection title="Group">
<AvatarGroup>
<Avatar><AvatarFallback>A1</AvatarFallback></Avatar>
<Avatar><AvatarFallback>A2</AvatarFallback></Avatar>
<Avatar><AvatarFallback>A3</AvatarFallback></Avatar>
<AvatarGroupCount>+5</AvatarGroupCount>
</AvatarGroup>
</SubSection>
</Section>
{/* ============================================================ */}
{/* IDENTITY */}
{/* ============================================================ */}
<Section title="Identity">
<SubSection title="Sizes">
<div className="flex items-center gap-6">
<Identity name="Agent Alpha" size="sm" />
<Identity name="Agent Alpha" />
<Identity name="Agent Alpha" size="lg" />
</div>
</SubSection>
<SubSection title="Initials derivation">
<div className="flex flex-col gap-2">
<Identity name="CEO Agent" size="sm" />
<Identity name="Alpha" size="sm" />
<Identity name="Quality Assurance Lead" size="sm" />
</div>
</SubSection>
<SubSection title="Custom initials">
<Identity name="Backend Service" initials="BS" size="sm" />
</SubSection>
</Section>
{/* ============================================================ */}
{/* TOOLTIPS */}
{/* ============================================================ */}
<Section title="Tooltips">
<div className="flex items-center gap-4">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="sm">Hover me</Button>
</TooltipTrigger>
<TooltipContent>This is a tooltip</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon-sm"><Settings /></Button>
</TooltipTrigger>
<TooltipContent>Settings</TooltipContent>
</Tooltip>
</div>
</Section>
{/* ============================================================ */}
{/* DIALOG */}
{/* ============================================================ */}
<Section title="Dialog">
<Dialog>
<DialogTrigger asChild>
<Button variant="outline">Open Dialog</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Dialog Title</DialogTitle>
<DialogDescription>
This is a sample dialog showing the standard layout with header, content, and footer.
</DialogDescription>
</DialogHeader>
<div className="space-y-3">
<div>
<Label>Name</Label>
<Input placeholder="Enter a name" className="mt-1.5" />
</div>
<div>
<Label>Description</Label>
<Textarea placeholder="Describe..." className="mt-1.5" />
</div>
</div>
<DialogFooter>
<Button variant="outline">Cancel</Button>
<Button>Save</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</Section>
{/* ============================================================ */}
{/* EMPTY STATE */}
{/* ============================================================ */}
<Section title="Empty State">
<div className="border border-border rounded-md">
<EmptyState
icon={Inbox}
message="No items to show. Create your first one to get started."
action="Create Item"
onAction={() => {}}
/>
</div>
</Section>
{/* ============================================================ */}
{/* PROGRESS BARS */}
{/* ============================================================ */}
<Section title="Progress Bars (Budget)">
<div className="space-y-3">
{[
{ label: "Under budget (40%)", pct: 40, color: "bg-green-400" },
{ label: "Warning (75%)", pct: 75, color: "bg-yellow-400" },
{ label: "Over budget (95%)", pct: 95, color: "bg-red-400" },
].map(({ label, pct, color }) => (
<div key={label} className="space-y-1">
<div className="flex items-center justify-between">
<span className="text-xs text-muted-foreground">{label}</span>
<span className="text-xs font-mono">{pct}%</span>
</div>
<div className="w-full h-2 bg-muted rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-[width,background-color] duration-150 ${color}`}
style={{ width: `${pct}%` }}
/>
</div>
</div>
))}
</div>
</Section>
{/* ============================================================ */}
{/* LOG VIEWER */}
{/* ============================================================ */}
<Section title="Log Viewer">
<div className="bg-neutral-950 rounded-lg p-3 font-mono text-xs max-h-80 overflow-y-auto">
<div className="text-foreground">[12:00:01] INFO Agent started successfully</div>
<div className="text-foreground">[12:00:02] INFO Processing task PAP-001</div>
<div className="text-yellow-400">[12:00:05] WARN Rate limit approaching (80%)</div>
<div className="text-foreground">[12:00:08] INFO Task PAP-001 completed</div>
<div className="text-red-400">[12:00:12] ERROR Connection timeout to upstream service</div>
<div className="text-blue-300">[12:00:12] SYS Retrying connection in 5s...</div>
<div className="text-foreground">[12:00:17] INFO Reconnected successfully</div>
<div className="flex items-center gap-1.5">
<span className="relative flex h-1.5 w-1.5">
<span className="absolute inline-flex h-full w-full rounded-full bg-cyan-400 animate-pulse" />
<span className="inline-flex h-full w-full rounded-full bg-cyan-400" />
</span>
<span className="text-cyan-400">Live</span>
</div>
</div>
</Section>
{/* ============================================================ */}
{/* PROPERTY ROW PATTERN */}
{/* ============================================================ */}
<Section title="Property Row Pattern">
<div className="border border-border rounded-md p-4 space-y-1 max-w-sm">
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground">Status</span>
<StatusBadge status="active" />
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground">Priority</span>
<PriorityIcon priority="high" />
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground">Assignee</span>
<div className="flex items-center gap-1.5">
<Avatar size="sm"><AvatarFallback>A</AvatarFallback></Avatar>
<span className="text-xs">Agent Alpha</span>
</div>
</div>
<div className="flex items-center justify-between py-1.5">
<span className="text-xs text-muted-foreground">Created</span>
<span className="text-xs">Jan 15, 2025</span>
</div>
</div>
</Section>
{/* ============================================================ */}
{/* NAVIGATION PATTERNS */}
{/* ============================================================ */}
<Section title="Navigation Patterns">
<SubSection title="Sidebar nav items">
<div className="w-60 border border-border rounded-md p-3 space-y-0.5 bg-card">
<div className="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium bg-accent text-accent-foreground">
<LayoutDashboard className="h-4 w-4" />
Dashboard
</div>
<div className="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground hover:bg-accent/50 hover:text-accent-foreground cursor-pointer">
<CircleDot className="h-4 w-4" />
Tasks
<span className="ml-auto text-xs bg-primary text-primary-foreground rounded-full px-1.5 py-0.5">
12
</span>
</div>
<div className="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground hover:bg-accent/50 hover:text-accent-foreground cursor-pointer">
<Bot className="h-4 w-4" />
Agents
</div>
<div className="flex items-center gap-2 px-3 py-1.5 rounded-md text-sm font-medium text-muted-foreground hover:bg-accent/50 hover:text-accent-foreground cursor-pointer">
<Hexagon className="h-4 w-4" />
Projects
</div>
</div>
</SubSection>
<SubSection title="View toggle">
<div className="flex items-center border border-border rounded-md w-fit">
<button className="px-3 py-1.5 text-xs font-medium bg-accent text-foreground rounded-l-md">
<ListTodo className="h-3.5 w-3.5 inline mr-1" />
List
</button>
<button className="px-3 py-1.5 text-xs font-medium text-muted-foreground hover:bg-accent/50 rounded-r-md">
<Target className="h-3.5 w-3.5 inline mr-1" />
Org
</button>
</div>
</SubSection>
</Section>
{/* ============================================================ */}
{/* GROUPED LIST (Issues pattern) */}
{/* ============================================================ */}
<Section title="Grouped List (Tasks pattern)">
<div>
<div className="flex items-center gap-2 px-4 py-2 bg-muted/50 rounded-t-md">
<StatusIcon status="in_progress" />
<span className="text-sm font-medium">In Progress</span>
<span className="text-xs text-muted-foreground ml-1">2</span>
</div>
<div className="border border-border rounded-b-md">
<EntityRow
leading={<PriorityIcon priority="high" />}
identifier="PAP-101"
title="Build agent heartbeat system"
onClick={() => {}}
/>
<EntityRow
leading={<PriorityIcon priority="medium" />}
identifier="PAP-102"
title="Add cost tracking dashboard"
onClick={() => {}}
/>
</div>
</div>
</Section>
{/* ============================================================ */}
{/* COMMENT THREAD PATTERN */}
{/* ============================================================ */}
<Section title="Comment Thread Pattern">
<div className="space-y-3 max-w-2xl">
<h3 className="text-sm font-semibold">Comments (2)</h3>
<div className="space-y-3">
<div className="rounded-md border border-border p-3">
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-medium text-muted-foreground">Agent</span>
<span className="text-xs text-muted-foreground">Jan 15, 2025</span>
</div>
<p className="text-sm">Started working on the authentication module. Will need API keys configured.</p>
</div>
<div className="rounded-md border border-border p-3">
<div className="flex items-center justify-between mb-1">
<span className="text-xs font-medium text-muted-foreground">Human</span>
<span className="text-xs text-muted-foreground">Jan 16, 2025</span>
</div>
<p className="text-sm">API keys have been added to the vault. Please proceed.</p>
</div>
</div>
<div className="space-y-2">
<Textarea placeholder="Leave a comment..." rows={3} />
<Button size="sm">Comment</Button>
</div>
</div>
</Section>
{/* ============================================================ */}
{/* COST TABLE PATTERN */}
{/* ============================================================ */}
<Section title="Cost Table Pattern">
<div className="border border-border rounded-lg overflow-hidden">
<table className="w-full text-xs">
<thead className="border-b border-border bg-accent/20">
<tr>
<th className="text-left px-3 py-2 font-medium text-muted-foreground">Model</th>
<th className="text-left px-3 py-2 font-medium text-muted-foreground">Tokens</th>
<th className="text-left px-3 py-2 font-medium text-muted-foreground">Cost</th>
</tr>
</thead>
<tbody>
<tr className="border-b border-border">
<td className="px-3 py-2">claude-sonnet-4-20250514</td>
<td className="px-3 py-2 font-mono">1.2M</td>
<td className="px-3 py-2 font-mono">$18.00</td>
</tr>
<tr className="border-b border-border">
<td className="px-3 py-2">claude-haiku-4-20250506</td>
<td className="px-3 py-2 font-mono">500k</td>
<td className="px-3 py-2 font-mono">$1.25</td>
</tr>
<tr>
<td className="px-3 py-2 font-medium">Total</td>
<td className="px-3 py-2 font-mono">1.7M</td>
<td className="px-3 py-2 font-mono font-medium">$19.25</td>
</tr>
</tbody>
</table>
</div>
</Section>
{/* ============================================================ */}
{/* SKELETONS */}
{/* ============================================================ */}
<Section title="Skeletons">
<SubSection title="Individual">
<div className="space-y-2">
<Skeleton className="h-4 w-48" />
<Skeleton className="h-8 w-full max-w-sm" />
<Skeleton className="h-20 w-full" />
</div>
</SubSection>
<SubSection title="Page Skeleton (list)">
<div className="border border-border rounded-md p-4">
<PageSkeleton variant="list" />
</div>
</SubSection>
<SubSection title="Page Skeleton (detail)">
<div className="border border-border rounded-md p-4">
<PageSkeleton variant="detail" />
</div>
</SubSection>
</Section>
{/* ============================================================ */}
{/* SEPARATOR */}
{/* ============================================================ */}
<Section title="Separator">
<div className="space-y-4">
<p className="text-sm text-muted-foreground">Horizontal</p>
<Separator />
<div className="flex items-center gap-4 h-8">
<span className="text-sm">Left</span>
<Separator orientation="vertical" />
<span className="text-sm">Right</span>
</div>
</div>
</Section>
{/* ============================================================ */}
{/* ICON REFERENCE */}
{/* ============================================================ */}
{/* TEAM CATALOG */}
{/* ============================================================ */}
<Section title="Team Catalog">
<p className="text-sm text-muted-foreground">
Components from the Team Catalog browse/install surface (<code className="font-mono text-xs">/teams-catalog</code>).
Fixtures are shared with the Storybook stories.
</p>
<SubSection title="TeamRow (browse list)">
<div className="w-[28rem] rounded-md border border-border">
<div className="px-3 py-2 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
Bundled · 1
</div>
<TeamRow team={sampleTeam} selected onSelect={() => {}} />
<div className="px-3 py-2 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
Optional · 2
</div>
<TeamRow team={optionalTeam} selected={false} onSelect={() => {}} />
<div className="px-3 py-2 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
Installed · 2
</div>
<TeamRow team={sampleTeam} selected={false} onSelect={() => {}} installed={outOfDateInstalledState} />
<TeamRow team={warnTeam} selected={false} onSelect={() => {}} installed={currentInstalledState} />
</div>
<p className="mt-2 text-xs text-muted-foreground">
Installed teams collapse under <code className="font-mono">INSTALLED · N</code>; an out-of-date
install (server <code className="font-mono">originHash</code> catalog <code className="font-mono">contentHash</code>)
shows the amber <code className="font-mono"></code> badge (PAP-10256).
</p>
</SubSection>
<SubSection title="TeamCard (onboarding grid)">
<p className="text-xs text-muted-foreground">
Square tile for the onboarding &ldquo;Pick a starter team&rdquo; grid. Selected tile gets{" "}
<code className="font-mono">ring-2 ring-ring</code>. Drives the{" "}
<code className="font-mono">useInstallTeamCatalogEntry</code> simplified flow.
</p>
<TeamCardShowcase />
</SubSection>
<SubSection title="TeamHierarchyPreview">
<div className="max-w-md">
<TeamHierarchyPreview team={sampleTeam} />
</div>
</SubSection>
<SubSection title="RequiredSkillsList">
<div className="max-w-xl">
<RequiredSkillsList skills={sampleTeam.requiredSkills} />
</div>
</SubSection>
<SubSection title="EnvInputsList">
<div className="max-w-xl">
<EnvInputsList inputs={sampleTeam.envInputs} />
</div>
</SubSection>
<SubSection title="ExternalSourcesList">
<div className="max-w-xl">
<ExternalSourcesList sources={sampleTeam.sourceRefs} />
</div>
</SubSection>
<SubSection title="Source policy step (StepSourcePolicy)">
<div className="max-w-xl rounded-md border border-border p-4">
<StepSourcePolicy
team={warnTeam}
allowExternalSources={allowExternal}
allowUnpinnedOptionalSources={allowUnpinned}
allowLocalPathSources={allowLocalPath}
onChange={(key, value) => {
if (key === "external") setAllowExternal(value);
if (key === "unpinned") setAllowUnpinned(value);
if (key === "localPath") setAllowLocalPath(value);
}}
/>
</div>
</SubSection>
<SubSection title="Skill plan step (StepSkillPlan)">
<div className="max-w-xl rounded-md border border-border p-4">
<StepSkillPlan team={sampleTeam} preparations={sampleSkillPreparations} />
</div>
</SubSection>
</Section>
{/* ============================================================ */}
<Section title="Common Icons (Lucide)">
<div className="grid grid-cols-4 md:grid-cols-6 gap-4">
{[
["Inbox", Inbox],
["ListTodo", ListTodo],
["CircleDot", CircleDot],
["Hexagon", Hexagon],
["Target", Target],
["LayoutDashboard", LayoutDashboard],
["Bot", Bot],
["DollarSign", DollarSign],
["History", History],
["Search", Search],
["Plus", Plus],
["Trash2", Trash2],
["Settings", Settings],
["User", User],
["Mail", Mail],
["Upload", Upload],
["Zap", Zap],
].map(([name, Icon]) => {
const LucideIcon = Icon as React.FC<{ className?: string }>;
return (
<div key={name as string} className="flex flex-col items-center gap-1.5 p-2">
<LucideIcon className="h-4 w-4 text-muted-foreground" />
<span className="text-[10px] text-muted-foreground font-mono">{name as string}</span>
</div>
);
})}
</div>
</Section>
{/* ============================================================ */}
{/* KEYBOARD SHORTCUTS */}
{/* ============================================================ */}
<Section title="Keyboard Shortcuts">
<div className="border border-border rounded-md divide-y divide-border text-sm">
{[
["Cmd+K / Ctrl+K", "Open Command Palette"],
["C", "New Task (outside inputs)"],
["[", "Toggle Sidebar"],
["]", "Toggle Properties Panel"],
["Cmd+Enter / Ctrl+Enter", "Submit markdown comment"],
].map(([key, desc]) => (
<div key={key} className="flex items-center justify-between px-4 py-2">
<span className="text-muted-foreground">{desc}</span>
<kbd className="px-2 py-0.5 text-xs font-mono bg-muted rounded border border-border">
{key}
</kbd>
</div>
))}
</div>
</Section>
<Section title="Task Output Surface">
<SubSection title="Multiple outputs (primary video + 'Also produced')">
<IssueOutputSection workProducts={DESIGN_GUIDE_OUTPUTS} />
</SubSection>
<SubSection title="Degraded output (invalid / failed attachment metadata)">
<IssueOutputSection workProducts={DESIGN_GUIDE_DEGRADED_OUTPUTS} />
</SubSection>
<SubSection title="Empty state">
<p className="text-xs text-muted-foreground">
When a task has produced no artifact work products, the Output section renders nothing
at all (no placeholder card).
</p>
</SubSection>
</Section>
</div>
);
}