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

  1. 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 --version in Terminal.
  2. 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.
  3. Get this project. In Terminal, run:
    git clone https://github.com/pantheon-systems/pds-proposals.git
    cd pds-proposals
  4. 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 in package-lock.json, so everyone gets the same one; see Updating PDS to move to a newer release.
  5. 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 version npm install put 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

  1. Open the project in Claude.

    • Desktop app: open the Code tab and choose the pds-proposals folder.
    • Terminal: run cd pds-proposals, then claude.

    Claude reads the project rules and docs/STATUS.md on its own, so it knows where things stand.

  2. Ask Claude to start Storybook, then open localhost:6007. (In Terminal you can also run npm run storybook yourself in a second window.) Leave it running.

  3. 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."

  4. 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.md for the engineer, marks it ready (a green dot in the Storybook sidebar) and updates docs/STATUS.md for 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:

  1. Work on a branch, and ask Claude "Update PDS to the latest v2" (or run npm update @pantheon-systems/pds-toolkit-react).
  2. Check the new version with npm ls @pantheon-systems/pds-toolkit-react, then run npm run typecheck and look through Storybook for anything that changed.
  3. Run npm run setup again. 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.
  4. Commit package-lock.json (and package.json if 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

  1. 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-canvas skill turns the board into a direction. You can also skip the canvas and just prompt your intent.
  2. Build. The build-proposal skill creates proposals/<Name>/ with context.md and decisions.md, then builds the component, CSS and stories using PDS tokens only.
  3. 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.
  4. Check tokens. /check-tokens (or npm run check:tokens) flags raw colors, font stacks, px spacing, radius and font sizes (with the nearest PDS token; font sizes are caught in the font-size property, in custom variables that hold one, in the font shorthand, inside calc() and in inline fontSize) and makes each proposal's custom.md list what is custom or net-new. A hook runs it after every CSS/TSX edit.
  5. Check impact. /check-impact (or npm 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.
  6. Hand off. When you say a proposal is ready for dev, the handoff-package skill runs the checks (types, lint, tokens, impact and the Storybook build), writes HANDOFF.md and impact.md, and adds the ready-for-dev tag, which puts a green dot on the component in the Storybook sidebar.
  7. Send to Figma (optional). Say "send to Figma" (or accept Claude's offer when a proposal is ready) and the send-to-figma skill 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 in proposals/<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's HANDOFF.md. Edits made in Figma can come back through figma-design-to-code.
  8. Refresh what ripples. When something changes that other proposals depend on (a ready tag, a rename, a new dependency, a handoff), npm run refresh regenerates every impact.md and docs/PORT-ORDER.md, and Claude fixes the notes that mention the change. docs/PORT-ORDER.md is the recommended order for the engineer.
  9. Record the session. The session-handoff skill refreshes docs/STATUS.md so 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 commands

The Storybook sidebar

Stories are grouped by the first part of their title, not by folder:

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 6009

On 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

  1. Read docs/PORT-ORDER.md. It is the recommended order, generated from three things: priority (what is needed sooner, set by the designer in docs/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.
  2. Pick a proposal that is marked ready (a green dot in the Storybook sidebar, ready-for-dev in the table) and read its proposals/<Name>/HANDOFF.md.
  3. Run it to see it: npm install, then npm run storybook and 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:

Porting a proposal

  1. Port what it depends on first (see docs/PORT-ORDER.md; each HANDOFF.md lists its dependencies under "Impact" and "Port notes").
  2. 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.
  3. Swap the local stand-ins for the real v2 pieces (the HANDOFF.md says 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.
  4. Check that no raw values slipped in: npm run check:tokens -- <Name> here, and the toolkit's own checks there.
  5. Anything marked open in HANDOFF.md or decisions.md is 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.