Files
PaperClipAI/ui/storybook/support/feature-map/FeatureOverview.tsx
T
DottaandPaperclip 5e8e992df2 fix(storybook): reconcile capability associations and map omissions (#16154)
## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - The feature map describes user capabilities and verification
recipes.
> - Storybook holds isolated examples of those capabilities.
> - Missing catalog links can hide existing examples.
> - Some stories also expose shipped capabilities absent from the map.
> - This pull request adds verified source associations and three paired
map entries.
> - Readers can find existing examples without changing their homes or
URLs.

## Linked Issues or Issue Description

Refs #16052, #16049 and #16046.

**What existing behavior does this improve?**

Storybook capability lookup and the feature/agent access maps.

**Current behavior**

Seventy capabilities have no example link. Some matching stories already
exist. Primary-agent preferences and company decision/fast-response
services appear only under broad neighboring capabilities.

**Proposed behavior**

Add source-supported links across 34 suites. Keep shared foundations in
place while cross-listing them. Add primary-agent, decision-model and
fast-response capabilities to both upstream maps. Retain explicit
simulation and partial-example limits.

**Reason and benefit**

The catalog distinguishes missing associations from missing examples.
The upstream map describes three existing product capabilities more
precisely.

**Breaking changes**

None to product behavior, story exports, folders, sidebar titles or
URLs. A shared foundation can declare a reasoned `home: "foundations"`
override while referencing related capabilities.

## What Changed

- Add 55 references across 34 suites. Replace primary-agent suites'
reporting-line association with their own capability.
- Add three source-reviewed capabilities and their API/actor boundaries
to paired maps.
- Preserve the shared Foundations home when cross-listing developer
design examples. Add a validation regression test.
- Record which exports demonstrate each association and what remains
simulated or unverified.
- Regenerate the catalog and capability data. Preserve all original 223
authored suites and 2,210 story IDs. Retain the two skill-removal
stories added by current master, for 2,212 authored stories in total.
- Reduce empty example lookups from 70 of 171 capabilities to 59 of 174.
Links do not establish complete capability coverage.

## Verification

- Passed: 27 catalog/generator/map tests, catalog validation, map
structural checks, token gates and skill validation.
- Passed: source inventory comparison preserves all authored files,
titles, meta IDs and story IDs.
- Passed: repository typecheck/build and Storybook build. Final built
index has 2,248 entries and preserves all previous entries, including
the two skills stories added by current master.
- The broad local repository test run was interrupted before completion
to resolve a new master conflict. A complete local suite pass is not
claimed; fresh CI passed on the reconciled branch.
- Browser: Design links to preserved Foundations stories;
company-service sections link settings/history/journey suites; decision
SetupToHistory passed 12 interaction steps; primary-agent ReplacePrimary
passed four steps. No console errors in selected checks.
- Greptile: 5/5 on final commit
`805a8af62abe589907e7cc6ca1b415ce1e0cc3c0`, with no findings or
unresolved threads.
- Final-revision CI passed all 57 checks/statuses, including all eight
browser shards, server and Runner tests, typecheck and build. The
unchanged Cloud-auth mocked-response race seen on a superseded revision
did not recur on the final commit. No test expectations were changed.
- New agent-access evidence is source-only. Live provider and
agent-credential verification is not established.

## Risks

- Suite-level associations can represent only selected states. Notes
retain those limits and existing review classifications.
- In-memory connection drafts do not prove reload persistence.
Resume-failure UI does not prove successful continuation. No-op budget
controls do not prove relief or resumption.
- Three map capabilities document existing behavior. No permission, API,
provider or production component changes.

## Model Used

OpenAI Codex, GPT-6 family, with reasoning, code execution and source
inspection. The exact runtime model ID and context window are not
exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-10-13 08:11:15 -05:00

149 lines
8.9 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import ReactMarkdown from "react-markdown";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import catalog from "../../../../maps/storybook-map/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 };
const recipeRoot = "https://github.com/paperclipai/paperclip/blob/master/maps/feature-map/";
const allFeatures = 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<Record<string, string>>({});
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<string, IndexEntry> }>;
})
.then(index => {
const next: Record<string, string> = {};
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<string, string> }) {
return (
<li className="space-y-2 py-3">
<div className="flex flex-wrap items-center gap-2">
{links[entry.file]
? <a className="font-medium underline underline-offset-4" href={links[entry.file]} target="_top">{entry.purpose}</a>
: <span className="font-medium">{entry.purpose}</span>}
<Badge variant="outline">{entry.status}</Badge>
<Badge variant="outline">{entry.implementation}</Badge>
<Badge variant="outline">{entry.boundary}</Badge>
</div>
<p className="text-sm text-muted-foreground">{entry.notes}</p>
</li>
);
}
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 foundations = examples.filter(entry => (entry.home === "foundations" || !entry.capabilities.length)
&& ["Shared foundations", entry.file, entry.purpose, entry.status, entry.implementation, entry.boundary].join(" ").toLowerCase().includes(search));
return (
<main className="paperclip-story__inner space-y-6 p-6">
<header className="space-y-3">
<h1 className="text-2xl font-semibold">Paperclip feature map</h1>
<p className="text-sm text-muted-foreground">The same product index as the feature map, with Storybook examples alongside each capability.</p>
<p className="text-sm text-muted-foreground">{allFeatures.length} feature families · {allFeatures.reduce((count, feature) => count + feature.capabilities.length, 0)} capabilities. Examples are fixtures and references, not proof of production behavior.</p>
<a className="text-sm underline underline-offset-4" href={`${recipeRoot}README.md`} target="_blank" rel="noreferrer">Read the feature-map index</a>
<Input aria-label="Find a feature or capability" placeholder="Find a feature, capability, or example…" value={query} onChange={event => setQuery(event.target.value)} />
</header>
{sections.length === 0 && foundations.length === 0 ? <p>No matching features or examples.</p> : null}
{sections.map(section => (
<section key={section.title} className="space-y-3">
<h2 className="text-lg font-semibold">{section.title}</h2>
<ul className="divide-y divide-border">
{section.features.map(feature => {
const suites = examples.filter(entry => entry.capabilities.some(ref => ref.startsWith(`${feature.id}/`)));
return (
<li key={feature.id} className="space-y-2 py-3">
<div className="flex flex-wrap items-center gap-2">
<a className="font-medium underline underline-offset-4" href={featureHref(feature.id)} target="_top">{feature.title}</a>
<span className="text-sm text-muted-foreground">{suites.length ? `${suites.length} related example ${suites.length === 1 ? "suite" : "suites"}` : "No cataloged examples yet"}</span>
</div>
<p className="text-sm text-muted-foreground">{feature.description}</p>
</li>
);
})}
</ul>
</section>
))}
{foundations.length ? (
<section className="space-y-3">
<h2 className="text-lg font-semibold">Shared foundations</h2>
<p className="text-sm text-muted-foreground">Shared UI components sit outside the product feature index.</p>
{error ? <p role="alert">Example links could not load. Reload to try again.</p> : null}
<ul className="divide-y divide-border">{foundations.map(entry => <SuiteLink key={entry.file} entry={entry} links={links} />)}</ul>
</section>
) : null}
</main>
);
}
export function FeatureOverview({ area }: { area: string }) {
const feature = allFeatures.find(item => item.id === area);
const { links, error } = useStoryLinks();
if (!feature) return <p role="alert">This feature is not in the feature-map index.</p>;
const guides = entries.filter(entry => entry.implementation === "guide" && entry.capabilities.some(ref => ref.startsWith(`${area}/`)));
return (
<main className="paperclip-story__inner space-y-6 p-6">
<header className="space-y-3">
<a className="text-sm underline underline-offset-4" href="./?path=/story/overview-storybook-guide--coverage-guide" target="_top">All features</a>
<h1 className="text-2xl font-semibold">{feature.title}</h1>
<p className="text-muted-foreground">{feature.description}</p>
<a className="text-sm underline underline-offset-4" href={`${recipeRoot}${area}.md`} target="_blank" rel="noreferrer">Feature recipe and verification steps</a>
<p className="text-sm text-muted-foreground">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.</p>
{error ? <p role="alert">Example links could not load. Reload to try again.</p> : null}
</header>
<nav aria-label="Capabilities" className="flex flex-wrap gap-3">
{feature.capabilities.map(capability => <a key={capability.id} className="text-sm underline underline-offset-4" href={`#${capability.id.split("/")[1]}`}>{capability.label}</a>)}
</nav>
{feature.capabilities.map(capability => {
const suites = examples.filter(entry => entry.capabilities.includes(capability.id));
return (
<section key={capability.id} id={capability.id.split("/")[1]} className="space-y-3">
<h2 className="text-lg font-semibold">{capability.label}</h2>
<code className="text-xs text-muted-foreground">{capability.id}</code>
<div className="text-sm text-muted-foreground"><ReactMarkdown>{capability.description}</ReactMarkdown></div>
{suites.length
? <ul className="divide-y divide-border">{suites.map(entry => <SuiteLink key={entry.file} entry={entry} links={links} />)}</ul>
: <p className="text-sm">No cataloged example yet. Use the feature recipe for entry points, verification steps, and known limits.</p>}
</section>
);
})}
{guides.length ? <section className="space-y-3"><h2 className="text-lg font-semibold">Related guides</h2><ul className="divide-y divide-border">{guides.map(entry => <SuiteLink key={entry.file} entry={entry} links={links} />)}</ul></section> : null}
</main>
);
}