mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-21 08:22:20 +02:00
## 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>
149 lines
8.9 KiB
TypeScript
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>
|
|
);
|
|
}
|