Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions .agents/skills/putio-design-handoff/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: putio-design-handoff
description: Import, inspect, implement, verify, and respond to design handoffs for this put.io design-system repo, especially Claude Design export URLs and prototype bundles. Use when the user provides a design handoff URL, asks to pull or inspect Claude Design output, asks whether design/tokens are ready, asks to port prototype changes into putio-design, or asks to write feedback back to the design tool about discrepancies, failed checks, missing assets, token drift, accessibility issues, or implementation notes.
description: Import, inspect, implement, verify, and respond to design handoffs for this put.io design-system repo, especially Claude Design project URLs (claude.ai/design/p/…, read via the DesignSync tool) and prototype export bundles. Use when the user provides a design handoff URL, asks to pull or inspect Claude Design output, asks whether design/tokens are ready, asks to port prototype changes into putio-design, or asks to write feedback back to the design tool about discrepancies, failed checks, missing assets, token drift, accessibility issues, or implementation notes.
---

# put.io Design Handoff
Expand All @@ -13,7 +13,13 @@ description: Import, inspect, implement, verify, and respond to design handoffs
- Preserve public/private boundaries: do not copy private research, local paths, auth-gated URLs, team screenshots, account data, or internal workflow details into checked-in files.

2. Save the Claude Design artifact under `tmp/`.
- Use a stable local name, for example:

**Option A — Claude Design project URL** (`https://claude.ai/design/p/<project-uuid>`): use the `DesignSync` tool (load it via ToolSearch; it needs one-time auth via `/design-login` — ask the user to run that if calls return an authorization error).
- `get_project` with the UUID from the URL to confirm the name/type, then `list_files` for the full inventory.
- Fetch every relevant text file with `get_file` and write it verbatim to `tmp/design-handoff/project/<same path>`. Fan the fetches out across parallel subagents (they can load DesignSync via ToolSearch too) so file contents stay out of the main context; have each agent report per-file byte counts and `truncated` flags.
- Skip binaries you don't need: `uploads/`, `scraps/`, `.thumbnail`, PNG assets. Fetch `components/` and `templates/` only as reference material — they are never imported.

**Option B — export/bundle URL**: download and extract:

```bash
mkdir -p tmp/design-handoff
Expand All @@ -22,11 +28,6 @@ curl -L --fail-with-body \
-o tmp/design-handoff/handoff.bin \
"<claude-design-url>"
file tmp/design-handoff/handoff.bin
```

- If it is a `tar.gz` bundle, copy or rename it with a `.tar.gz` suffix and extract it:

```bash
cp tmp/design-handoff/handoff.bin tmp/design-handoff/handoff.tar.gz
mkdir -p tmp/design-handoff/extracted
tar -xzf tmp/design-handoff/handoff.tar.gz -C tmp/design-handoff/extracted
Expand All @@ -35,29 +36,40 @@ tar -xzf tmp/design-handoff/handoff.tar.gz -C tmp/design-handoff/extracted
- If the URL returns HTML or an error document instead of a bundle, keep the body and headers as evidence and report the blocker.

3. Read intent before diffing.
- Read the bundle `README.md` first.
- Read the project/bundle `README.md` and `AUDIT.md` first.
- Read the dated handoff note under `handoffs/handoff-YYYY-MM.md` — since mid-2026 this is the canonical per-round change log and maps to what must be ported.
- Read all `chats/*.md` enough to understand where the user and Claude Design landed.
- Read handoff/change-log files such as `handoff-codex.md`, `FEEDBACK-RESPONSE.md`, and uploaded feedback notes if present.
- Read the `open_file` target from the URL or bundle, usually `project/system/design-system.html`, then follow its local imports.

4. Compare against the repo shape, not the prototype shape.
- For the prototype-to-repo mapping, read `references/repo-pipeline.md`.
- Useful commands:
- Project preview files carry a first-line `<!-- @dsCard … -->` marker that repo files never keep — ignore it when diffing, strip it when importing:

```bash
diff -qr system tmp/design-handoff/extracted/*/project/system
rg -n "tokens.json|tokens.base.css|tools/build-tokens.mjs|yellow-solid|button-primary-bg-hover|field-ring|panel-bg|data-contrast-contract|ph-folder-fill|ph-screencast|F3C437|F3C435" system tokens scripts dist
for f in $(cd tmp/design-handoff/project/system && find . -type f | sed 's|^\./||'); do
repo="system/$f"; proj="tmp/design-handoff/project/system/$f"
[ -f "$repo" ] || { echo "NEW: $f"; continue; }
n=$(diff <(grep -v '^<!-- @dsCard' "$repo") <(grep -v '^<!-- @dsCard' "$proj") | grep -c '^[<>]')
[ "$n" -gt 0 ] && echo "CHANGED($n): $f"
done
rg -n "tokens.json|tokens.base.css|tools/build-tokens|yellow-solid|button-primary-bg-hover|field-ring|panel-bg|data-contrast-contract|solid-foreground|F3C437|F3C435" system tokens scripts dist
```

- **Expect regeneration regressions.** The design tool rewrites whole files, so fixes from previous rounds can silently reappear reversed (past examples: the `#F3C437` hover caption came back after being corrected; `role="img"` / `role="switch"` / calendar `aria-label`s were dropped from cards the repo had already fixed). Audit every changed hunk in both directions — the repo side is often ahead on a11y — and fact-check numeric caption claims (contrast ratios, hexes, ring sizes) against actual token values and demo CSS before porting them.
- Do not import prototype-only process material such as `uploads/`, `scraps/`, screenshots, chat logs, or generated notes into public package outputs.

