is <one line: what it is and what it's built with, e.g. "a Next.js app in a pnpm monorepo">.
Designs live in Paper, and the code follows them. Match spacing, sizes and type exactly.
One file per feature, named " ", each holding that feature's boards on one page. Before you design or build, open the file for the feature you're on:
- " Home" ([https://app.paper.design/file/](https://app.paper.design/file/) ): 01 to 01f (the page, , empty, the ⋯ menu), D01 in dark.
- " Settings" ([https://app.paper.design/file/](https://app.paper.design/file/) ): 10 to 10h (General, Account, Billing, Notifications).
- " Shell" ([https://app.paper.design/file/](https://app.paper.design/file/) ): 20 to 20d (sidebar, command menu, errors, offline).
- …
Board rules:
- Every board has an ID that's unique across all files , like "12c Settings / Billing: Card declined". Code and messages cite boards by it.
- A flow takes a number, its states take letters : 12, 12b, 12c… A flow that runs past z takes the next unused number. Next unused: . Update this line when you take one.
- A flow ends with a "How it behaves" board : the rules a screenshot can't show (what Esc does, what happens with 0 or 1,000 items, what can be undone).
- A dark copy's ID starts with D ("D12 Settings / Billing (Dark)").
- A new screen goes in its feature's file, and a new feature gets a file of its own , added to the list above. When a file nears Paper's size limit, split it by part (" Mail / Thread", " Mail / Drafts") and update the list.
I follow along by opening the file in Paper and watching, so:
-
Name the file with the board. Write the board's full name and its file, linked: "12c Settings / Billing: Card declined" in Settings , never "board 12c" alone.
-
Say where before you change it. Before your first write to a file, name the file and the boards you're about to add or change.
-
End with what changed where. The final message lists each file you changed, linked, with its boards.
-
Paper matches the code. A change that alters a screen updates its board in the same task, without asking. A board that's out of date is a bug.
-
Draw with the real parts. Boards use the same tokens, type scale and components as the code, so building one is assembling, not interpreting.
-
Read values, don't eyeball them. Use Paper's MCP (
get_jsx,get_computed_styles) for exact sizes, colors and spacing. Never measure a screenshot. -
Text in rem, everything else in px. Divide a Paper text size or line height by the root font size (<16>) to get rem. Spacing, radius and sizes stay px and match Paper 1:1.
-
Compare before calling it done. Open the built screen in the browser, screenshot it beside the board, and fix what differs.
-
Never reuse real data from screenshots or references. Names, companies and numbers come from the made-up data in
<path/to/fixtures>. -
Components first: HeroUI (
@heroui/react) and HeroUI Pro (@heroui-pro/react) . Write a custom component only when none fits, and make it look like one. -
One icon set: <Gravity UI (
@gravity-ui/icons)>. -
Colors come from tokens in
<path/to/theme.css>, light and dark. No raw hex in components except brand marks. -
Rows, tabs and segments change instantly on hover and focus, with no transitions. A fading highlight makes an app feel slow.
-
Keyboard focus is a thin 1px border , not a 2px accent ring. Text fields keep the ring, which shows where typing goes.
-
Every empty list says why : an icon, what's empty in a few words, what will show up there, and a button when only the user can fix it. Never a bare "No items".
-
** is a skeleton** shaped like the rows it turns into, not "…" text. A spinner is only for an action the user started.
-
A button that waits shows it : a spinner inside, at the same width. Don't also disable it; disable the buttons around it.
-
Lists that can grow are virtualized from the start.
-
Copy: short, plain words. No em dashes in UI copy.
Anything a screen does can be done without the pointer. A new screen ships with its keys, not after them.
-
Plan the keys with the screen. Before building, list what it does and give each frequent action a key. Check
<path/to/shortcuts.ts>first, so a key means the same thing everywhere (↵ open, Esc close or go back, ⌘K command menu, J/K move, Z undo). -
Single-letter keys never fire while typing or inside a menu or dialog.
-
Map every key in
<path/to/shortcuts.ts>, which draws the shortcuts sheet and Settings > Shortcuts. -
Check it by keyboard alone before finishing: move, open, act, undo, close.
-
No query or fetch per item: one query with a join, or the provider's batch endpoint.
-
Run independent calls together (
Promise.all), never one after another. -
Never hold a lock or transaction across a network call.
-
Fetch only as far as the screen needs.
-
Before writing a migration or backfill script: say what data changes and why, then wait for a yes.
Each feature's details live in a CLAUDE.md in its folder, which Claude Code reads once it opens a file there. Keep this file to rules that apply across the app.
<src/features/billing/CLAUDE.md>: Billing (plans, invoices, the card form).- …
<pnpm dev>, <pnpm typecheck>, <pnpm lint>, <pnpm test>.