mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-29 21:07:40 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - The feature map describes product areas and verification entry points. > - Storybook has 223 story suites that show production components, proposals, and retained history. > - The parent PR maps those suites to features, but their folders and sidebar still use unrelated groups. > - This pull request puts the existing suites under feature folders and uses the feature map's names and order in the sidebar. > - Explicit meta IDs preserve existing story links while the titles change. > - Contributors can find stories through the same product structure without losing examples. ## Linked Issues or Issue Description Refs #16046. This is the requested follow-up. Parent #16046 is merged. This PR now targets master, with its single follow-up commit rebased onto the parent squash merge. The Storybook implementation is unchanged by that rebase. Related open Storybook PRs were checked; this change organizes existing suites and adds no product capability. ## What Changed - Move all 223 story files into folders named for their primary feature area. Keep useful scenario groups below the feature's sidebar label. - Order the sidebar by the eight feature-map sections. Put Overview first and shared Foundations last. Leave areas with no stories out of the sidebar. - Move 19 support files into `ui/storybook/support/` and update relative imports and active documentation. - Preserve all 2,210 story IDs and the single docs ID through explicit CSF meta IDs. Keep all entry names and story exports. - Record stable IDs and sidebar titles in the downstream catalog. Validate feature folders, title prefixes, and IDs. Update the maintenance skill and review evidence. ## Verification - Storybook build passed. Built index comparison against parent `75bd3c977cd828d5222823850339e8204f40eb47` found the same 2,211 entries, IDs, and names. - UI typecheck passed (`ui/node_modules/.bin/tsc -b` from `ui`, invoked as `./node_modules/.bin/tsc -b`). - UI Vitest project: 728 files and 8,176 tests passed. - Catalog checker and its nine tests passed. Feature-map checks, token gates, and the maintenance-skill validator passed. - Chromium: nine existing play cases and one render case reached `storyFinished: success` with no page exceptions. The guide rendered, sidebar sections followed map order, and a search for `steering/queue` opened its preserved story link. - Source comparison found only meta title/ID changes, rebased relative references, and updated fixture path text in story files. - To inspect: build Storybook, start `node scripts/serve-storybook-static.mjs --port 6136`, and open the Overview guide. Expand feature folders and use existing story links. - The complete repository suite was not repeated for this organization change. The parent records a backend timeout that passed in isolation, without a complete broad-suite pass. Visual comparisons remain blocked by the unpublished empty baseline. The separate fixture-server story was not run. ## Risks - Open branches that add stories under old paths will need to choose a feature folder and update the catalog. Cross-feature suites have one primary home and retain related capability references. - Sidebar paths change; story and docs IDs remain stable. Automated references to source paths may need the new location. - This is one requested follow-up with 258 changed files, mostly moves. It exceeds the usual review-size guideline because splitting it would leave the sidebar and folder convention partly migrated. - Browser verification samples ten stories, not all 2,210. Existing production/prototype classifications and review gaps remain unchanged. - Required CI gates and a 5/5 review passed on the original head. Fresh checks and review are running for the rebased head. Catalog validation and all nine checker tests passed after rebase; UI, map, skill, and checker contents are identical to the original reviewed head. ## Model Used - OpenAI Codex, GPT-6. The exact runtime model ID and context-window size are not exposed in this session. Used reasoning, repository inspection, code execution, and local browser automation. ## 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 - [ ] All Paperclip CI gates are green - [ ] 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>
110 lines
7.7 KiB
TypeScript
110 lines
7.7 KiB
TypeScript
import { useEffect, useState } from "react";
|
||
import { useQueryClient } from "@tanstack/react-query";
|
||
import { Layout } from "@/components/Layout";
|
||
import { IssueDetail } from "@/pages/IssueDetail";
|
||
import { AgentDetail } from "@/pages/AgentDetail";
|
||
import { PluginLauncherProvider } from "@/plugins/launchers";
|
||
import { Navigate, Route, Routes } from "@/lib/router";
|
||
import { queryKeys } from "@/lib/queryKeys";
|
||
import { seedIssueDetailCache } from "@/lib/issueDetailCache";
|
||
import { storybookAgents, storybookIssues, storybookAuthSession } from "../../fixtures/paperclipData";
|
||
import type { IssueChatComment, IssueChatLinkedRun } from "@/lib/issue-chat-messages";
|
||
|
||
export function CredentialTaskPage({ otherPerson = false, runtimeMode = "native", newerAttempt = false }: {
|
||
otherPerson?: boolean; runtimeMode?: "legacy" | "native"; newerAttempt?: boolean;
|
||
}) {
|
||
const client = useQueryClient();
|
||
const [fixture] = useState(() => {
|
||
const agent = { ...storybookAgents[0]!, name: "Codie", urlKey: "codie", status: "idle" as const,
|
||
adapterType: runtimeMode === "native" ? "paperclip_runner" : "codex_local" };
|
||
const task = { ...storybookIssues[0]!, id: "credential-task-demo", identifier: "PAP-389",
|
||
title: "Use task names in Slack notifications", description: null, status: "blocked" as const,
|
||
assigneeAgentId: agent.id, assigneeUserId: null, projectId: null, parentId: null,
|
||
executionRunId: null, checkoutRunId: null, executionLockedAt: null,
|
||
labels: [], labelIds: [],
|
||
blockedBy: [], blocks: [], ancestors: [], documentSummaries: [], activeRecoveryAction: null,
|
||
planDocument: null, successfulRunHandoff: null, currentExecutionWorkspace: null,
|
||
};
|
||
const ago = (minutes: number) => new Date(Date.now() - minutes * 60_000).toISOString();
|
||
const comments: IssueChatComment[] = [{
|
||
id: "credential-comment", companyId: task.companyId, issueId: task.id,
|
||
authorType: "user", authorUserId: otherPerson ? "nicky" : "user-board", authorAgentId: null,
|
||
body: "I just saw a similar bug where we use the task ID rather than the human-friendly task name. Confirm whether your earlier fix addresses this bug. If not, implement a fix and open a PR.",
|
||
presentation: null, metadata: null, createdAt: new Date(ago(4)), updatedAt: new Date(ago(4)),
|
||
}, {
|
||
id: "credential-retry-comment", companyId: task.companyId, issueId: task.id,
|
||
authorType: "user", authorUserId: otherPerson ? "nicky" : "user-board", authorAgentId: null,
|
||
body: "I tried reconnecting my OpenAI subscription but Codie still won’t run for me.",
|
||
presentation: null, metadata: null, createdAt: new Date(ago(2)), updatedAt: new Date(ago(2)),
|
||
}];
|
||
const run: IssueChatLinkedRun = {
|
||
runId: "credential-run", agentId: agent.id, agentName: agent.name, adapterType: agent.adapterType,
|
||
responsibleUserId: otherPerson ? "nicky" : "user-board", runtimeMode, status: "failed",
|
||
errorCode: "configuration_incomplete", error: "This credential is not shared with the responsible user",
|
||
createdAt: ago(1), startedAt: null, finishedAt: ago(1), hasStoredOutput: false,
|
||
resultJson: { configurationIncomplete: { selectionFailure: "ai_connection_credential_not_shared",
|
||
credentialAccess: { connectionName: "Dotta’s API Key" } } },
|
||
};
|
||
const runs = newerAttempt ? [run, { ...run, runId: "newer-run", status: "succeeded", errorCode: null, error: null,
|
||
createdAt: ago(0.5), startedAt: ago(0.5), finishedAt: ago(0.1), resultJson: null }] : [run];
|
||
if (newerAttempt) comments.push({ ...comments[0]!, id: "credential-success-comment", authorType: "agent",
|
||
authorUserId: null, authorAgentId: agent.id, body: "The notification now uses the task’s title. I checked the task link and opened the fix for review.",
|
||
runId: "newer-run", createdAt: new Date(ago(0.1)), updatedAt: new Date(ago(0.1)) });
|
||
seedIssueDetailCache(client, task);
|
||
client.setQueryData(queryKeys.auth.session, { ...storybookAuthSession,
|
||
user: { ...storybookAuthSession.user, name: otherPerson ? "Dotta" : "Nicky" } });
|
||
client.setQueryData(queryKeys.agents.list(task.companyId), [agent, ...storybookAgents.slice(1)]);
|
||
client.setQueryData(queryKeys.liveRuns(task.companyId), []);
|
||
client.setQueryData(["email-thread", task.companyId, task.id], null);
|
||
for (const ref of [task.id, task.identifier]) {
|
||
client.setQueryData(["email-thread", task.companyId, ref], null);
|
||
client.setQueryData(queryKeys.issues.comments(ref), { pages: [[...comments].reverse()], pageParams: [null] });
|
||
client.setQueryData(queryKeys.issues.runs(ref), runs);
|
||
client.setQueryData(queryKeys.issues.documents(ref), []);
|
||
client.setQueryData([...queryKeys.issues.documents(ref), "plan"], null);
|
||
client.setQueryData(queryKeys.issues.liveRuns(ref), []);
|
||
client.setQueryData(queryKeys.issues.activeRun(ref), null);
|
||
client.setQueryData(["issues", "tree-control-state", ref], { activePauseHold: null });
|
||
}
|
||
const originalFetch = window.fetch;
|
||
const fetchFixture: typeof fetch = async (input, init) => {
|
||
const raw = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
||
const url = new URL(raw, window.location.origin);
|
||
if (url.pathname === `/api/companies/${task.companyId}/agents`) return Response.json([agent, ...storybookAgents.slice(1)]);
|
||
if (url.pathname === `/api/companies/${task.companyId}/live-runs`) return Response.json([]);
|
||
if ([task.id, task.identifier].some(ref => url.pathname === `/api/companies/${task.companyId}/email/tasks/${ref}`)) return Response.json(null);
|
||
if (url.pathname === `/api/agents/${agent.id}` || url.pathname === "/api/agents/codie") return Response.json(agent);
|
||
const runResource = url.pathname.match(/^\/api\/heartbeat-runs\/(credential-run|newer-run)\/(events|log)$/);
|
||
if (runResource?.[2] === "events") return Response.json([]);
|
||
if (runResource?.[2] === "log") return Response.json({ runId: runResource[1], store: "local", logRef: "storybook", content: "", nextOffset: 0 });
|
||
if (url.pathname === `/api/companies/${task.companyId}/user-directory`) return Response.json({ users: [
|
||
{ principalId: "user-board", status: "active", user: { id: "user-board", name: otherPerson ? "Dotta" : "Nicky", email: "board@example.test", image: null } },
|
||
{ principalId: "nicky", status: "active", user: { id: "nicky", name: "Nicky", email: "nicky@example.test", image: null } },
|
||
] });
|
||
const match = url.pathname.match(/^\/api\/issues\/([^/]+)(?:\/(.*))?$/);
|
||
if (match && [task.id, task.identifier].includes(match[1]!)) {
|
||
const resource = match[2] ?? "";
|
||
if (!resource) return Response.json(task);
|
||
if (resource === "comments") return Response.json([...comments].reverse());
|
||
if (resource === "runs") return Response.json(runs);
|
||
if (resource === "active-run") return Response.json(null);
|
||
if (resource === "documents/plan") return Response.json({ error: "No plan" }, { status: 404 });
|
||
if (resource === "tree-control/state") return Response.json({ activePauseHold: null });
|
||
if (resource === "read") return Response.json({ ok: true });
|
||
return Response.json([]);
|
||
}
|
||
return originalFetch(input, init);
|
||
};
|
||
window.fetch = fetchFixture;
|
||
return { task, restore: () => { if (window.fetch === fetchFixture) window.fetch = originalFetch; } };
|
||
});
|
||
useEffect(() => fixture.restore, [fixture]);
|
||
return <PluginLauncherProvider><Routes>
|
||
<Route path="/:companyPrefix" element={<Layout />}>
|
||
<Route path="issues/:issueId" element={<IssueDetail />} />
|
||
<Route path="agents/:agentId/*" element={<AgentDetail />} />
|
||
</Route>
|
||
<Route path="*" element={<Navigate to={`/PAP/issues/${fixture.task.identifier}`} replace />} />
|
||
</Routes></PluginLauncherProvider>;
|
||
}
|