update
This commit is contained in:
@@ -0,0 +1,387 @@
|
||||
# AGENTS.md — Frontend Coding Conventions (React & Next.js)
|
||||
|
||||
These rules apply to every task in this repository. Follow them exactly. If a rule conflicts with existing code, follow the rule for new code and do not refactor unrelated files. If a rule is unclear, ask before guessing.
|
||||
|
||||
## 0. How this file is organized
|
||||
|
||||
- **Sections 1–13 are general** and apply to every project (React or Next.js).
|
||||
- **Section 14 is framework-specific.** Detect the project type first:
|
||||
- `next` in `package.json` dependencies → **Next.js** project → also follow 14.1.
|
||||
- Otherwise (Vite, CRA, etc.) → **React** project → also follow 14.2.
|
||||
- Never apply the rules of the other framework.
|
||||
|
||||
## 1. Core principles
|
||||
|
||||
1. **Readability beats cleverness.** Write the simplest code that works. A teammate must understand it on the first read.
|
||||
2. **Easy to extend.** Small, single-purpose functions and components; clear layers; no hidden coupling.
|
||||
3. **Don't repeat yourself, but don't abstract early.** Duplicate once, tolerate it twice, extract on the **third** occurrence (Rule of Three).
|
||||
4. **One source of truth.** All HTTP goes through one Axios client; all app-wide state lives in one Redux store; all shared UI lives in `components/shared`.
|
||||
5. **Documented.** Every function has a doc comment; important places have inline comments.
|
||||
6. **Formatted before it runs.** Prettier runs automatically before dev and build.
|
||||
|
||||
## 2. Ask before you start (mandatory)
|
||||
|
||||
At the **start of every task**, before writing any code, ask the user these questions and wait for the answers:
|
||||
|
||||
1. **Language:** "Should this be written in **TypeScript** or **JavaScript**?"
|
||||
2. **Ant Design:** "Do you need **Ant Design** for this, or should I use **only Tailwind CSS**?"
|
||||
|
||||
Rules:
|
||||
- Never assume the answers, even if the repo already contains `tsconfig.json` or Ant Design. Always ask.
|
||||
- Skip a question only if the user already answered it explicitly in the current task description.
|
||||
- If Ant Design is not needed, use **Tailwind only** and do not import anything from `antd`.
|
||||
- State the chosen answers in your final summary.
|
||||
|
||||
## 3. Stack
|
||||
|
||||
| Concern | Choice |
|
||||
|---|---|
|
||||
| Language | **Asked every task** (TypeScript or JavaScript). If TypeScript: `strict: true` |
|
||||
| UI | React 18 (function components + hooks only) |
|
||||
| Framework | Next.js App Router **or** React + Vite (detected, see section 0) |
|
||||
| Global state | Redux Toolkit |
|
||||
| HTTP | Axios (no RTK Query, no raw `fetch`) |
|
||||
| Forms | React Hook Form |
|
||||
| Styling | Tailwind CSS by default. Ant Design **only if the user confirms** |
|
||||
| Formatting | Prettier |
|
||||
| Testing | Vitest + React Testing Library (Cypress for E2E) |
|
||||
|
||||
Do not add a dependency without stating why and asking first.
|
||||
|
||||
## 4. Naming
|
||||
|
||||
### Files and folders
|
||||
- **Component files:** `PascalCase.tsx` / `.jsx` → `UserCard.tsx`
|
||||
- **Everything else:** `camelCase.ts` / `.js` → `useAuth.ts`, `userService.ts`, `authSlice.ts`, `formatDate.ts`
|
||||
- **Folders:** `camelCase` (`userManagement/`). A folder holding one component plus companion files is `PascalCase` (`UserCard/`).
|
||||
- **Tests:** next to the file → `UserCard.test.tsx`
|
||||
- **Type files (TS):** `user.types.ts`
|
||||
- **Exception:** Next.js reserved names (`page.tsx`, `layout.tsx`, `loading.tsx`, `route.ts`) and route folders follow Next.js rules.
|
||||
|
||||
### Code identifiers
|
||||
| Kind | Style | Example |
|
||||
|---|---|---|
|
||||
| Variables, functions | camelCase | `totalPrice`, `calculateTotal()` |
|
||||
| Components | PascalCase | `UserCard` |
|
||||
| Hooks | `use` + PascalCase | `useDebounce` |
|
||||
| Types, interfaces | PascalCase, **no `I` prefix** | `User`, `UserCardProps` |
|
||||
| Props type | `<Component>Props` | `UserCardProps` |
|
||||
| Constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT` |
|
||||
| Booleans | `is` / `has` / `can` / `should` | `isLoading`, `hasError` |
|
||||
| Internal handlers | `handle` + Event | `handleSubmit` |
|
||||
| Callback props | `on` + Event | `onSubmit` |
|
||||
| Slice | `<feature>Slice` | `authSlice` |
|
||||
| Thunks | verb + noun | `fetchUsers` |
|
||||
| Selectors | `select` + Name | `selectCurrentUser` |
|
||||
| Service functions | verb + noun | `getUsers`, `createOrder` |
|
||||
|
||||
- Prefer union types over `enum`: `type Status = 'idle' | 'loading' | 'failed'`.
|
||||
- Names are descriptive. Abbreviate only `id`, `url`, `api`, `props`.
|
||||
- Identifiers and comments are in **English**.
|
||||
|
||||
## 5. Project structure (layer-based)
|
||||
|
||||
The shared layers are **identical** in both project types and live in `src/`:
|
||||
|
||||
```
|
||||
src/
|
||||
components/
|
||||
shared/ # reusable components used by 2+ pages/features
|
||||
layout/ # Header, Sidebar, PageLayout
|
||||
hooks/ # shared custom hooks
|
||||
services/ # Axios client + API functions (the ONLY place for HTTP)
|
||||
apiClient.ts
|
||||
userService.ts
|
||||
store/
|
||||
index.ts # configureStore + RootState/AppDispatch
|
||||
hooks.ts # useAppDispatch, useAppSelector
|
||||
slices/
|
||||
types/
|
||||
utils/ # pure helpers
|
||||
constants/
|
||||
styles/
|
||||
tests/
|
||||
```
|
||||
|
||||
Only the routing layer differs (see section 14):
|
||||
- **Next.js:** `src/app/` (routes only).
|
||||
- **React (Vite):** `src/pages/`, `src/routes/`, `src/main.tsx`, `src/assets/`.
|
||||
|
||||
### Layer rules
|
||||
- Route files (`app/` or `pages/`) compose components and wire data; no business logic beyond that.
|
||||
- A component used by **one** page stays in that page's folder. On its **third** use (or when a second page needs it) move it to `components/shared`.
|
||||
- `components/shared` must be **generic and props-driven**: no imports from pages/routes, `services`, or `store`.
|
||||
- Components never call Axios. They call hooks, dispatch thunks, or use data passed as props.
|
||||
- `utils` are pure functions (no React, no side effects).
|
||||
- No circular imports. Use the `@/` alias for `src/`; avoid `../` chains deeper than two levels.
|
||||
|
||||
## 6. Writing simple, readable code
|
||||
|
||||
- **Max nesting: 2 levels** (an `if` inside a loop is the limit). Flatten with early returns, guard clauses, or by extracting a named function.
|
||||
- **Max function length: ~30 lines.** If longer, split into well-named helpers.
|
||||
- **One function, one job.** If you need "and" to describe it, split it.
|
||||
- **Max 3 parameters.** Beyond that, pass a single object.
|
||||
- **Rule of Three:** repeated logic/markup is extracted on the third occurrence into a util, hook, or `shared` component. Do not pre-abstract.
|
||||
- No nested ternaries. Use `if`/early return or a lookup object.
|
||||
- Name intermediate values instead of writing long chained expressions.
|
||||
- No magic numbers or strings: move them to `constants/`.
|
||||
- Prefer plain `map`/`filter`/`find` over clever `reduce` chains.
|
||||
- Avoid premature optimization: add `useMemo`/`useCallback` only for a measured problem or a memoized child.
|
||||
|
||||
```ts
|
||||
// Bad: nested and hard to scan
|
||||
function getLabel(user) {
|
||||
if (user) {
|
||||
if (user.isActive) {
|
||||
return user.name;
|
||||
}
|
||||
}
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
// Good: guard clause, flat
|
||||
function getLabel(user: User | null): string {
|
||||
if (!user?.isActive) return 'Unknown';
|
||||
return user.name;
|
||||
}
|
||||
```
|
||||
|
||||
## 7. Documentation and comments
|
||||
|
||||
### Every function gets a doc comment
|
||||
Every function must have a short JSDoc block directly above it describing **what it does**: components, hooks, helpers, services, thunks, selectors, and handlers.
|
||||
|
||||
- First line: one clear sentence about the purpose.
|
||||
- Add `@param` and `@returns` when the function has parameters or a return value.
|
||||
- One line is enough for trivial functions.
|
||||
- Not required for tiny inline callbacks (e.g. inside `.map()`).
|
||||
|
||||
```ts
|
||||
/**
|
||||
* Returns the display name of a user, or "Unknown" when the user is inactive.
|
||||
* @param user - The user to read the name from.
|
||||
* @returns The name to show in the UI.
|
||||
*/
|
||||
function getLabel(user: User | null): string {
|
||||
if (!user?.isActive) return 'Unknown';
|
||||
return user.name;
|
||||
}
|
||||
```
|
||||
|
||||
### Inline comments for important places
|
||||
Always comment:
|
||||
- **Why** something is done in a non-obvious way (workarounds, browser/device quirks, offline handling).
|
||||
- Business rules, calculations, and necessary magic values.
|
||||
- Complex conditions, regex, and data transformations.
|
||||
- Side effects and cleanup in `useEffect`, and why the dependencies are what they are.
|
||||
- Anything that looks like a bug but is intentional.
|
||||
- Open work as `// TODO: <what and why>`.
|
||||
|
||||
Do **not** write comments that only repeat the code (`// increment i`). No commented-out code. No leftover `console.log`.
|
||||
|
||||
## 8. Components
|
||||
|
||||
- Function declarations for components; arrow functions for inline callbacks.
|
||||
- One component per file, under ~150 lines. Split when larger.
|
||||
- Define a props type right above the component (TS) or a `@typedef` (JS). Do not use `React.FC`.
|
||||
- **Named exports only.** Default export only where a framework requires it (Next.js special files, `React.lazy` pages).
|
||||
- Order: hooks → derived values → handlers → effects → early returns → JSX.
|
||||
- Always render **loading, error, and empty** states for async UI.
|
||||
- Extract repeated logic into custom hooks and repeated UI into `components/shared` (Rule of Three).
|
||||
- Semantic HTML and basic accessibility (labels, alt text, focus).
|
||||
|
||||
```tsx
|
||||
type UserCardProps = {
|
||||
user: User;
|
||||
onSelect: (id: string) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows a clickable card for one user and reports the selection.
|
||||
*/
|
||||
export function UserCard({ user, onSelect }: UserCardProps) {
|
||||
const handleClick = () => onSelect(user.id);
|
||||
|
||||
return (
|
||||
<button type="button" onClick={handleClick}>
|
||||
{user.name}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 9. TypeScript / JavaScript
|
||||
|
||||
Follow the answer to the question in section 2.
|
||||
|
||||
### If TypeScript
|
||||
- No `any`. Use `unknown` and narrow, or define a type.
|
||||
- Type all parameters and exported return values; let inference handle locals.
|
||||
- Request/response shapes live in `types/` and are shared by services and slices.
|
||||
- `type` for unions and props; `interface` only when extending.
|
||||
- No non-null assertion (`!`) without a comment explaining why it is safe.
|
||||
- Files use `.ts` / `.tsx`.
|
||||
|
||||
### If JavaScript
|
||||
- Files use `.js` / `.jsx`. Same naming and structure rules apply.
|
||||
- Describe data shapes with JSDoc (`@typedef`, `@param {Type}`, `@returns {Type}`), kept in `types/`.
|
||||
- Document props with a `@typedef` above the component.
|
||||
- `const`/`let` only (never `var`), strict equality (`===`), optional chaining where needed.
|
||||
|
||||
## 10. API layer (all requests via Axios)
|
||||
|
||||
- **Every HTTP request goes through Axios.** Do not use `fetch`, `XMLHttpRequest`, or other HTTP clients. (WebSocket is not HTTP; see 14.2.)
|
||||
- There is **one Axios instance**: `services/apiClient.ts`. It configures `baseURL`, timeout, auth header, and interceptors for token refresh and error normalization.
|
||||
- Requests are made **only inside `services/`**, one file per domain (`userService.ts`). Services are plain async functions that return data and know nothing about Redux or React.
|
||||
```ts
|
||||
/**
|
||||
* Fetches the list of all users from the API.
|
||||
* @returns The users array.
|
||||
*/
|
||||
export async function getUsers(): Promise<User[]> {
|
||||
const { data } = await apiClient.get<User[]>('/users');
|
||||
return data;
|
||||
}
|
||||
```
|
||||
- Endpoint paths are constants (in the service file or `constants/endpoints.ts`), not scattered strings.
|
||||
- Errors are normalized **once** in the interceptor into a single shape (`{ message, status }`). Callers show `error.message`; they do not parse Axios errors themselves.
|
||||
- Cancel or ignore stale requests with `AbortController` when a component can unmount mid-request.
|
||||
|
||||
## 11. Global state (Redux Toolkit)
|
||||
|
||||
The store holds **only** these categories. Anything else stays local (`useState`, or a small hook calling a service).
|
||||
|
||||
| Category | Slice | Holds |
|
||||
|---|---|---|
|
||||
| Auth & current user | `authSlice` | token, profile, permissions |
|
||||
| General UI settings | `uiSlice` | theme, language, sidebar state |
|
||||
| Device connection | `deviceSlice` | online/offline status, connected device info (only if the project talks to devices) |
|
||||
| Shared cross-page data | one slice per domain (e.g. `lookupsSlice`) | data needed by 2+ pages |
|
||||
| Shopping cart | `cartSlice` | cart items and quantities (header badge, cart page, product pages) |
|
||||
|
||||
Rules:
|
||||
- One slice per feature in `store/slices/`. Slice shape for fetched data:
|
||||
`{ items, status: 'idle' | 'loading' | 'succeeded' | 'failed', error: string | null }`.
|
||||
- Async work uses `createAsyncThunk` that calls a **service**; handle `pending/fulfilled/rejected` in `extraReducers`.
|
||||
- Use typed `useAppDispatch` / `useAppSelector` only (plain equivalents in JS).
|
||||
- Write selectors beside the slice; components do not read deep state shapes directly.
|
||||
- Page-local data (a table on one page, form state, open/closed flags) does **not** go in Redux.
|
||||
- Keep the state shape documented: when adding a slice, add one line to the table above.
|
||||
|
||||
## 12. Formatting (Prettier is mandatory)
|
||||
|
||||
Code **must** be formatted with Prettier before the project runs in dev or build.
|
||||
|
||||
`package.json` scripts:
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,css,json,md}\"",
|
||||
"format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,css,json,md}\"",
|
||||
"predev": "npm run format",
|
||||
"prebuild": "npm run format",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint . --max-warnings 0"
|
||||
}
|
||||
}
|
||||
```
|
||||
(`predev` and `prebuild` run automatically before `npm run dev` / `npm run build`. `typecheck` is TypeScript only.)
|
||||
|
||||
`.prettierrc`:
|
||||
```json
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 100
|
||||
}
|
||||
```
|
||||
|
||||
- Run `npm run format` after editing files and before reporting a task as done.
|
||||
- Never hand-format against Prettier; never use `// prettier-ignore` without a reason in a comment.
|
||||
- ESLint passes with zero warnings (`react-hooks`, `@typescript-eslint` if TS, import order).
|
||||
- Import order: React → third-party → `@/` aliases → relative → styles. Remove unused imports.
|
||||
|
||||
## 13. Styling, testing, and git
|
||||
|
||||
### Styling
|
||||
- **Default is Tailwind CSS only.** Use Ant Design **only** if the user said yes in section 2.
|
||||
- Tailwind utilities for layout/spacing; `clsx` for conditional classes; extract a component when the same class list repeats 3+ times.
|
||||
- When Ant Design is approved: customize via theme tokens, not CSS overrides, and do not mix it with custom components that do the same job.
|
||||
- No inline `style` except for truly dynamic values. No hard-coded colors or sizes: use tokens.
|
||||
- Mobile-first and responsive; for Persian UI use RTL-safe logical utilities (`ms-*`, `me-*`, `ps-*`, `pe-*`).
|
||||
|
||||
### Testing
|
||||
- Test behavior via React Testing Library; query by role/label/text.
|
||||
- Mock the network at the **service** boundary; never call real APIs.
|
||||
- New utils, slices, and non-trivial components ship with tests; bug fixes ship with a regression test.
|
||||
|
||||
### Git
|
||||
- Branches: `feature/<name>`, `fix/<name>`. Commits: Conventional Commits (`feat:`, `fix:`, `refactor:`, `test:`, `docs:`, `chore:`).
|
||||
- Keep changes small and focused; do not mix refactors with features.
|
||||
|
||||
## 14. Framework-specific rules
|
||||
|
||||
### 14.1 Next.js (App Router)
|
||||
|
||||
```
|
||||
src/
|
||||
app/ # routes only: page.tsx, layout.tsx, loading.tsx, route.ts
|
||||
components/ hooks/ services/ store/ types/ utils/ constants/ styles/
|
||||
```
|
||||
|
||||
- Components are **Server Components by default**. Add `'use client'` to the smallest leaf that needs state, effects, or browser APIs.
|
||||
- Public/SEO data is fetched on the server (the same Axios client may be used server-side), not through Redux.
|
||||
- Redux and browser-only hooks live in client components, provided via a client `Providers` component. Create the store only if client state is actually needed.
|
||||
- Every page defines metadata (`generateMetadata`) with title and description.
|
||||
- Use `next/image` and `next/link`. Use route handlers (`route.ts`) only when an API layer is truly needed.
|
||||
- Never touch `window`/`localStorage` during server rendering.
|
||||
- Default exports are allowed for `page`, `layout`, `loading`, `error`, `not-found`.
|
||||
|
||||
### 14.2 React (Vite SPA / PWA / embedded UI)
|
||||
|
||||
```
|
||||
src/
|
||||
assets/ # images, fonts, icons
|
||||
pages/ # one folder per route; page-specific components live here
|
||||
routes/ # router config, route guards
|
||||
main.tsx
|
||||
components/ hooks/ services/ store/ types/ utils/ constants/ styles/ tests/
|
||||
```
|
||||
|
||||
- Lazy-load pages with `React.lazy` + `Suspense`.
|
||||
- Assume **unreliable or no network** if the app targets devices (vending, PWA). Handle offline state explicitly; never block the UI on a failed request.
|
||||
- Device communication over HTTP goes through Axios in `services/`. WebSocket/serial live in a dedicated `services/` module with typed messages and reconnect logic, updating `deviceSlice`.
|
||||
- For ESP32 / Orange Pi targets keep bundles small: avoid heavy dependencies and unused polyfills.
|
||||
- Document PWA caching/queue strategy in the PR description.
|
||||
|
||||
## 15. Refactoring an existing project
|
||||
|
||||
When asked to refactor a project to match this file:
|
||||
|
||||
1. **Detect the project type** (section 0) and ask the questions in section 2 before starting. Do not migrate between TypeScript and JavaScript unless explicitly asked.
|
||||
2. **Audit first.** Scan the project and report what violates these rules (structure, naming, HTTP calls outside `services/`, missing doc comments, deep nesting, duplicated code, unformatted files). Do not change code yet; present a short plan and wait for approval.
|
||||
3. **Preserve behavior.** A refactor must not change what the app does. No new features, no UI changes, no dependency upgrades.
|
||||
4. **Work in small steps,** one concern at a time, in this order:
|
||||
1. Add Prettier config and scripts, then format the project (formatting-only commit).
|
||||
2. Centralize HTTP: create `apiClient` and move every request into `services/`.
|
||||
3. Restructure folders and rename files per sections 4 and 5; update imports.
|
||||
4. Organize global state per section 11; move page-local state out of Redux.
|
||||
5. Extract duplicated code (Rule of Three) and move reusable components to `components/shared`.
|
||||
6. Flatten nesting and split long functions (section 6).
|
||||
7. Add doc comments and important inline comments (section 7).
|
||||
5. **Verify after every step:** run `format`, `typecheck` (TS only), `lint`, and `test`, and make sure the project still builds. Fix breakage before moving on.
|
||||
6. **One commit per step,** using Conventional Commits (`refactor: ...`).
|
||||
7. **Do not touch** anything you are unsure about; list it in the final report instead.
|
||||
|
||||
## 16. Agent checklist (before finishing any task)
|
||||
|
||||
1. Did I detect the project type and ask the TypeScript/JavaScript and Ant Design questions (section 2)?
|
||||
2. Did I read the surrounding code and match its patterns?
|
||||
3. Is new code in the correct layer (section 5)? Any new HTTP call goes in `services/` via `apiClient`.
|
||||
4. Nesting ≤ 2, functions ≤ ~30 lines, no third copy of repeated code?
|
||||
5. Does every function have a JSDoc description, and do important places have inline comments?
|
||||
6. Did I run `npm run format`, `typecheck` (TS only), `lint`, and `test`?
|
||||
7. Did I summarize what changed, the chosen language/UI library, assumptions, and follow-ups?
|
||||
8. Did I avoid touching unrelated files, configs, and dependencies?
|
||||
+4100
-1
File diff suppressed because one or more lines are too long
+180561
-1
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user