import { useEffect, useMemo, useState } from "react"; import ReactMarkdown, { defaultUrlTransform } from "react-markdown"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import catalog from "../../generated/catalog.json"; import featureIndex from "../../generated/feature-index.json"; type Entry = Omit<(typeof catalog.entries)[number], "capabilities" | "home"> & { capabilities: string[]; home?: string }; const entries: Entry[] = catalog.entries; type IndexEntry = { id: string; type: string; importPath: string }; type Capability = { id: string; label: string; description: string; verification?: { scope: string; storybook: string; production: string; sources: string }; }; type Feature = { id: string; title: string; description: string; capabilities: Capability[] }; const recipeRoot = "https://github.com/paperclipai/paperclip/blob/master/maps/feature-map/"; const allFeatures: Feature[] = featureIndex.sections.flatMap(section => section.features); const examples = entries.filter(entry => entry.implementation !== "guide"); const featureHref = (id: string) => `./?path=/story/feature-map-${id}--overview`; function useStoryLinks() { const [links, setLinks] = useState>({}); const [error, setError] = useState(false); useEffect(() => { const controller = new AbortController(); void fetch(new URL("index.json", window.location.href), { signal: controller.signal }) .then(response => { if (!response.ok) throw new Error("Story index unavailable"); return response.json() as Promise<{ entries: Record }>; }) .then(index => { const next: Record = {}; for (const entry of Object.values(index.entries)) { if (entry.type !== "story") continue; const file = `ui/${entry.importPath.replace(/^\.\//, "")}`; next[file] ??= `./?path=/story/${entry.id}`; } setLinks(next); }) .catch(() => { if (!controller.signal.aborted) setError(true); }); return () => controller.abort(); }, []); return { links, error }; } function SuiteLink({ entry, links }: { entry: Entry; links: Record }) { return (
  • {links[entry.file] ? {entry.purpose} : {entry.purpose}} {entry.status} {entry.implementation} {entry.boundary}

    {entry.notes}

  • ); } export function FeatureMapIndex() { const [query, setQuery] = useState(""); const { links, error } = useStoryLinks(); const search = query.trim().toLowerCase(); const sections = useMemo(() => featureIndex.sections.map(section => ({ ...section, features: section.features.filter(feature => { const related = entries.filter(entry => entry.capabilities.some(ref => ref.startsWith(`${feature.id}/`))); return [section.title, feature.title, feature.id, feature.description, ...feature.capabilities.flatMap(capability => [capability.id, capability.label, capability.description]), ...related.flatMap(entry => [entry.file, entry.purpose, entry.status, entry.implementation]), ].join(" ").toLowerCase().includes(search); }), })).filter(section => section.features.length), [search]); const proposals = examples.filter(entry => entry.home === "design-proposals" && ["Design proposals", entry.file, entry.purpose, entry.notes].join(" ").toLowerCase().includes(search)); const foundations = examples.filter(entry => entry.home !== "design-proposals" && (entry.home === "foundations" || !entry.capabilities.length) && ["Shared foundations", entry.file, entry.purpose, entry.status, entry.implementation, entry.boundary].join(" ").toLowerCase().includes(search)); return (

    Paperclip feature map

    The same product index as the feature map, with Storybook examples alongside each capability.

    {allFeatures.length} feature families · {allFeatures.reduce((count, feature) => count + feature.capabilities.length, 0)} capabilities. Examples are fixtures and references, not proof of production behavior.

    Read the feature-map index setQuery(event.target.value)} />
    {sections.length === 0 && foundations.length === 0 && proposals.length === 0 ?

    No matching features or examples.

    : null} {proposals.length ? (

    Design proposals

    Design work awaiting review and app adoption. These examples do not count as current production coverage.

    {error ?

    Example links could not load. Reload to try again.

    : null}
      {proposals.map(entry => )}
    ) : null} {sections.map(section => (

    {section.title}

      {section.features.map(feature => { const suites = examples.filter(entry => entry.capabilities.some(ref => ref.startsWith(`${feature.id}/`))); return (
    • {feature.title} {suites.length ? `${suites.length} related example ${suites.length === 1 ? "suite" : "suites"}` : "No cataloged examples yet"}

      {feature.description}

    • ); })}
    ))} {foundations.length ? (

    Shared foundations

    Shared UI components sit outside the product feature index.

    {error ?

    Example links could not load. Reload to try again.

    : null}
      {foundations.map(entry => )}
    ) : null}
    ); } export function FeatureOverview({ area }: { area: string }) { const feature = allFeatures.find(item => item.id === area); const { links, error } = useStoryLinks(); if (!feature) return

    This feature is not in the feature-map index.

    ; const guides = entries.filter(entry => entry.implementation === "guide" && entry.capabilities.some(ref => ref.startsWith(`${area}/`))); return (
    All features

    {feature.title}

    {feature.description}

    Feature recipe and verification steps Agent access contract

    Capabilities follow the feature recipe in the same order. Related suites may live under another feature. A suite link does not establish that every behavior has an example or passed a test.

    {error ?

    Example links could not load. Reload to try again.

    : null}
    {feature.capabilities.map(capability => { const suites = examples.filter(entry => entry.capabilities.includes(capability.id)); return (

    {capability.label}

    {capability.id}
    {capability.description}
    {suites.length ?
      {suites.map(entry => )}
    :

    No cataloged example yet. Use the feature recipe for entry points, verification steps, and known limits.

    } {capability.verification ?
    Verification scope

    {capability.verification.storybook}

    Production verification: {capability.verification.production}

    defaultUrlTransform(new URL(url, `${recipeRoot}${area}.md`).href)}>{capability.verification.sources}
    : null}
    ); })} {guides.length ?

    Related guides

      {guides.map(entry => )}
    : null}
    ); }