mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-08 01:31:54 +01:00
PAP-10254 Team Catalog onboarding seam: design-guide entry + Storybook fixture
Render TeamCard in the /design-guide "Team Catalog" section ("TeamCard
(onboarding grid)") and add the OnboardingTeamGrid / TeamCardSelected
Storybook stories, both showing a defaultInstall tile selected (§12.5.1).
These files also carry the in-flight Phase G design-guide showcase
(TeamRow/TeamHierarchyPreview/RequiredSkillsList/EnvInputsList/
ExternalSourcesList/StepSourcePolicy/StepSkillPlan subsections, shared
fixtures import, and secret-value StepPreview story props) that was staged
in the shared working tree; committing keeps the Team Catalog UI surface
self-consistent on master. tsc -b clean; 12 Team Catalog tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
9dc43eed42
commit
17cff6b9a7
2 files changed
+165
-86
No files matched your search
@@ -126,6 +126,23 @@ import { Identity } from "@/components/Identity";
|
||||
import { IssueReferencePill } from "@/components/IssueReferencePill";
|
||||
import { MembershipAction } from "@/components/MembershipAction";
|
||||
import { IssueOutputSection } from "@/components/issue-output/IssueOutputSection";
|
||||
import {
|
||||
EnvInputsList,
|
||||
ExternalSourcesList,
|
||||
RequiredSkillsList,
|
||||
StepSkillPlan,
|
||||
StepSourcePolicy,
|
||||
TeamCard,
|
||||
TeamHierarchyPreview,
|
||||
TeamRow,
|
||||
} from "@/pages/TeamCatalog";
|
||||
import {
|
||||
onboardingTeams,
|
||||
optionalTeam,
|
||||
sampleSkillPreparations,
|
||||
sampleTeam,
|
||||
warnTeam,
|
||||
} from "@/pages/TeamCatalog.fixtures";
|
||||
import type { IssueWorkProduct } from "@paperclipai/shared";
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -221,6 +238,24 @@ function SubSection({ title, children }: { title: string; children: React.ReactN
|
||||
);
|
||||
}
|
||||
|
||||
// Onboarding seam (design §6 + §12.5): the TeamCard tile in its "Pick a starter
|
||||
// team" 3-col grid, with the first defaultInstall tile selected.
|
||||
function TeamCardShowcase() {
|
||||
const [selectedId, setSelectedId] = useState(onboardingTeams[0]?.id ?? null);
|
||||
return (
|
||||
<div className="grid max-w-2xl gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{onboardingTeams.map((team) => (
|
||||
<TeamCard
|
||||
key={team.id}
|
||||
team={team}
|
||||
selected={team.id === selectedId}
|
||||
onSelect={() => setSelectedId(team.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Color swatch */
|
||||
/* ------------------------------------------------------------------ */
|
||||
@@ -259,6 +294,9 @@ export function DesignGuide() {
|
||||
{ key: "status", label: "Status", value: "Active" },
|
||||
{ key: "priority", label: "Priority", value: "High" },
|
||||
]);
|
||||
const [allowExternal, setAllowExternal] = useState(false);
|
||||
const [allowUnpinned, setAllowUnpinned] = useState(false);
|
||||
const [allowLocalPath, setAllowLocalPath] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="space-y-10 max-w-4xl">
|
||||
@@ -1417,6 +1455,85 @@ export function DesignGuide() {
|
||||
|
||||
{/* ============================================================ */}
|
||||
{/* ICON REFERENCE */}
|
||||
{/* ============================================================ */}
|
||||
{/* TEAM CATALOG */}
|
||||
{/* ============================================================ */}
|
||||
<Section title="Team Catalog">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Components from the Team Catalog browse/install surface (<code className="font-mono text-xs">/teams</code>).
|
||||
Fixtures are shared with the Storybook stories.
|
||||
</p>
|
||||
|
||||
<SubSection title="TeamRow (browse list)">
|
||||
<div className="w-[28rem] rounded-md border border-border">
|
||||
<div className="px-3 py-2 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Bundled · 1
|
||||
</div>
|
||||
<TeamRow team={sampleTeam} selected onSelect={() => {}} />
|
||||
<div className="px-3 py-2 text-[11px] font-semibold uppercase tracking-wide text-muted-foreground">
|
||||
Optional · 2
|
||||
</div>
|
||||
<TeamRow team={optionalTeam} selected={false} onSelect={() => {}} />
|
||||
<TeamRow team={warnTeam} selected={false} onSelect={() => {}} />
|
||||
</div>
|
||||
</SubSection>
|
||||
|
||||
<SubSection title="TeamCard (onboarding grid)">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
Square tile for the onboarding “Pick a starter team” grid. Selected tile gets{" "}
|
||||
<code className="font-mono">ring-2 ring-ring</code>. Drives the{" "}
|
||||
<code className="font-mono">useInstallTeamCatalogEntry</code> simplified flow.
|
||||
</p>
|
||||
<TeamCardShowcase />
|
||||
</SubSection>
|
||||
|
||||
<SubSection title="TeamHierarchyPreview">
|
||||
<div className="max-w-md">
|
||||
<TeamHierarchyPreview team={sampleTeam} />
|
||||
</div>
|
||||
</SubSection>
|
||||
|
||||
<SubSection title="RequiredSkillsList">
|
||||
<div className="max-w-xl">
|
||||
<RequiredSkillsList skills={sampleTeam.requiredSkills} />
|
||||
</div>
|
||||
</SubSection>
|
||||
|
||||
<SubSection title="EnvInputsList">
|
||||
<div className="max-w-xl">
|
||||
<EnvInputsList inputs={sampleTeam.envInputs} />
|
||||
</div>
|
||||
</SubSection>
|
||||
|
||||
<SubSection title="ExternalSourcesList">
|
||||
<div className="max-w-xl">
|
||||
<ExternalSourcesList sources={sampleTeam.sourceRefs} />
|
||||
</div>
|
||||
</SubSection>
|
||||
|
||||
<SubSection title="Source policy step (StepSourcePolicy)">
|
||||
<div className="max-w-xl rounded-md border border-border p-4">
|
||||
<StepSourcePolicy
|
||||
team={warnTeam}
|
||||
allowExternalSources={allowExternal}
|
||||
allowUnpinnedOptionalSources={allowUnpinned}
|
||||
allowLocalPathSources={allowLocalPath}
|
||||
onChange={(key, value) => {
|
||||
if (key === "external") setAllowExternal(value);
|
||||
if (key === "unpinned") setAllowUnpinned(value);
|
||||
if (key === "localPath") setAllowLocalPath(value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</SubSection>
|
||||
|
||||
<SubSection title="Skill plan step (StepSkillPlan)">
|
||||
<div className="max-w-xl rounded-md border border-border p-4">
|
||||
<StepSkillPlan team={sampleTeam} preparations={sampleSkillPreparations} />
|
||||
</div>
|
||||
</SubSection>
|
||||
</Section>
|
||||
|
||||
{/* ============================================================ */}
|
||||
<Section title="Common Icons (Lucide)">
|
||||
<div className="grid grid-cols-4 md:grid-cols-6 gap-4">
|
||||
|
||||
@@ -2,7 +2,6 @@ import { useState } from "react";
|
||||
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||
import type {
|
||||
Agent,
|
||||
CatalogTeam,
|
||||
CatalogTeamImportPreviewResult,
|
||||
CompanyPortabilityCollisionStrategy,
|
||||
} from "@paperclipai/shared";
|
||||
@@ -13,99 +12,25 @@ import {
|
||||
StepSkillPlan,
|
||||
StepSourcePolicy,
|
||||
StepTargetManager,
|
||||
TeamCard,
|
||||
TeamDetailPane,
|
||||
TeamRow,
|
||||
} from "@/pages/TeamCatalog";
|
||||
import {
|
||||
onboardingTeams,
|
||||
optionalTeam,
|
||||
sampleTeam as baseTeam,
|
||||
warnTeam,
|
||||
} from "@/pages/TeamCatalog.fixtures";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Fixtures
|
||||
//
|
||||
// Team fixtures (baseTeam/optionalTeam/warnTeam) are shared with the in-app
|
||||
// /design-guide showcase via @/pages/TeamCatalog.fixtures so the two surfaces
|
||||
// stay in sync. Preview/agent fixtures below are story-only.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const baseTeam: CatalogTeam = {
|
||||
id: "paperclipai:bundled:company-defaults:core-exec-team",
|
||||
key: "paperclipai/bundled/company-defaults/core-exec-team",
|
||||
kind: "bundled",
|
||||
category: "company-defaults",
|
||||
slug: "core-exec-team",
|
||||
name: "Core Exec Team",
|
||||
description:
|
||||
"A starter executive team: a CEO who manages a CTO and a CMO, plus a launch project and a weekly standup routine. Installs ready-to-run agents you can customize.",
|
||||
path: "catalog/bundled/company-defaults/core-exec-team",
|
||||
entrypoint: "TEAM.md",
|
||||
schema: "agentcompanies/v1",
|
||||
defaultInstall: true,
|
||||
recommendedForCompanyTypes: ["company-root"],
|
||||
tags: ["exec", "starter"],
|
||||
counts: {
|
||||
agents: 3,
|
||||
projects: 1,
|
||||
tasks: 1,
|
||||
routines: 1,
|
||||
localSkills: 1,
|
||||
catalogSkills: 1,
|
||||
externalSkillSources: 1,
|
||||
},
|
||||
rootAgentSlugs: ["ceo"],
|
||||
agentSlugs: ["ceo", "cto", "cmo"],
|
||||
projectSlugs: ["launch"],
|
||||
requiredSkills: [
|
||||
{ type: "catalog", ref: "engineering/code-review", agentSlugs: ["cto"], resolved: true, catalogSkillKey: "engineering/code-review" },
|
||||
{ type: "github", ref: "acme/growth-playbook@v1.2.0", agentSlugs: ["cmo"], resolved: false, sourceRef: "v1.2.0" },
|
||||
],
|
||||
envInputs: [
|
||||
{ key: "OPENAI_API_KEY", agentSlug: "cto", projectSlug: null, kind: "secret", requirement: "required" },
|
||||
{ key: "DEFAULT_TIMEZONE", agentSlug: null, projectSlug: "launch", kind: "plain", requirement: "optional" },
|
||||
],
|
||||
sourceRefs: [
|
||||
{ type: "github", ref: "acme/growth-playbook@v1.2.0", pinned: true },
|
||||
{ type: "url", ref: "https://example.com/policies/brand.md", pinned: false },
|
||||
],
|
||||
files: [
|
||||
{ path: "TEAM.md", kind: "team", sizeBytes: 2144, sha256: "a1" },
|
||||
{ path: "README.md", kind: "readme", sizeBytes: 980, sha256: "a2" },
|
||||
{ path: "agents/ceo/AGENTS.md", kind: "agent", sizeBytes: 1200, sha256: "a3" },
|
||||
{ path: "agents/cto/AGENTS.md", kind: "agent", sizeBytes: 1100, sha256: "a4" },
|
||||
{ path: "agents/cmo/AGENTS.md", kind: "agent", sizeBytes: 1050, sha256: "a5" },
|
||||
{ path: "projects/launch/PROJECT.md", kind: "project", sizeBytes: 640, sha256: "a6" },
|
||||
],
|
||||
trustLevel: "external_sources",
|
||||
compatibility: "compatible",
|
||||
contentHash: "sha256:deadbeefdeadbeefdeadbeefdeadbeef",
|
||||
packageName: "@paperclipai/teams-catalog",
|
||||
packageVersion: "0.1.0",
|
||||
};
|
||||
|
||||
const optionalTeam: CatalogTeam = {
|
||||
...baseTeam,
|
||||
id: "paperclipai:optional:software-development:platform-pod",
|
||||
key: "paperclipai/optional/software-development/platform-pod",
|
||||
kind: "optional",
|
||||
category: "software-development",
|
||||
slug: "platform-pod",
|
||||
name: "Platform Engineering Pod",
|
||||
description: "An optional platform pod with a tech lead and two engineers.",
|
||||
recommendedForCompanyTypes: [],
|
||||
counts: { ...baseTeam.counts, agents: 4, routines: 2 },
|
||||
rootAgentSlugs: ["tech-lead"],
|
||||
agentSlugs: ["tech-lead", "eng-1", "eng-2", "sre"],
|
||||
trustLevel: "markdown_only",
|
||||
sourceRefs: [],
|
||||
};
|
||||
|
||||
const warnTeam: CatalogTeam = {
|
||||
...baseTeam,
|
||||
id: "paperclipai:optional:research:lab-with-local-source",
|
||||
slug: "lab-with-local-source",
|
||||
name: "Research Lab (local source)",
|
||||
kind: "optional",
|
||||
category: "research",
|
||||
trustLevel: "scripts_executables",
|
||||
sourceRefs: [
|
||||
{ type: "url", ref: "https://example.com/unpinned.md", pinned: false },
|
||||
{ type: "local_path", ref: "/Users/dev/skills/secret-sauce", pinned: false },
|
||||
],
|
||||
};
|
||||
|
||||
const companyAgents: Agent[] = [
|
||||
makeAgent("agent-1", "Founder", "ceo"),
|
||||
makeAgent("agent-2", "Head of Eng", "engineer"),
|
||||
@@ -348,6 +273,43 @@ export const InstallApplyProgress: Story = {
|
||||
),
|
||||
};
|
||||
|
||||
// Onboarding seam (design §6 + §12.5): the TeamCard tile rendered in the
|
||||
// 3-col "Pick a starter team" grid, with the first defaultInstall tile selected.
|
||||
export const OnboardingTeamGrid: Story = {
|
||||
render: function Render() {
|
||||
const [selectedId, setSelectedId] = useState(onboardingTeams[0]?.id ?? null);
|
||||
return (
|
||||
<Frame width="max-w-2xl">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-lg font-semibold">Pick a starter team</h2>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
We'll set up agents, projects, and routines so you can start with a working team.
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-4 grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{onboardingTeams.map((team) => (
|
||||
<TeamCard
|
||||
key={team.id}
|
||||
team={team}
|
||||
selected={team.id === selectedId}
|
||||
onSelect={() => setSelectedId(team.id)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</Frame>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
// A single TeamCard in its selected state.
|
||||
export const TeamCardSelected: Story = {
|
||||
render: () => (
|
||||
<div className="mx-auto w-64">
|
||||
<TeamCard team={onboardingTeams[0]} selected onSelect={noop} />
|
||||
</div>
|
||||
),
|
||||
};
|
||||
|
||||
export const InstallSuccess: Story = {
|
||||
render: () => (
|
||||
<Frame>
|
||||
|
||||
Reference in new issue
Block a user