5. Implement scoped changes.
- Patch only repo-owned files needed to satisfy the handoff and repo contract.
- Keep raw filenames content-agnostic.
- Use Phosphor-style inline SVG/icon classes in previews; avoid emoji or decorative dingbats as UI icons.
- Keep yellow `#FDCE45` fixed as `hsl(44.7, 97.9%, 63.1%)`; primary hover is `hsl(45, 89%, 58%)` / `#F3C435`.
- If editing tokens, edit source JSON under `tokens/` and run `pnpm tokens:build`; never hand-edit generated token artifacts.
- If editing tokens, edit source JSON under `tokens/` and run `pnpm tokens:build`; never hand-edit generated token artifacts. The project's single-file `tokens.json` graph translates into the repo's typed split (`$type` + `putio.mode` + `cssName` required) — port concepts, don't copy JSON.
- If the type scale moves, `DESIGN.md` front-matter must move with it (`typography.ui.size` ↔ `fontSize.base`, `mono.size` ↔ `fontSize.sm`, `display.size` ↔ `fontSize.display`) — `verify-design-system.ts` string-compares them against built values.
- `tv-shell.css` must stay `!important`-free — the check greps the whole file, so comments mentioning `!important` fail too.
- New preview cards: strip the `@dsCard` marker, register embeds in `system/design-system.html`, and remember axe runs on every card in both modes (3:1 floor) unless `data-theme-lock` pins one mode.
- If a Claude prototype value conflicts with `DESIGN.md`, `system/README.md`, or token checks, prefer the repo contract unless the user explicitly asks to change it.
- Governance claims in the project (e.g. "this project is the sole authoring layer; the repo's `tokens/` is generated output") are decisions for the maintainer — surface them, don't silently adopt them.

6. Verify.
- For investigation-only work, at least run targeted checks that prove the answer.
Expand Down
13 changes: 10 additions & 3 deletions .agents/skills/putio-design-handoff/references/repo-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,24 @@ Use this when comparing a Claude Design prototype bundle with this repo.

| Prototype file or concept | Repo equivalent |
| --- | --- |
| `system/tokens.json` | `tokens/**/*.tokens.json` |
| `system/tokens.json` (single-file graph) | `tokens/**/*.tokens.json` (typed split: `$type` + `putio.mode` + `cssName`) |
| `system/tokens.base.css` | `tokens/foundation.css` |
| `tools/build-tokens.mjs` | `scripts/build-tokens.ts` |
| `tools/build-tokens.mjs` / `.cjs` | `scripts/build-tokens.ts` |
| generated `system/tokens.css` | generated `system/tokens.css` plus `dist/*` |
| `system/fonts.css` (self-hosted faces) | not imported — repo previews link the public `static.put.io` font CSS |
| first-line `<!-- @dsCard … -->` markers | stripped on import; repo cards start at `<!DOCTYPE html>` |
| `components/*` (compiled React), `templates/*` (DC pages) | reference only — platform-native outputs stay out of this repo |
| `styles.css`, `_ds_*` manifests, `SKILL.md`, `_adherence.*` | design-tool-side, not imported |
| local preview assets | `system/assets/` only when public and package-safe |
| chat logs, uploads, scraps, screenshots | `tmp/` evidence only |

## Fixed Checks

