Architecture
Rules
- Data direction — Components → Hooks (React Query) →
lib/actions/*→fetchWrapper*. - No server data in stores — stores hold only IDs/filters/UI flags.
- Shareable view state in URL — filters/sort/page live in query params.
- Hooks are pure — no toasts/router in services; components own UX.
- One list shape —
type PageResult<T> = {
items: T[];
page?: number;
totalPages?: number;
nextCursor?: string;
hasNextPage: boolean;
};- Test at seams — adapters few, services most, hooks behavior, a handful of E2E.
🧭
One list shape keeps services interchangeable and hooks simple.
Bad vs Good
Bad
Duplicate shapes per endpoint; pagination in components; inconsistent invalidation.
URL state policy
- Query params:
?q=...&sort=...&page=... - Encoded objects kept shallow; arrays supported via repeated keys.
Where logic lives
- Components: presentation, inputs, toasts, routing.
- Hooks: network behavior, cache keys, invalidation, debouncing.
- Services (
lib/actions): map API → typed shapes, small transforms. - Adapters: base URL, headers, auth,
handleResponseerror mapping.
Implementation Plan (Day 1 → Week 2)
- Day 1–2: add
useInfiniteList,useMutationEx,useDisclosure,useDebouncedValue,useUrlState. ESLint guardrail: disallowfetchWrapper*outsidelib/actions. - Day 3–4: standardize list
PageResultin services; addusePostsInfinite; write tests #1–5. - Day 5: lists/chat domain hooks; write tests #6–9.
- Week 2: Playwright golden path; enforce PR checklist.
Developer PR Checklist
- Server data via React Query hook (no stores).
- Shareable UI state in URL (
useUrlState). - Service returns PageResult; no toasts inside service.
- Mutation invalidates correct keys.
- Tests added at the right seam.
-
fetchWrapper*used only inlib/actions.
Last updated on