# Frontend conventions checklist (quick reference) The authoritative rules are [`client/CLAUDE.md`](../../../client/CLAUDE.md) and the **frontend-designer** skill (invoke it for any visual work). This is a tick-list. If anything here seems to conflict with those, **they win**. ## Boundaries & structure - [ ] Never add a layout above `[locale]`; `src/app/[locale]/layout.tsx` is the root layout. - [ ] Respect the RSC/client boundary: no `next/headers`, `next-intl/server`, or `@/lib/cookies/server` in client components; no `@/lib/cookies/client` in an RSC. - [ ] New route group / provider / top-level `src/` folder ⇒ update the **Project Structure** tree in `client/CLAUDE.md`. ## Data fetching & caching (prevent extra fetches and re-renders) - [ ] Fetch **only** through `clientFetch`/`serverFetch` (`@/lib/api`); domain calls live in `src/services/{domain}/apis/`. Never raw `fetch()`. - [ ] Server state is **TanStack Query**: a `keys.ts` factory per domain, deliberate `staleTime`/`gcTime`, and **cache invalidation on mutation** (`invalidateQueries`/`setQueryData`) so you never refetch data you already hold. Prefer prefetch/`initialData` from RSC where it removes a client round-trip. - [ ] One hook per file (`use{Action}.ts`), `useQuery`/`useMutation`. Don't toast 401/403/5xx in hooks (the fetch layer already does) — only domain-specific 4xx messages. - [ ] Minimise re-renders: stable references (`useCallback`/`useMemo` only where it pays), `select` to subscribe to slices, colocate state low, lift only when shared. Don't put fast-changing state in a high context provider. ## UI composition - [ ] **Concrete MUI primitives stay MUI** — use `Button`, `Avatar`, `Paper`, `TextField`, etc. (or the existing `App*` wrappers); never invent a new root-level Button/Avatar. - [ ] **Composite/shareable components** (built from primitives, reused in >1 place — a nurse card, an OTP input, a price-breakdown, a step header) live at the right **shared** level (`src/components/…`), not inline in a page or buried in a leaf. A component imported from >1 place gets a co-located `*.test.tsx`. - [ ] Page-only, never-reused composition can stay in the page. ## i18n, theme, direction - [ ] Every user-visible string is a key in **both** `messages/en.json` and `messages/fa.json` (in sync). `fa` is default and **RTL** — design RTL-first; verify mirroring. - [ ] Colours come from `tokens.css` (`var(--bal-…)` / brand tokens), never hardcoded in `sx`. Use the pre-built `APP_THEME_LTR/RTL`; never `createTheme()` in a component. - [ ] MUI **v9** API only (`sx={{ mb: 4 }}`, no v5/v6-only props). ## Quality - [ ] Magic strings → named constants (`src/constants/` or a colocated `constants.ts`). - [ ] Cookies/app state only through the cookie manager; never `document.cookie`/`localStorage` for auth. - [ ] No dead code (unused vars/imports are lint **errors**). Comment the *why*, not the *what*. - [ ] `npm run check` green; `npm run test:ci` green when a shared component changed. - [ ] Types come from the published contract in `dev/contracts/` — don't guess server shapes; if a shape is missing, append a request to `dev/shared-working-context/frontend/requests/for-backend.md` and mock behind the `services/{domain}` seam meanwhile.