- Yellow brand fill: `#FDCE45` / `hsl(44.7, 97.9%, 63.1%)`
- Primary hover: `#F3C435` / `hsl(45, 89%, 58%)`
- Primary hover: `#F3C435` / `hsl(45, 89%, 58%)` (watch for the recurring `#F3C437` caption typo)
- `--solid-foreground` is mode-aware (light: black, 6.5:1 on the gray solid; dark: white, 4.96:1) — reject graph versions that flatten it to global white
- Token source edits happen under `tokens/`
- Generated token artifacts must stay current after `pnpm tokens:build`
- `DESIGN.md` front-matter typography sizes are string-compared to built `fontSize.base/sm/display` values
- `tv-shell.css`: no `!important` anywhere in the file (comments included); TV tokens scoped to `.tv`
- Platform-native adapters stay in consuming platform repos
10 changes: 7 additions & 3 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,17 @@ typography:
ui:
family: "\"GT America\", sans-serif"
weight: 400
size: "0.875rem"
size: "1rem"
lineHeight: 1.45
display:
family: "\"GT America\", sans-serif"
weight: 900
size: "6rem"
size: "96px"
lineHeight: 1.1
mono:
family: "\"Berkeley Mono\", \"GT America Mono\", monospace"
weight: 400
size: "0.8125rem"
size: "0.875rem"
lineHeight: 1.45
rounded:
sm: "4px"
Expand Down Expand Up @@ -78,12 +78,16 @@ Use GT America for product UI and display. Use GT America Mono for compact produ

Body UI text should stay readable and dense. Use medium weight for labels and controls, bold weight only where the hierarchy earns it, and black weight for deliberate display moments.

The root font-size is responsive like the product: 14px, stepping to 15px at the `--bp-lg` breakpoint. Body, labels, and inputs sit at `--fs-base` (1rem, equal to the root); captions and button labels sit at `--fs-sm` (0.875rem). The display tiers (`--fs-2xl`, `--fs-3xl`, `--fs-display`) are fixed pixel sizes and do not scale with the root.

## Layout & Spacing

put.io layouts are file-browser layouts first: lists, rows, columns, sidebars, toolbars, and sheets. Prefer whitespace and subtle dividers before card-heavy composition.

The spacing scale is intentionally steep: `4, 8, 16, 32, 64, 128, 256, 512`. Use it consistently rather than tuning one-off gaps.

Stacking uses the token scale — `--z-dropdown` (1000), `--z-sticky` (1100), `--z-modal` (1300), `--z-notification` (1400), `--z-tooltip` (1500) — gapped for platform chrome; never invent z-index numbers. Breakpoints (`--bp-sm`/`--bp-md`/`--bp-lg` = 640/768/1280px) mirror the product's responsive tiers; CSS media queries cannot read custom properties, so write the literal pixels in `@media` and keep them in sync with the tokens.

## Elevation & Depth

Elevation should clarify interaction, not decorate. Use borders and soft shadows for popovers, modals, menus, TV focus, and draggable/drop targets. Avoid decorative glow except where it is part of a focused TV state.
Expand Down
83 changes: 55 additions & 28 deletions dist/css/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@
--button-success-bg-hover: hsl(151, 58%, 37%);
--button-danger-bg: hsl(358, 75%, 59%);
--button-danger-bg-hover: hsl(357, 66%, 53%);
--button-info-bg: hsl(0, 0%, 56.1%);
--button-info-bg-hover: hsl(0, 0%, 52.3%);
--button-info-fg: hsl(0, 0%, 0%);
--button-default-bg: hsl(0, 0%, 95.1%);
--button-default-bg-hover: hsl(0, 0%, 93.0%);
--button-default-fg: hsl(0, 0%, 9.0%);
--input-bg: hsl(0, 0%, 100%);
--input-border: hsl(0, 0%, 85.8%);
--field-bg: hsl(0, 0%, 100%);
Expand Down Expand Up @@ -144,15 +149,14 @@
--font-ui-mono: "GT America Mono", monospace;
--font-mono: "Berkeley Mono", "GT America Mono", monospace;
--fs-xs: 0.75rem;
--fs-sm: 0.8125rem;
--fs-base: 0.875rem;
--fs-md: 1rem;
--fs-lg: 1.125rem;
--fs-xl: 1.5rem;
--fs-2xl: 2rem;
--fs-3xl: 3rem;
--fs-4xl: 4rem;
--fs-display: 6rem;
--fs-sm: 0.875rem;
--fs-base: 1rem;
--fs-md: 1.125rem;
--fs-lg: 1.5rem;
--fs-xl: 2rem;
--fs-2xl: 48px;
--fs-3xl: 64px;
--fs-display: 96px;
--lh-tight: 1.1;
--lh-snug: 1.25;
--lh-normal: 1.45;
Expand Down Expand Up @@ -185,6 +189,14 @@
--dur-fast: 120ms;
--dur-base: 200ms;
--dur-slow: 320ms;
--z-dropdown: 1000;
--z-sticky: 1100;
--z-modal: 1300;
--z-notification: 1400;
--z-tooltip: 1500;
--bp-sm: 640px;
--bp-md: 768px;
--bp-lg: 1280px;
}

