diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..16b3983e
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,66 @@
+# source.coop
+
+Conventions that aren't visible from the code itself. See
+[CONTRIBUTING.md](CONTRIBUTING.md) for architecture and the PR process.
+
+## Storybook
+
+Stories are published at [ui.source.coop](https://ui.source.coop). Run it locally
+with `npm run storybook`.
+
+Every UI change lands in Storybook in the same PR: a new component gets a
+`.stories.tsx`, a changed one gets its stories updated, a removed one gets its
+stories removed. A state that can only be reached by clicking through the running
+app is a state nobody reviews.
+
+Stories render in a browser bundle, so a component that fetches its own data
+can't have one. `ProductSummaryCard` is an `async` server component whose first
+act is `await getPageSession()` — cookies and a DynamoDB lookup, neither of which
+exists in the browser — so no prop you pass can make it render.
+
+Split such a component in two: the server half keeps the `async`, the session and
+the permission checks, and passes plain values down; the presentational half is
+an ordinary function of its props, knows nothing about sessions, and is the half
+that gets a story. `ProductDoi` is that half of `ProductSummaryCard` — the card
+calls ``, and the story renders `ProductDoi` with any DOI
+it likes.
+
+A component that imports a server action needs `sb.mock()` in
+`.storybook/preview.tsx`, or its story fails with `__filename is not defined`.
+
+Show responsive behavior by pinning a viewport, never by wrapping a story in a
+fixed-width box. A box exactly as wide as the frame has none of the page padding
+the real layout has, so it invents overflow the page doesn't have.
+
+The JSDoc above `meta`, and above each story export, is published prose on
+ui.source.coop — not a code comment. Write it for a reader.
+
+## Pull requests
+
+A PR that touches the UI links the stories it affects, on that branch's Storybook
+deploy:
+
+```
+https://source-coop-ui-git--radiantearth.vercel.app/?path=/story/
+```
+
+Link the specific stories rather than the root, and include a screenshot of
+anything visual — it shows the change in the review itself, and it outlives the
+branch deployment, which goes away with the branch. (Branch deployments ask for a
+Vercel login; ui.source.coop is public.)
+
+The title and description describe the branch as it stands, not as it stood when
+the PR was opened. After pushing to a branch with an open PR, re-read both: if
+the approach has moved on, rewrite them; if they still hold, say so rather than
+leaving it unsaid. A description is read as the spec, so a stale one sends a
+reviewer looking for code that isn't there. Two parts rot first — a Testing
+section asserts checks that may predate the current commits, and a title has to
+keep earning its Conventional Commits type once the work changes shape.
+
+## Comments
+
+A comment says why the code is the way it is, in the present tense. What it used
+to do, what broke, and what a change fixed belong in the commit message and the PR
+description — that is where someone goes looking for history, and it stays
+accurate there as the code moves on. This matters twice over in story JSDoc, which
+is published.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e945596f..3890ce82 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -13,7 +13,9 @@
`feat`, `fix`, `docs`, `test`, `ci`, `refactor`, `perf`, `chore`, `revert`
4. **Preview deployments.** Vercel automatically creates preview deployments for PRs from regular contributors. Outside contributions require approval from a regular contributor before a preview is deployed.
-5. **Get review and merge** into `main`.
+5. **Describe UI changes.** A PR that changes the UI links the stories it affects on that branch's Storybook deployment and includes a screenshot of anything visual — the screenshot shows the change in the review itself, and outlives the branch deployment.
+6. **Keep the PR current.** The title and description describe the branch as it stands. When a push changes the approach, update them — a stale description is read as the spec, and sends a reviewer looking for code that isn't there.
+7. **Get review and merge** into `main`.
## Deployments
@@ -74,6 +76,12 @@ Run tests with `npm test`.
[Radix UI](https://www.radix-ui.com/) is the component library. Components are organized into `src/components/core/` (reusable primitives), `src/components/layout/` (page structure), and `src/components/features/` (domain-specific). Forms use `src/components/core/DynamicForm.tsx` with Next.js server actions.
+### Storybook
+
+Stories live beside their components as `*.stories.tsx` and are published at [ui.source.coop](https://ui.source.coop); each branch also gets its own deployment. Run it locally with `npm run storybook`.
+
+UI changes carry their stories with them — a new component gets stories, a changed one gets them updated, a removed one gets them deleted. The JSDoc above a story file's `meta` and above each story export is published prose, not a code comment.
+
### Server Actions & API Routes
Server actions (`src/lib/actions/`) handle form submissions — they validate with Zod, return `FormState` objects, and call `revalidatePath()` after mutations. API routes (`src/app/api/v1/`) return JSON and use status codes from the `http-status-codes` library. Both use `isAuthorized()` for permission checks.