diff --git a/ui/src/pages/DesignGuide.tsx b/ui/src/pages/DesignGuide.tsx index 0d2f0c59e45..a184b0896c1 100644 --- a/ui/src/pages/DesignGuide.tsx +++ b/ui/src/pages/DesignGuide.tsx @@ -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 ( +
+ {onboardingTeams.map((team) => ( + setSelectedId(team.id)} + /> + ))} +
+ ); +} + /* ------------------------------------------------------------------ */ /* 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 (
@@ -1417,6 +1455,85 @@ export function DesignGuide() { {/* ============================================================ */} {/* ICON REFERENCE */} + {/* ============================================================ */} + {/* TEAM CATALOG */} + {/* ============================================================ */} +
+

+ Components from the Team Catalog browse/install surface (/teams). + Fixtures are shared with the Storybook stories. +

+ + +
+
+ Bundled · 1 +
+ {}} /> +
+ Optional · 2 +
+ {}} /> + {}} /> +
+
+ + +

+ Square tile for the onboarding “Pick a starter team” grid. Selected tile gets{" "} + ring-2 ring-ring. Drives the{" "} + useInstallTeamCatalogEntry simplified flow. +

+ +
+ + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+
+ + +
+ { + if (key === "external") setAllowExternal(value); + if (key === "unpinned") setAllowUnpinned(value); + if (key === "localPath") setAllowLocalPath(value); + }} + /> +
+
+ + +
+ +
+
+
+ {/* ============================================================ */}
diff --git a/ui/storybook/stories/team-catalog.stories.tsx b/ui/storybook/stories/team-catalog.stories.tsx index 012c5e84d2f..d45accae5f4 100644 --- a/ui/storybook/stories/team-catalog.stories.tsx +++ b/ui/storybook/stories/team-catalog.stories.tsx @@ -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 ( + +
+

Pick a starter team

+

+ We'll set up agents, projects, and routines so you can start with a working team. +

+
+
+ {onboardingTeams.map((team) => ( + setSelectedId(team.id)} + /> + ))} +
+ + ); + }, +}; + +// A single TeamCard in its selected state. +export const TeamCardSelected: Story = { + render: () => ( +
+ +
+ ), +}; + export const InstallSuccess: Story = { render: () => (