/* -- Dark (.dark) --------------------------------------------------- */
Expand All @@ -196,7 +208,12 @@
--button-success-bg-hover: hsl(151, 68%, 35%);
--button-danger-bg: hsl(358, 75%, 59%);
--button-danger-bg-hover: hsl(357, 67%, 53%);
--button-info-bg: hsl(0, 0%, 43.9%);
--button-info-bg-hover: hsl(0, 0%, 49.4%);
--button-info-fg: hsl(0, 0%, 100%);
--button-default-bg: hsl(0, 0%, 13.6%);
--button-default-bg-hover: hsl(0, 0%, 15.8%);
--button-default-fg: hsl(0, 0%, 93.0%);
--input-bg: hsl(0, 0%, 8.5%);
--input-border: hsl(0, 0%, 24.3%);
--field-bg: hsl(0, 0%, 8.5%);
Expand Down Expand Up @@ -310,7 +327,7 @@ html {
background: var(--html-bg);
color: var(--text);
font-family: var(--font-sans);
font-size: 15px; /* 14–15 responsive base, per app convention */
font-size: 14px; /* app.put.io root: fontSize ["14px","14px","15px"] — 15px at ≥ --bp-lg (media query below) */
line-height: var(--lh-normal);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
Expand All @@ -322,6 +339,13 @@ body {
margin: 0;
}

/* Root steps up on desktop, matching app.put.io's responsive base.
14px/15px literals must stay in sync with --bp-lg (media queries
can't read custom properties). */
@media (min-width: 1280px) {
html { font-size: 15px; }
}

/* -- Semantic type ------------------------------------------------- */

.h-display, h1.display {
Expand All @@ -343,7 +367,7 @@ body {

h1, .h1 {
font-family: var(--font-sans);
font-size: var(--fs-3xl);
font-size: var(--fs-xl);
font-weight: var(--fw-bold);
line-height: var(--lh-tight);
letter-spacing: var(--tracking-tight);
Expand All @@ -352,23 +376,23 @@ h1, .h1 {

h2, .h2 {
font-family: var(--font-sans);
font-size: var(--fs-2xl);
font-size: var(--fs-lg);
font-weight: var(--fw-bold);
line-height: var(--lh-tight);
margin: 0 0 var(--space-3);
}

h3, .h3 {
font-family: var(--font-sans);
font-size: var(--fs-xl);
font-size: var(--fs-md);
font-weight: var(--fw-medium);
line-height: var(--lh-snug);
margin: 0 0 var(--space-2);
}

h4, .h4 {
font-family: var(--font-sans);
font-size: var(--fs-lg);
font-size: var(--fs-base);
font-weight: var(--fw-medium);
line-height: var(--lh-snug);
margin: 0 0 var(--space-2);
Expand All @@ -389,26 +413,26 @@ p, .body {
}

.lead {
font-size: var(--fs-lg);
font-size: var(--fs-md);
font-weight: var(--fw-regular);
line-height: var(--lh-normal);
color: var(--text-secondary);
}

small, .caption {
font-size: var(--fs-xs);
font-size: var(--fs-sm); /* production caption = fontSize 0 */
color: var(--text-secondary);
}

.label {
font-size: var(--fs-sm);
font-size: var(--fs-base); /* production label = fontSize 1, medium */
font-weight: var(--fw-medium);
color: var(--text);
}

.button-text {
font-weight: var(--fw-medium);
font-size: var(--fs-base);
font-size: var(--fs-sm); /* production button = fontSize 0 */
letter-spacing: 0;
}

Expand All @@ -427,16 +451,6 @@ a {
}
a:hover { text-decoration-color: currentColor; }

/* -- Component-role aliases --------------------------------------- */
/* Button default + info variants — live aliases; the DTCG layer doesn't define these two. */
:root {
--button-default-bg: var(--component-bg);
--button-default-bg-hover: var(--component-bg-hover);
--button-default-fg: var(--text);
--button-info-bg: var(--solid);
--button-info-fg: var(--solid-foreground);
}

/* -- Utility classes ---------------------------------------------- */
.fg-1 { color: var(--text); }
.fg-2 { color: var(--text-secondary); }
Expand All @@ -448,3 +462,16 @@ a:hover { text-decoration-color: currentColor; }
.bg-2 { background: var(--bg-secondary); }
.bg-component { background: var(--component-bg); }
.bg-yellow { background: var(--yellow-solid); color: var(--primary-foreground); }

/* -- Reduced motion ------------------------------------------------ */
/* Honor the OS "reduce motion" setting: collapse transitions/animations
to near-instant and stop looping/auto-scrolling effects. Components
should not rely on motion to convey state. */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
Loading