Files
vending-machine-game/AGENTS.md
T
Hamed df930694f7
CI/CD / test (push) Successful in 1m20s
CI/CD / publish (push) Successful in 56s
CI/CD / deploy (push) Successful in 5s
update
2026-10-28 13:26:44 +03:30

19 KiB
Raw Blame History

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.
// 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()).
/**
 * 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).
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.
    /**
     * 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:

{
  "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:

{
  "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?