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.
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, ask Claude to hand it off. It checks the tokens, writes
HANDOFF.mdfor the engineer 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 |
|---|---|
| "Start Storybook" or "Show me the preview" | Runs npm run storybook and gives you the address to open |
| "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 |
| "Hand off the Panel proposal" | Uses the handoff-package skill |
| "Update the status for next time" | Uses the session-handoff skill |
| "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. 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 spacing and a spec card for net-new parts.
- Check tokens.
/check-tokens(ornpm run check:tokens) flags raw colors, font stacks, px spacing, radius and font sizes (with the nearest PDS token) 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, and separates changes that reach them from opt-in additions. It finds who uses what, not how it looks, so look at the listed ones in Storybook. - Hand off. The
handoff-packageskill runs the checks, including impact, and writesHANDOFF.md. - Record the session. The
session-handoffskill refreshesdocs/STATUS.mdso the next session starts informed.
Folder structure
pds-proposals/
├── proposals/ One folder per component proposal
│ ├── Button/ Example proposal
│ │ ├── context.md Why it exists and what it must do (written first)
│ │ ├── decisions.md Each non-obvious choice, as it was made
│ │ ├── custom.md Anything custom or net-new, with a reason
│ │ ├── Button.tsx The component
│ │ ├── button.css Styles, PDS tokens only
│ │ ├── Button.stories.tsx Every variant and state in Storybook
│ │ ├── Button.mdx Usage page
│ │ └── HANDOFF.md Written when ready for an engineer
│ └── _story-helpers/ Shared helpers for stories
├── templates/ Copy-from files for context, decisions, custom, HANDOFF
├── docs/
│ └── STATUS.md Where the work stands (read at the start of each session)
├── scripts/ setup, check-tokens, check-impact, build-site
├── .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
│ │ ├── handoff-package/ Runs the checks and writes HANDOFF.md for the engineer
│ │ └── 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 commandsWhat is in the repo
| Path | What it is |
|---|---|
proposals/ |
One folder per proposal: code, stories, .mdx usage page, context.md, decisions.md, HANDOFF.md |
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) |
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 |
.claude/skills/ |
build-proposal, read-canvas, check-tokens, check-impact, handoff-package, session-handoff |
.claude/settings.json |
Session-start hook; token-check hook after CSS/TSX edits |
.storybook/ |
Storybook config, the Annotate overlay, the sidebar "New" pill |
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 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 check:tokens [-- Name] |
Flags raw values, unknown tokens, and custom or net-new items missing from custom.md |
npm run check:impact [-- Name] |
Lists the PDS components and other proposals that use what a proposal changes (--write saves impact.md) |
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.