diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..b63e35eb5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,106 @@ +# AGENTS.md + +Orientation for AI coding agents working in this repository. + +**Read [`docs/glossary.md`](docs/glossary.md) first.** ResearchHub carries a lot of domain jargon — +RFPs, proposals, preregistrations, unified documents, bounties, RSC — and much of it does not mean +what it sounds like. The glossary defines every term with the file that owns it. + +## What this is + +The ResearchHub web app: a Next.js 16 App Router frontend in TypeScript, talking to a separate +Django REST API. ResearchHub is an open-science platform that rewards researchers with +ResearchCoin (RSC) for peer review, open publishing, and funding work. + +This repo contains only the frontend. The backend lives elsewhere; treat its API as a fixed +contract you read from, not something you can change. + +## Commands + +| Command | Purpose | +| ------------------------- | -------------------------------------------------------------------- | +| `npm run dev` | Dev server | +| `npm run type-check` | `tsc --noEmit` — run this after any change | +| `npm run lint` | ESLint across the repo | +| `npm run lint:work-pages` | The `researchhub/work-document-tracking` rule on work document pages | +| `npm run format` | Prettier | +| `npm run test:smoke` | Playwright smoke suite | + +`npm install` needs an `.npmrc` with credentials for two private registries — Tiptap Pro +(`@tiptap-pro`) and FontAwesome Pro (`@fortawesome`, `@awesome.me`). The file is gitignored; see +`.github/workflows/smoke.yml` for the exact contents CI writes. `.nvmrc` pins Node 22, though CI +installs on Node 20. + +The pre-commit hook runs `lint-staged`, which formats staged files, applies `eslint --fix`, and +runs a full `npm run type-check`. Expect commits to be slow, and do not use `--no-verify`. + +CI runs only the smoke suite and a branch mirror. Lint, type-check, and build are not gated by +GitHub Actions, so verify them locally. + +## Layout + +| Path | Contents | +| ----------------------------------------- | ---------------------------------------------------------------------------------- | +| `app/` | App Router routes, plus shared page chrome in `app/layouts/` | +| `components/` | `components/ui/` design-system primitives; every other directory is a feature area | +| `services/` | One `*Service` class per Django domain, all going through `ApiClient` | +| `types/` | Domain models _and_ their `transform*` functions | +| `hooks/`, `contexts/` | Client state — one hook per file, one context per feature | +| `utils/`, `lib/`, `constants/`, `config/` | Helpers, server-only helpers, constants | +| `smoke/` | Playwright specs | +| `.cursor/rules/` | Architecture guides per layer, worth reading before a large change | + +Import with the `@/` alias (`~/` also resolves to the root). `store/` holds static and mock data, +not global state. + +## Conventions that will trip you up + +**Every API payload is transformed.** Django returns snake_case; the app consumes camelCase domain +models. Add a `transform*` function in `types/` built with `createTransformer`, which preserves the +original payload as `.raw`. Never leak raw API shapes into components. + +**All HTTP goes through `ApiClient`** (`services/client.ts`), a static class that attaches the +Django token as `Authorization: Token `. Use `get` for authenticated reads and `getPublic` +for anonymous ones. There is no `BaseService`, no `fetchWithAuth`, and no lowercase `apiClient` +instance. + +**List endpoints are DRF-paginated:** `{ count, next, previous, results }`. `next` is an absolute +URL you can pass straight back to `ApiClient.get`. + +**Data fetching is plain async code.** No React Query for app data (it appears only inside +`contexts/OnchainContext.tsx` for wallet state) and no Server Actions anywhere. Server Components +call services directly; client components call them from hooks. + +**State has no store library.** Reach for local state, then a hook in `hooks/`, then a context in +`contexts/` mounted from `components/providers/ClientProviders.tsx`, then URL params. Do not add +Redux or Zustand. + +**Compose Tailwind classes with `cn()`** from `utils/styles.ts`, and use `cva` for multi-variant +components. Use `components/ui/Button`, not a bare `