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.