import type { Meta, StoryObj } from "@storybook/react-vite"; import type { ReactNode } from "react"; import type { IssueRecoveryAction, IssueRelationIssueSummary } from "@paperclipai/shared"; import { Eye, ExternalLink, OctagonAlert, RefreshCw, TriangleAlert } from "lucide-react"; import { IssueRecoveryActionCard } from "@/components/IssueRecoveryActionCard"; import { IssueRow } from "@/components/IssueRow"; import { IssueBlockedNotice } from "@/components/IssueBlockedNotice"; import { storybookAgentMap, storybookAgents, createIssue } from "../fixtures/paperclipData"; const claudeAgent = storybookAgents.find((agent) => agent.name.toLowerCase().startsWith("claude")) ?? storybookAgents[0]!; const codexAgent = storybookAgents.find((agent) => agent.name.toLowerCase().startsWith("codex")) ?? storybookAgents[0]!; function StoryFrame({ title, description, children }: { title: string; description?: string; children: ReactNode }) { return (
Source-issue recovery

{title}

{description ? (

{description}

) : null}
{children}
); } function buildAction(overrides: Partial = {}): IssueRecoveryAction { return { id: "00000000-0000-0000-0000-0000000000aa", companyId: "company-storybook", sourceIssueId: "00000000-0000-0000-0000-0000000000ff", recoveryIssueId: null, kind: "missing_disposition", status: "active", ownerType: "agent", ownerAgentId: claudeAgent.id, ownerUserId: null, previousOwnerAgentId: codexAgent.id, returnOwnerAgentId: codexAgent.id, cause: "missing_disposition", fingerprint: "fp", evidence: { summary: "Run finished without picking a disposition. The PR has tests passing on CI.", sourceRunId: "7accd7a4-c9ca-4db2-9233-3228a037cc09", correctiveRunId: "2606404d-3859-4142-ba37-3228a037cc09", }, nextAction: "Choose and record a valid issue disposition without copying transcript content.", wakePolicy: { type: "wake_owner" }, monitorPolicy: null, attemptCount: 1, maxAttempts: 3, timeoutAt: null, lastAttemptAt: "2026-04-20T11:55:00.000Z", outcome: null, resolutionNote: null, resolvedAt: null, createdAt: "2026-04-20T11:55:00.000Z", updatedAt: "2026-04-20T11:55:00.000Z", ...overrides, }; } function CardPanel({ caption, action, forcedState, canFalsePositive }: { caption: string; action: IssueRecoveryAction; forcedState?: React.ComponentProps["forcedState"]; canFalsePositive?: boolean; }) { return (
{caption}
{}} canFalsePositive={canFalsePositive} />
); } function AllStatesPanel() { return (
); } function buildBlocker( overrides: Partial = {}, ): IssueRelationIssueSummary { return { id: "blocker-1", identifier: "PAP-9065", title: "Add full company search page", status: "in_progress", priority: "medium", assigneeAgentId: claudeAgent.id, assigneeUserId: null, ...overrides, }; } function BlockerNoticePanel() { return (
); } type RunCardRecoveryState = "needed" | "in_progress" | "observe_only" | "escalated"; const RUN_CARD_RECOVERY_TONE: Record = { needed: { icon: TriangleAlert, label: "Recovery needed", className: "border-amber-500/60 bg-amber-500/15 text-amber-700 dark:text-amber-300", }, in_progress: { icon: RefreshCw, label: "Recovery in progress", className: "border-sky-500/60 bg-sky-500/15 text-sky-700 dark:text-sky-300", }, observe_only: { icon: Eye, label: "Observing active run", className: "border-border bg-muted text-muted-foreground", }, escalated: { icon: OctagonAlert, label: "Recovery escalated", className: "border-red-500/60 bg-red-500/15 text-red-700 dark:text-red-300", }, }; function ActiveRunRecoveryChip({ state }: { state: RunCardRecoveryState }) { const tone = RUN_CARD_RECOVERY_TONE[state]; const Icon = tone.icon; return ( {tone.label} ); } function ActiveRunCardMock({ identifier, title, recoveryState, }: { identifier: string; title: string; recoveryState: RunCardRecoveryState; }) { return (
CodexCoder
Live now
{identifier} - {title}
Live transcript…
); } function ActiveRunPanel() { return (
); } function InboxRowPanel() { const baseIssue = createIssue(); return (
); } const meta = { title: "Paperclip/Source Issue Recovery", component: AllStatesPanel, parameters: { layout: "fullscreen" }, } satisfies Meta; export default meta; type Story = StoryObj; export const RecoveryActionCardStates: Story = { render: () => ( ), }; export const InboxRowChips: Story = { render: () => ( ), }; export const BlockerNoticeRecoveryIndicators: Story = { render: () => ( ), }; export const ActiveRunPanelRecoveryChips: Story = { render: () => ( ), };