import type { Meta, StoryObj } from "@storybook/react-vite"; import { ArrowRight, User } from "lucide-react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { AssigneeChip, AssigneeRunningBanner, ComposerHandoffPreviewRow, ComposerMentionCoach, HandoffWakeRow, InterruptAssignConfirm, PauseAffectsSummaryView, RunStatusBadge, type HandoffChipResolvers, } from "@/components/interrupt-handoff/InterruptHandoffViews"; import { computeComposerHandoffPreview, computePauseAffectsSummary, describeReassignInterrupt, } from "@/lib/interrupt-handoff"; /** * Visual states for the interrupt-handoff UX clarity work (PAP-10669). Each card * is one row of the design's state matrix: interrupted → handed to QA, * interrupted → waiting on user, and no agent selected. */ const resolvers: HandoffChipResolvers = { agentMap: new Map([ ["agent-claude", { name: "ClaudeCoder", icon: null }], ["agent-qa", { name: "QA", icon: null }], ]), currentUserId: "user-board", resolveUserLabel: (id) => (id === "user-board" ? "Riley Board" : null), }; const claude = { agentId: "agent-claude", userId: null }; const qa = { agentId: "agent-qa", userId: null }; const board = { agentId: null, userId: "user-board" }; const unassigned = { agentId: null, userId: null }; function ActivityCard({ title, state, to, interruptedRunAttached, composerTarget, composerCurrent, composerHasActiveRun, }: { title: string; state: string; to: { agentId: string | null; userId: string | null }; interruptedRunAttached?: boolean; composerTarget: string; composerCurrent: string; composerHasActiveRun: boolean; }) { const preview = computeComposerHandoffPreview({ reassignTarget: composerTarget, currentAssigneeValue: composerCurrent, hasActiveRun: composerHasActiveRun, bodyHasAgentMention: false, plainNameCandidate: null, }); return ( {title} {state} {/* Timeline run row */}
ClaudeCoder run 3a648d1b
{/* Assignee change row + wake row */}
Assignee
{/* Composer footer preview */}
Composer preview
); } const meta: Meta = { title: "Surfaces/Interrupt Handoff", }; export default meta; type Story = StoryObj; export const StateMatrix: Story = { render: () => (
), }; export const ComposerCoach: Story = { render: () => (
{}} onDismiss={() => {}} />
), }; export const PlainTextWarning: Story = { render: () => { const preview = computeComposerHandoffPreview({ reassignTarget: "agent:agent-claude", currentAssigneeValue: "agent:agent-claude", hasActiveRun: true, bodyHasAgentMention: false, plainNameCandidate: { agentId: "agent-qa", matchedText: "QA" }, }); return (
); }, }; /** Surface 2 — standalone assignee picker: grouped sections, the live-run * banner, and the "Interrupt & assign" confirm step shown mid-run. */ export const AssigneePicker: Story = { render: () => { const copy = describeReassignInterrupt({ runningAgentName: "ClaudeCoder" }); const sectionHeader = (text: string) => (
{text}
); return (
{sectionHeader("Agents")} {sectionHeader("Board users")}
{}} onCancel={() => {}} />
); }, }; /** Surface 4 — pause/hold dialog "What this affects" bucket summary. */ export const PauseAffects: Story = { render: () => { const live = computePauseAffectsSummary([ { assigneeAgentId: "agent-claude", assigneeUserId: null, activeRun: { status: "running" } }, { assigneeAgentId: "agent-qa", assigneeUserId: null, activeRun: { status: "queued" } }, { assigneeAgentId: "agent-claude", assigneeUserId: null, activeRun: null }, { assigneeAgentId: null, assigneeUserId: "user-board", activeRun: null }, { assigneeAgentId: null, assigneeUserId: null, activeRun: null }, ]); const idle = computePauseAffectsSummary([ { assigneeAgentId: null, assigneeUserId: "user-board", activeRun: null }, { assigneeAgentId: null, assigneeUserId: null, activeRun: null }, ]); return (
); }, };