PDS proposals
A workspace for designing PDS v2 components in code and handing them to an engineer, with Claude Code assisting with the building. Proposals are near-production React, CSS and Storybook stories on top of the v2 @pantheon-systems/pds-toolkit-react package. Nothing here is submitted to the toolkit; each proposal is packaged for an engineer to port.
There are two audiences. Engineers porting the proposals start at For developers. The designer building them with Claude starts at How to get started.
How to get started with the workflow
You don't need to be a developer. You describe what you want, Claude builds it, and you review it in the browser. Use whichever Claude you prefer: the Claude desktop app (the Code tab) or Claude Code in Terminal. Both work the same way here. Do the setup once, then use "Each time you work" after that.
One-time setup
- Install Node. You need version 22.12 or newer. Download it from nodejs.org (the LTS or newer installer) and check it worked by running
node --versionin Terminal. - Install Claude and sign in. Either download the Claude desktop app or install Claude Code for Terminal. Pick one; you can switch later. Tip: in either one you can paste the commands from steps 3 to 5 and ask Claude to run them for you.
- Get this project. In Terminal, run:
git clone https://github.com/pantheon-systems/pds-proposals.git cd pds-proposals - Install the project. Run
npm install. This installs everything the project needs, including the v2 PDS toolkit that every proposal imports, so Storybook shows the real PDS components, CSS and tokens. There is nothing PDS-specific to install yourself. The exact version is pinned inpackage-lock.json, so everyone gets the same one; see Updating PDS to move to a newer release. - Run setup (required). Run
npm run setup. It checks your Node version, clones the PDS v2 source (pds-toolkit-react) into a.pds-reference/folder inside this project (gitignored, separate from any other PDS clone you have), checks out the release that matches the PDS versionnpm installput in, and records where it is for Claude. Claude reads that source to copy a component's source as a base, to follow v2 conventions, and to look up dark-mode colors, because the PDS tool Claude uses only returns light ones. Without it, proposals drift from real PDS. Never edit it. If you skip this step, Claude reminds you at the start of the session.
Each time you work
Open the project in Claude.
- Desktop app: open the Code tab and choose the
pds-proposalsfolder. - Terminal: run
cd pds-proposals, thenclaude.
Claude reads the project rules and
docs/STATUS.mdon its own, so it knows where things stand.- Desktop app: open the Code tab and choose the
Ask Claude to start Storybook, then open localhost:6007. (In Terminal you can also run
npm run storybookyourself in a second window.) Leave it running.Tell Claude what to build. For example: "Build a proposal for a tag input", or paste a Figma link or a brief. Review it in Storybook (light and dark, with the keyboard, with Annotate on) and ask for changes in plain words: "Make the padding match Button."
When it looks right, tell Claude it is ready for dev ("The Panel is ready for dev"). It checks the tokens and what else the proposal could affect, builds Storybook to confirm it passes, writes
HANDOFF.mdfor the engineer, marks it ready (a green dot in the Storybook sidebar) and updatesdocs/STATUS.mdfor next time. You decide when a proposal is ready.
You can just ask
Once Claude is open you don't need to type commands. Say what you want in plain words and Claude runs the right command for you. For example:
| Say this | Claude does this |
|---|---|
| "Load preview", "preview this" or "Start Storybook" (preview and Storybook mean the same thing here) | Uses the preview-proposal skill: starts Storybook if it is not running and opens the proposal's story in Claude's in-app browser |
| "Open the ideation canvas" | Runs npm run canvas |
| "Build a proposal for a tag input" | Uses the build-proposal skill |
| "Build from the board I just sent" | Uses the read-canvas skill |
"Are the tokens clean?" (or /check-tokens) |
Runs the token check and explains any flags |
"What would this change affect?" (or /check-impact) |
Uses the check-impact skill: lists the PDS components and other proposals that use what you changed |
| "The Panel is ready for dev" (or "Hand off the Panel proposal") | Uses the handoff-package skill: runs the token check, the impact check and the Storybook build, writes HANDOFF.md and impact.md, and adds the green ready-for-dev dot in the sidebar |
| "Send this to Figma" (or "port it to Figma", "update Figma") | Uses the send-to-figma skill: sends the proposal to the PDS-Proposals Figma file, one page per proposal, every story (light only; dark mode stays in Storybook) as editable layers, with library icons swapped in and fonts bound to the library's variables. It finds the proposal's page from proposals/<Name>/figma.json (or by name), so a resend replaces the frames in place and a rename renames the page and frames. Claude also asks once when a proposal is marked ready for dev |
| "I renamed the proposal, update Figma" | Same skill: goes straight to the page recorded in figma.json, renames the page and its frames, and updates the notes that link to it |
| "Update the status for next time" | Uses the session-handoff skill |
| "Button is hacked together in production, make it a priority" (or "Panel is needed next sprint") | Records it in docs/PRIORITY.md and regenerates the port order, with everything it depends on pulled up too |
| "What order should the engineer port these in?" | Reads docs/PORT-ORDER.md, or regenerates it with npm run plan:port |
| "Update everything" (after a rename, a ready tag or a new dependency) | Runs npm run refresh and fixes the stale notes it finds. Claude does this on its own when it notices something ripple |
| "Run setup" | Runs npm run setup |
| "What does this error mean?" (paste it) | Explains it and fixes it where it can |
You never have to remember a command name. If you're not sure how to say something, describe the outcome you want.
Updating PDS
npm install never moves to a newer PDS release on its own. The version is pinned in package-lock.json and package.json allows any v2 release (^2.0.0-beta.12), never v1. To bump it deliberately:
- Work on a branch, and ask Claude "Update PDS to the latest v2" (or run
npm update @pantheon-systems/pds-toolkit-react). - Check the new version with
npm ls @pantheon-systems/pds-toolkit-react, then runnpm run typecheckand look through Storybook for anything that changed. - Run
npm run setupagain. It moves the PDS v2 source checkout to the release that matches the new package. Claude also warns you at the start of a session if the two have drifted apart. - Commit
package-lock.json(andpackage.jsonif the range changed), and open a PR for review.
If a release is newer than the range allows (for example a new major), edit the range in package.json first, then run the update.
Workflow in brief
- Ideate (optional). Sketch on the canvas (e.g. Figma) with PDS components, boxes and notes, then Send to Claude. On the ideation canvas you can also double-click a component and click any part inside it to inspect its spacing, type, color, border and radius as PDS tokens, and swap tokens to see the change live; Claude applies those changes to the CSS. The
read-canvasskill turns the board into a direction. You can also skip the canvas and just prompt your intent. - Build. The
build-proposalskill createsproposals/<Name>/withcontext.mdanddecisions.md, then builds the component, CSS and stories using PDS tokens only. - Review. In Storybook, check every story in light and dark, by keyboard, and with Annotate on. Annotate outlines PDS components green and net-new parts purple, and shows the padding and gaps inside every PDS component and part, existing or net-new, with a spec card on click.
- Check tokens.
/check-tokens(ornpm run check:tokens) flags raw colors, font stacks, px spacing, radius and font sizes (with the nearest PDS token; font sizes are caught in thefont-sizeproperty, in custom variables that hold one, in thefontshorthand, insidecalc()and in inlinefontSize) and makes each proposal'scustom.mdlist what is custom or net-new. A hook runs it after every CSS/TSX edit. - Check impact.
/check-impact(ornpm run check:impact) lists the existing PDS components and other proposals that use the parts and tokens this proposal changes, separates changes that reach them from opt-in additions, and lists the proposals that import it or use its classes (for example Timeline Item is used by Chain of Thought). It finds who uses what, not how it looks, so look at the listed ones in Storybook. - Hand off. When you say a proposal is ready for dev, the
handoff-packageskill runs the checks (types, lint, tokens, impact and the Storybook build), writesHANDOFF.mdandimpact.md, and adds theready-for-devtag, which puts a green dot on the component in the Storybook sidebar. - Send to Figma (optional). Say "send to Figma" (or accept Claude's offer when a proposal is ready) and the
send-to-figmaskill captures the stories into the PDS-Proposals Figma file through the Figma MCP server, one page per proposal, so the design team can review and edit them on the canvas. It then opens the page in Claude's in-app browser (sign in to Figma there once). It remembers the page inproposals/<Name>/figma.json, so "update Figma" after an edit replaces the frames in place and a rename renames the page and frames. It works out what the proposal is built from (the dependency graph) and sends the net-new pieces to their own pages, and it checks that PDS tokens came through as Figma variables (swapping icons for the library's and binding any raw font family). The link goes in the proposal'sHANDOFF.md. Edits made in Figma can come back throughfigma-design-to-code. - Refresh what ripples. When something changes that other proposals depend on (a ready tag, a rename, a new dependency, a handoff),
npm run refreshregenerates everyimpact.mdanddocs/PORT-ORDER.md, and Claude fixes the notes that mention the change.docs/PORT-ORDER.mdis the recommended order for the engineer. - Record the session. The
session-handoffskill refreshesdocs/STATUS.mdso the next session starts informed.
Folder structure
pds-proposals/
├── proposals/ One folder per proposal (component proposals and recipes)
│ ├── Timeline/ Example: a proposal that holds two components
│ │ ├── context.md Why it exists and what it must do (written first)
│ │ ├── decisions.md Each non-obvious choice, as it was made, and open questions
│ │ ├── custom.md Anything custom or net-new, with a reason
│ │ ├── impact.md What else the changes could affect (generated)
│ │ ├── Timeline.tsx, TimelineItem.tsx The components
│ │ ├── timeline.css, timeline-item.css Styles, PDS tokens only
│ │ ├── Timeline.stories.tsx, TimelineItem.stories.tsx Every variant and state in Storybook
│ │ ├── Timeline.mdx, TimelineItem.mdx Usage pages
│ │ └── HANDOFF.md Written when you say it is ready for dev
│ ├── EventLogWithAgent/ Example recipe: stories that compose other proposals, no component of its own
│ ├── _base/ Local base code the proposals build on that is not a proposal (the Button and Menu Button copies; see their READMEs)
│ └── _story-helpers/ Shared helpers for stories
├── overview/ The Overview page at the top of the Storybook sidebar (Overview.mdx)
├── templates/ Copy-from files for context, decisions, custom, HANDOFF
├── docs/
│ ├── STATUS.md Where the work stands (read at the start of each session)
│ ├── PRIORITY.md What is needed sooner and why (hand-written; feeds the port order)
│ ├── FIGMA.md The PDS-Proposals Figma file and the index of proposal pages for `send-to-figma`
│ └── PORT-ORDER.md Recommended port order for the engineer (generated; do not edit)
├── scripts/ setup, check-tokens, check-impact, port-order, refresh-derived, build-site
├── .github/ CODEOWNERS (managed by github-terraform; do not edit by hand)
├── .storybook/ Storybook config, Annotate overlay, sidebar badges
├── canvas/ Ideation canvas (saved boards are gitignored)
├── src/ Next.js viewer that shows proposals, source and Storybook
├── .claude/
│ ├── skills/ Playbooks Claude follows for each workflow step
│ │ ├── read-canvas/ Turns an ideation canvas board into a proposal direction
│ │ ├── build-proposal/ Builds a proposal from a Figma link or a brief
│ │ ├── check-tokens/ Flags raw values and undocumented custom or net-new parts
│ │ ├── check-impact/ Lists what else a proposal's changes could affect (also runs at handoff)
│ │ ├── handoff-package/ On "ready for dev": runs the checks, writes HANDOFF.md, adds the ready tag
│ │ ├── preview-proposal/ Opens a proposal's Storybook preview in the in-app browser ("load preview")
│ │ ├── send-to-figma/ Captures a proposal's Storybook stories into a Figma Design file (Figma MCP)
│ │ └── session-handoff/ Refreshes docs/STATUS.md for the next session
│ └── settings.json Session-start and token-check hooks
├── CLAUDE.md Rules and workflow Claude follows here
├── .pds-reference/ Read-only PDS v2 source Claude copies from (created by npm run setup, gitignored)
├── CLAUDE.local.md Tells Claude where it is (created by npm run setup, gitignored)
└── package.json Dependencies and npm run commandsThe Storybook sidebar
Stories are grouped by the first part of their title, not by folder:
- Overview (
overview/Overview.mdx): the first page. What this is, how to review (theme, Annotate, the green dot) and links to every proposal. It is a hand-written list: add a row when a proposal is added or renamed. - Component Proposals (
Component Proposals/<Name>): components with their own API. Related ones are grouped (Buttons, Identity, Timeline, and AI Chat, one group holding the chat pieces: Chat Attachment, Chat Input, Chat Message, Chat Panel and Suggestion Card). - Recipes (
Recipes/<Name>): compositions of existing components, with no API of their own. A recipe lives inproposals/<Name>/like any other proposal; only its story title differs.
Two marks appear next to a component: a purple New pill for a pattern that is not in PDS v2 (tags: ['new-pattern']), and a green dot when it is ready for the engineer (tags: ['ready-for-dev']). The sidebar order is alphabetical by title.
What is in the repo
| Path | What it is |
|---|---|
proposals/ |
One folder per proposal: code, stories, .mdx usage page, context.md, decisions.md, custom.md, impact.md and HANDOFF.md (the last two are written at handoff) |
overview/ |
The Overview page shown first in the Storybook sidebar |
templates/ |
Copy-from files for context.md, decisions.md, custom.md and HANDOFF.md |
scripts/check-tokens.mjs |
The token check (npm run check:tokens) |
scripts/check-impact.mjs |
The impact check (npm run check:impact) |
scripts/port-order.mjs |
The port-order generator (npm run plan:port) |
scripts/refresh-derived.mjs |
Regenerates every impact.md and the port order, and lists notes to re-check (npm run refresh) |
scripts/setup.mjs, scripts/build-site.mjs |
One-time setup, and the site build |
src/, next.config.mjs |
The Next.js viewer that shows proposals, source and Storybook |
docs/STATUS.md |
Where the work stands: proposals, settled conventions, open questions |
docs/FIGMA.md |
The PDS-Proposals Figma file (the only destination) and an index of each proposal's page (index generated by npm run refresh from the figma.json files; read by send-to-figma) |
docs/PRIORITY.md |
What is needed sooner and why (now, next, later); hand-written, read by the port order |
docs/PORT-ORDER.md |
The recommended order for porting the proposals, from priority, dependencies and ready tags (generated) |
.claude/skills/ |
build-proposal, read-canvas, check-tokens, check-impact (also run by handoff-package), handoff-package, preview-proposal, send-to-figma, session-handoff |
.claude/settings.json |
Session-start hook; token-check hook after CSS/TSX edits |
.storybook/ |
Storybook config, the Annotate overlay, the sidebar "ready for dev" dot |
.github/ |
CODEOWNERS, managed by github-terraform (do not edit by hand) |
canvas/ |
The ideation canvas (boards are saved to canvas/boards/, gitignored) |
CLAUDE.md |
Rules and workflow Claude follows here |
The site
The repo root is also a Next.js app (src/). The viewer opens directly on the built Storybook (/storybook). Under /proposals/ it also shows each proposal's notes (context.md, decisions.md, custom.md, HANDOFF.md) and every source file with syntax highlighting, for engineers who want to read the code without cloning. It reads proposals/ and docs/ at build time, so there is nothing to keep in sync.
npm run build # builds Storybook, then the viewer (output in .next/)
npm start # serves it, like Pantheon does
npm run site:dev # live-reloading dev server on port 6009On Pantheon it runs as a Next.js server (build then start in package.json; Node version from engines). Every page is prerendered at build time, so nothing is computed per request. The Pantheon site builds the repo root, because the viewer reads proposals/ and docs/ from it and Pantheon's monorepo mode would hide everything outside the build path.
Commands
| Command | Does |
|---|---|
npm run setup |
One-time setup: checks Node, clones the PDS v2 source into .pds-reference/ at the matching release |
npm run storybook |
Storybook on port 6007 |
npm run canvas |
Ideation canvas on port 6008 |
npm run build:storybook |
Static Storybook to storybook-static/ only |
npm run typecheck / lint / format |
Checks on proposals/ and config |
npm run build / npm start |
Build and serve Storybook + the Next.js viewer |
npm run site:dev |
Live-reloading dev server for the viewer on port 6009 |
npm run check:tokens [-- Name] |
Flags raw values, unknown tokens, and custom or net-new items missing from custom.md |
npm run refresh |
Regenerates every impact.md and docs/PORT-ORDER.md after something ripples, and lists readiness statements to re-check |
npm run plan:port |
Writes docs/PORT-ORDER.md: the recommended order for porting the proposals, from docs/PRIORITY.md, their dependencies and ready tags (generated) |
npm run check:impact [-- Name] |
Lists the PDS components and other proposals that use what a proposal changes (--write saves impact.md) |
For developers: using this as a hand-off
If you are the engineer porting these into pds-toolkit-react, this is your starting point. You do not need Claude, and you do not need to run the designer's workflow. Everything you need is written down in the repo.
What you are getting. Each proposal is near-production React, CSS and Storybook stories built on the published v2 package, plus the reasoning behind it. Nothing here has been submitted to the toolkit. Where the package lacks something (for example mergeClasses, or a new icon), the proposal uses a small local stand-in and says so in its HANDOFF.md; the port swaps in the real one.
Where to start
- Read
docs/PORT-ORDER.md. It is the recommended order, generated from three things: priority (what is needed sooner, set by the designer indocs/PRIORITY.md: already hacked together in production, or needed in current or upcoming sprints), the proposals' dependencies (what a component imports or styles, and what its stories assemble it with, such as the Chat Input's buttons; tier 1 needs nothing, each later tier needs only earlier ones; anything a prioritized proposal needs is pulled up with it) and their ready tags. It also says which proposals are ready and which are blocked or still being worked on. - Pick a proposal that is marked ready (a green dot in the Storybook sidebar,
ready-for-devin the table) and read itsproposals/<Name>/HANDOFF.md. - Run it to see it:
npm install, thennpm run storybookand open localhost:6007, or use the hosted copy if you were given a link. Check light and dark with the Theme toolbar and turn on Annotate to see which parts are existing PDS (green) and which are net-new (purple).
What is in a proposal folder
| File | Read it for |
|---|---|
HANDOFF.md |
The brief: what and why, the API table, tokens used, impact, reused vs net-new, accessibility, open questions, port notes. Start here. |
custom.md |
Exactly what is not plain PDS: net-new components, classes, props, custom CSS variables, and any raw value kept on purpose. This is your list of what to add to the toolkit. |
decisions.md |
Why each non-obvious choice was made, and the open questions that still need an answer. |
impact.md |
What else the change could affect: existing PDS components, and other proposals that import it or use its classes. Generated. |
context.md |
The original problem and constraints. |
<Name>.tsx, <name>.css |
The component and its styles. Tokens only (--pds-*). |
<Name>.stories.tsx, <Name>.mdx |
Every variant and state, and a usage page. |
Two kinds of proposal. In the Storybook sidebar:
- Component Proposals are components with their own API (props, CSS). Most are net-new; some extend an existing PDS component (for example
isAgenticon Button). - Recipes compose existing components for a screen pattern and have no API of their own (for example an event log with an agent in it). There is nothing to port in a recipe beyond the components it uses; copy the composition as documentation.
Porting a proposal
- Port what it depends on first (see
docs/PORT-ORDER.md; eachHANDOFF.mdlists its dependencies under "Impact" and "Port notes"). - For a net-new component, use the toolkit's generator (
npm run generate:component) and copy the files over. For an extension, apply the listed additions to the existing v2 component. - Swap the local stand-ins for the real v2 pieces (the
HANDOFF.mdsays which). Component conventions are in the toolkit's own.claude/CLAUDE.md; the proposals already follow them, so the port should be close to a copy. - Check that no raw values slipped in:
npm run check:tokens -- <Name>here, and the toolkit's own checks there. - Anything marked open in
HANDOFF.mdordecisions.mdis a question for the designer, not a gap to fill by guessing.
Figma. If a proposal was sent to Figma, HANDOFF.md has the link to its page in the PDS-Proposals file under Preview (also in proposals/<Name>/figma.json): the stories as editable layers, in light only. They are captures of the running Storybook, not Figma components linked to the PDS library, except that icons found in the PDS Design Library v2 are swapped for its icon instances and text is bound to its font-family variables.
Reading the code without cloning. The repo is also a small site (see The site) that shows each proposal's notes and every source file with syntax highlighting next to the Storybook.
Optional: the PDS source for comparison. npm run setup clones the PDS v2 source into .pds-reference/ at the release that matches the installed package. It is only needed if you want to diff a proposal against the real component it copies from.
What is generated and what is written by hand. docs/PORT-ORDER.md and every impact.md are generated (npm run refresh rebuilds them), so do not edit them by hand. docs/PRIORITY.md is the one hand-written input to the order. HANDOFF.md, decisions.md, custom.md and context.md are written as the work happens.
Using this for a new set of proposals
Add proposals under proposals/, or clear the existing ones and keep the tooling.
You can also prompt Claude to 'Add a new proposal' (or something similar).
Keep .storybook/, canvas/, templates/, .claude/ and CLAUDE.md; reset docs/STATUS.md once the first proposal exists. Run npm run setup once (see How to get started); it records the required PDS v2 reference checkout.