import { useEffect, useState, type ReactNode } from "react"; import type { Meta, StoryObj } from "@storybook/react-vite"; import { useQueryClient } from "@tanstack/react-query"; import { AlertCircle, KeyRound } from "lucide-react"; import type { CompanySecret, EnvBinding } from "@paperclipai/shared"; import { Secrets } from "@/pages/Secrets"; import { SecretBindingPicker, type SecretBindingValue } from "@/components/SecretBindingPicker"; import { EnvVarEditor } from "@/components/EnvVarEditor"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { useCompany } from "@/context/CompanyContext"; import { queryKeys } from "@/lib/queryKeys"; import { storybookCompanies, storybookSecrets } from "../fixtures/paperclipData"; const COMPANY_ID = "company-storybook"; // Seed localStorage before CompanyContext mounts so its `useState` initializer reads the right id. if (typeof window !== "undefined") { window.localStorage.setItem("paperclip.selectedCompanyId", COMPANY_ID); } function StorybookSecretsFixtures({ children }: { children: ReactNode }) { const queryClient = useQueryClient(); // Seed query caches synchronously so children hydrate from cache on first render. queryClient.setQueryData(queryKeys.secrets.list(COMPANY_ID), storybookSecrets); const { selectedCompanyId, setSelectedCompanyId } = useCompany(); useEffect(() => { if (selectedCompanyId !== COMPANY_ID) { setSelectedCompanyId(COMPANY_ID); } }, [selectedCompanyId, setSelectedCompanyId]); // Block render until the company id is the storybook fixture so the BindingPicker's // useQuery never sees the production-like null state. if (selectedCompanyId !== COMPANY_ID) { return null; } return <>{children}; } const meta: Meta = { title: "Product/Secrets", parameters: { layout: "fullscreen", a11y: { test: "off", }, }, }; export default meta; type Story = StoryObj; function Section({ eyebrow, title, children }: { eyebrow: string; title: string; children: ReactNode }) { return (

{eyebrow}

{title}

{children}
); } export const SecretsInventory: Story = { render: () => (
), }; function BindingPickerSurface({ initial, label, }: { initial: SecretBindingValue | null; label: string; }) { const [value, setValue] = useState(initial); return ( {label} Picker can be reused across agent, project, environment, and plugin config surfaces.
          {JSON.stringify(value, null, 2)}
        
); } export const BindingPicker: Story = { render: () => { return (
); }, }; export const EnvEditorWithSecrets: Story = { render: () => { function EditorDemo({ initial, label }: { initial: Record; label: string }) { const [env, setEnv] = useState>(initial); return ( {label} ({ ...storybookSecrets[0]!, id: `secret-${Math.random().toString(36).slice(2, 8)}`, name, key: name.toLowerCase(), description: `New secret with value len=${value.length}`, })} onChange={(next) => setEnv(next ?? {})} /> ); } return (
); }, }; export const RunFailureCopy: Story = { render: () => (
Run failed PAP-2350 ยท run-storybook
Secret OPENAI_API_KEY is{" "} disabled The agent tried to resolve env.OPENAI_API_KEY for{" "} agent:CodexCoder but the secret is currently disabled. No value was loaded, no run logs were emitted that contained secret material.

Next action

  • Re-enable the secret on{" "} Company settings > Secrets
  • Or, rotate to a new value and pin v3 explicitly for this agent.
  • Or, swap the binding to a different secret with the binding picker.

Audit

secret_access_events.outcome=failure error=secret_disabled consumer=agent:CodexCoder

), };