import type { Meta, StoryObj } from "@storybook/react-vite"; import { AlertTriangle, CheckCircle2, Clock3, Eye, GitPullRequest, Inbox, WalletCards } from "lucide-react"; import { ActivityRow } from "@/components/ActivityRow"; import { ApprovalCard } from "@/components/ApprovalCard"; import { BudgetPolicyCard } from "@/components/BudgetPolicyCard"; import { Identity } from "@/components/Identity"; import { IssueRow } from "@/components/IssueRow"; import { PriorityIcon } from "@/components/PriorityIcon"; import { StatusBadge } from "@/components/StatusBadge"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { storybookActivityEvents, storybookAgentMap, storybookAgents, storybookApprovals, storybookBudgetSummaries, storybookEntityNameMap, storybookEntityTitleMap, storybookIssues, } from "../fixtures/paperclipData"; function Section({ eyebrow, title, children, }: { eyebrow: string; title: string; children: React.ReactNode; }) { return (
{eyebrow}

{title}

{children}
); } function ControlPlaneSurfaces() { return (
Product surfaces

Control-plane boards and cards

Paperclip's common board surfaces are deliberately dense: task rows, approvals, budget policy cards, and audit rows all need to scan quickly while preserving enough state to make autonomous work governable.

company scoped single assignee auditable
{storybookIssues.map((issue, index) => ( undefined} onArchive={() => undefined} desktopTrailing={ {issue.assigneeAgentId ? ( ) : ( Board )} } trailingMeta={index === 0 ? "3m ago" : index === 1 ? "blocked by budget" : "review requested"} mobileMeta={} titleSuffix={ index === 0 ? ( Storybook ) : null } /> ))}
{storybookApprovals.map((approval) => ( undefined : undefined} onReject={approval.status === "pending" ? () => undefined : undefined} detailLink={`/approvals/${approval.id}`} /> ))}
{storybookBudgetSummaries.map((summary, index) => ( undefined : undefined} /> ))}
undefined} />
{storybookActivityEvents.map((event) => ( ))}
Run summary card language Compact status treatments used around live work. {[ { icon: Clock3, label: "Running", detail: "CodexCoder is editing Storybook fixtures", tone: "text-cyan-600" }, { icon: GitPullRequest, label: "Review", detail: "QAChecker requested browser screenshots", tone: "text-amber-600" }, { icon: CheckCircle2, label: "Verified", detail: "Vitest and static Storybook build passed", tone: "text-emerald-600" }, { icon: AlertTriangle, label: "Blocked", detail: "Budget hard stop paused a run", tone: "text-red-600" }, ].map((item) => { const Icon = item.icon; return (
{item.label}
{item.detail}
); })}
{storybookAgents.map((agent) => (
{agent.title}

{agent.capabilities}

{agent.role} {agent.adapterType} ${(agent.spentMonthlyCents / 100).toFixed(0)} spent
))}
Inbox slice Small panels should keep controls reachable without nested cards.
Unread 7
Needs review 3
Blocked 1
Review target
undefined} desktopTrailing={} trailingMeta="active run" />
); } const meta = { title: "Product/Control Plane Surfaces", component: ControlPlaneSurfaces, parameters: { docs: { description: { component: "Product-surface stories exercise the board UI components that carry Paperclip's task, approval, budget, activity, and agent governance workflows.", }, }, }, } satisfies Meta; export default meta; type Story = StoryObj; export const BoardStateMatrix: Story = {};