Status
Where the work stands, for picking up in a new session. Generated and refreshed by the session-handoff skill; read CLAUDE.md first for the rules. Last refreshed: 2026-10-13.
Read this first
- Nothing here has been checked by eye in a session. Headless Chrome hangs in the Claude sandbox, so no session can take screenshots. Every proposal passes
npm run typecheck,npm run lintandnpm run build, but render, animation, hover, open-menu, dark-mode and Annotate behavior is only confirmed when the designer checks in the browser and reports back. Treat "built" as "unverified" until then. - No per-proposal
HANDOFF.mdexists yet. Thehandoff-packageskill has not been run on any proposal. Each proposal hascontext.mdanddecisions.md, which is where the open questions live. - Storybook runs from
npm run storybook(port 6007). Do notpkillit to check the port; it may be the designer's own instance. Changes to.storybook/main.tsormanager.tsxneed a restart; story, component and.mdxedits hot-reload.
Proposals (all under proposals/, Storybook group Proposals)
Sidebar order is set in .storybook/preview.tsx (storySort): Icon, Spinner, Buttons (Button, Icon Button, Menu Button, Send Button), Chat Input, Thinking State, Identity (Agent Avatar, Avatar Label, Identity Block), Agent Step, Chain of Thought.
| Proposal | What it is | Base | Marked net-new |
|---|---|---|---|
| Spinner | color='agentic' (AI gradient) added to the existing color prop; stories also offer discovery (--pds-color-status-discovery-fill) |
v2 Spinner | no (extension) |
| Icon | Local Icon wrapper adding sparklesSolid (third-party agents) and microphoneLines; zappy already exists |
v2 Icon | no |
| Button | isAgentic boolean: AI gradient border on primary and secondary, gradient working spinner |
v2 Button | no (extension) |
| IconButton | isAgentic recolors the border; needs hasBorder; standard variant only |
v2 IconButton | no (extension) |
| MenuButton | isAgentic passed to the trigger (Button or icon-only IconButton) |
v2 MenuButton | no (extension) |
Panel (Proposals/Panel) |
agentic-default, agentic-raised, agentic-sunken variants (existing surface plus the AI gradient border) and isAnimated shimmer |
v2 Panel | no (extension) |
| SendButton | Square filled button, state: send / working / stop; spinnerColor prop |
structure of IconButton | yes |
| ChatInput | Textarea-style field with a toolbar inside it (actionsStart / actionsEnd slots); variant: default / compact; label hidden by default |
Textarea foundations | yes |
ModelSelector (Proposals/Buttons/Model Selector) |
Controlled picker (value, options, onValueChange) wrapping MenuButton; subtle trigger with no fill in any state (color change on hover/open); check on the selected option via a new isSelected item flag on MenuButton |
MenuButton foundations | yes |
ChatMessage (Proposals/Chat Message) |
One turn in a conversation; sender: agent (plain, full width) / user (filled bubble on the right, hugs content, max 80%); children slot; required hidden label for screen readers; plus a thin ChatMessageList that owns the gap between turns (20px) |
none (new) | yes |
SuggestionCard (Proposals/Suggestion Card) |
An agent's proposed changes in chat, built on Panel: title, description, actions slot, state: pending / accepted (+ statusLabel); SuggestionChange rows (field label, before/after with dl) |
PDS Panel | yes |
| ThinkingState | Icon + label + optional elapsed time; sizes xs / s / m; AI shimmer on the label and a pulsing icon while active |
StatusIndicator family | yes |
| AgentStep | One row of agent work: optional status icon, label, quiet secondary text, optional expandable detail | new | yes |
| ChainOfThought | Collapsible list of agent steps; summary line is a Thinking State with a chevron; isOpen, expandWhenActive |
ThinkingState + AgentStep | yes |
AgentAvatar (Identity/Agent Avatar) |
Purpose-built agent avatar so people can tell an agent from a person: circle (default) or square shape, dark fill (--pds-color-foreground-default), icon in --pds-color-foreground-reverse, AI-gradient ring ("halo", hasHalo, default on) with a gap; iconName: zappy (Pantheon agents, default) or sparklesSolid (third-party); sizes xs-xl; required label (screen-reader text), linkContent + ariaLabel |
v2 Avatar, with image / per-user color / user fallback removed | yes |
AvatarLabel (Identity/Avatar Label) |
PDS Avatar Label with the Agent Avatar in place of every Avatar; avatarProps is required (it carries label, iconName, hasHalo); class stays pds-avatar-label |
v2 AvatarLabel | yes |
IdentityBlock (Identity/Identity Block) |
v2 Identity Block with the Agent Avatar in place of Avatar (avatarProps requires label); added a Collapsed story |
v2 IdentityBlock | no |
CodeDiff (Proposals/Code Diff) |
Composes the package PanelList, PanelRow (expandable), CodeBlock (language='diff') and optional FileDiff: a list of changed files, each expanding to its diff |
none (built from PDS parts) | yes |
Cross-proposal dependencies (the port needs these together): SendButton and Button use the local Spinner (for 'agentic'); IconButton, MenuButton and ThinkingState use the local Icon wrapper (for the new icons); MenuButton uses the local Button and IconButton; ChatInput composes IconButton, MenuButton, ModelSelector and SendButton; ChainOfThought composes ThinkingState and AgentStep; AgentAvatar uses the local Icon wrapper (sparklesSolid); AvatarLabel renders the local AgentAvatar.
Conventions that were settled (don't relitigate)
- Agentic is a boolean modifier (
isAgentic), not a variant and not a separate component (Button, IconButton, MenuButton). The Spinner takes it as a color value. The border color token is--pds-gradient-ai-active, the same aspds-ai-active-border. A gradient cannot go inborder-color, so the pattern is a transparent border, the gradient on the border-box, and the fill on the padding-box over an opaque surface layer. - Stories: controls over extra stories. The designer removed standalone Agentic, Link, Button Group, WithTallyMobile and CustomClickHandler stories to keep the sidebar short; "toggle in the controls" is preferred. Storybook defaults are set to show the agentic state (
isAgentic: true) for handoff; component defaults stayfalse. - Fields are not round. Use the standard input radius, not a pill, even for the compact chat bar. Controls and fields follow PDS input conventions.
- No validation on the chat input. Removed on purpose; chat has nothing to validate.
- Label hidden by default on ChatInput (still required, read by screen readers).
- Thinking State: shimmer uses the PDS utility
pds-ai-active-shimmeron the label text only (a greypds-pulsechip was tried and rejected). The icon pulses between 75% and 100% (never larger than normal).showTimerdefaults tofalse; the consumer owns the timer and passes a formattedtime. Sizes are named after the type tokens (xs/s/m). - Net-new vs extension: net-new patterns carry
tags: ['new-pattern']in the story meta. - Comments and wording: keep component TSDoc short; no content hardcoded in components (stories carry sample text).
Usage pages (new)
Every proposal has a <Name>/<Name>.mdx docs page, shown as Usage in the sidebar (<Meta of={...} name='Usage' />, Purpose, Usage, Implementation with a Canvas and Controls for one story). .storybook/main.ts now includes ../proposals/**/*.mdx. The designer has edited them by hand: Usage sections were removed from Button, Icon Button, Menu Button, Spinner and Chain of Thought; Agent Avatar, Agent Step, Icon, Thinking State, Chat Input and Avatar Label keep short ones. The wording is Claude's drafting plus her edits, so ask before adding guidance back.
Hosting
npm run build writes the static Storybook to storybook-static/. The current hosted copy is an unmerged PR on pantheon-systems/claude-prototypes; the plan is to host it from a Next.js site (see README).
Tooling in .storybook/
- Theme toolbar sets
data-themeon<html>;preview.csspaints the canvas from--pds-color-surface-defaultso dark mode shows. The Storybook UI chrome stays light (a dark chrome would need thestorybook-dark-modeaddon, a new dependency, not added). new-patterntag: a purple "New" pill on the component in the sidebar (manager.tsx,renderLabel) and a "New pattern" Badge above each story (preview.tsx). The sidebar pill was fixed late (a missing React import) and not yet confirmed visible.- Annotate (
annotate/Annotate.tsx,annotate/specs.ts,annotate/annotate.css; toolbar toggle, off by default): outlines everypds-*component, green if its class exists in the published package CSS and purple if not (automatic, nothing to register). A new modifier on an existing component (for exampleagentic) shows a purple tag. Icons are checked against the PDS icon list, so hand-drawn ones need apds-icon--<name>class. On net-new parts it also draws padding bands (blue) and gap bands (amber) with px values, and hovering opens a spec card (size, padding, gap, radius, border, type, colors) with the matching PDS token names. Tokens are matched by resolving every token on a probe and comparing values, so a value can match more than one token and the card says so. Margins, shadows and states are not shown. Hardest parts to trust without eyes: band positions on the resizable field, label overlap, and the dark-theme color matching.
Things that behave in a way you might not expect
- The published
@pantheon-systems/pds-toolkit-react@nextpackage CSS is loaded alongside each local copy (same class names). Package rules sit in@layer pds-v2, so unlayered local CSS wins; a rule deleted from a local copy would still come through from the package. - The package does not export
mergeClasses,extractText,StatusDot,NavAccordion,useOverlayZIndex,InputLabelorInputMessage. The proposals use small local stand-ins (proposals/Button/utils.tsx,proposals/MenuButton/NavAccordion.tsx) and note indecisions.mdthat the port swaps in the real ones.useOverlayZIndexis simply omitted from MenuButton. @media (--pds-bp-m)(custom media) does not work in this repo (no postcss plugin). The localbutton.cssuses(min-width: 768px); the port restores the token form.- This repo's TypeScript is stricter than v2's, so several copied files have null-to-undefined fixes with no behavior change.
- The published icon types do not know the new icon names. Local
Iconwidens the type;MenuButtonandIconButtonuse it. Menu items typed with the package'sPDSIconwould need a cast forsparklesSolid.
Open questions (also in each decisions.md)
AgentAvatar: confirm in the browser that the 1px halo and gap read at
xs(2x), and the dark-mode inversion (light disc, dark bolt) is wanted. Should the icon grow whenhasHalois off? Isshape='square'wanted (it was re-added on request after non-agentic options were removed)? No white-on-gradient token exists; the fill and icon use the foreground tokens.AvatarLabel: rename the class (the published package already ships CSS for
pds-avatar-label)? It no longer renders a humanAvatar; for people, use the PDS Avatar Label.Button: do subtle, critical, critical-secondary, reverse and reverse-secondary get the gradient border, or only the spinner? Should agentic combine with
tally/statusDot? Should the border animate likepds-ai-active-border(currently static)?IconButton: should
criticalorreverseget the border?Icon:
sparklesSolidsays "solid" not "sharp"; rename? The sparkles and mic paths come from Font Awesome Pro; confirm the licence covers copied glyphs. The stop glyph (faStopsolid, Free) andmicrophoneLines(faMicrophoneLinesregular, Pro) can go in through the v2 icon registry instead of hand paths.SendButton: should it get
isAgentic? A voice (waveform) state for an empty field? Circle shape was rejected.ChatInput: Enter / Shift+Enter handling (left to the screen); the model selector (now
ModelSelector) shows a chevron (reference shows text only); agentic border on the field; compact growth (one row now); character counter / max length. The reference menus also show trailing status, selected toggles, submenus and count badges, none of whichMenuButtonsupports (extending its item type would be the route).ThinkingState: the finished state (reworded, or collapsible reasoning); placement; whether the shimmer reads in dark mode (the PDS shimmer token is a white band with no dark value).
Spinner: confirm the round arc ends at the smallest sizes at 2x.
Suggested next steps
- Decide whether the hosted Storybook should be refreshed with any later edits.
- The designer walks through each story in light and dark (hover, focus, open menus, Annotate on) and reports problems; fix those first.
- Resolve the open questions above, then run the
handoff-packageskill per proposal (typecheck, lint, token grep,npm run build, a11y panel) to write eachHANDOFF.md. Nothing has been checked against raw color values; grep before handoff (the Spinner mask data URIs containstroke='black', alpha-only). - Commit.