12 KiB
AGENTS.md — Coffee Project Frontend
Primary Codex reference: ../.codex/services/frontend.md.
If this file conflicts with the .codex guide, prefer the .codex guide. CLAUDE.md remains for Claude-specific tooling.
Project Overview
Next.js 16 application using Feature-Sliced Design (FSD) architecture, powered by Bun runtime and package manager.
Tech Stack
| Category | Technology |
|---|---|
| Runtime | Bun 1.3.5 |
| Framework | Next.js 16.1.1 (App Router) |
| Language | TypeScript 5.9 |
| UI Library | React 19 |
| Styling | SCSS Modules, normalize.css |
| State/Fetch | TanStack React Query 5, Axios, Xior |
| Animation | Framer Motion |
| Utilities | Lodash, date-fns, classnames, usehooks-ts |
| Icons | Lucide React, SVGR (custom icons) |
| Notifications | React Toastify |
| File Upload | React Dropzone |
| Linting | ESLint 9, Prettier, Stylelint |
| Testing | Jest, Testing Library |
Commands
bun dev # Start dev server
bun run build # Production build
bun run start # Start production server
bun run lint # Run ESLint + Prettier
bun run gc <layer> <ComponentName> # Generate FSD component
bun run gicons # Convert SVGs to React components
Project Structure (FSD)
app/ # Next.js App Router entry
pages/ # Keep empty (Next.js requires it)
public/ # Static assets
src/
├── app/ # App layer: global styles, providers
├── pages/ # Page compositions
├── widgets/ # Large UI blocks (header, sidebar)
├── features/ # User interactions (auth, search)
├── entities/ # Business entities (user, product)
└── shared/ # Reusable: UI kit, utils, API, assets
├── ui/ # Button, Input, Icons...
├── api/ # API clients
├── lib/ # Utilities
└── assets/ # Images, raw-icons
Path Aliases
@app/* → ./src/app/*
@pages/* → ./src/pages/*
@widgets/* → ./src/widgets/*
@features/* → ./src/features/*
@entities/* → ./src/entities/*
@shared/* → ./src/shared/*
@/* → ./src/*
Component Structure
Each component follows flat FSD structure (simplified for MVP):
ComponentName/
├── index.ts # Public API (re-export)
├── ComponentName.tsx # Component + imports
├── ComponentName.module.scss # Styles
├── ComponentName.d.ts # Props interface
└── useComponentApi.ts # Optional: hooks/API if needed
Note: Old nested structure (
ui/,model/,api/folders) has been deprecated. The backup of the old generator is at.scripts/create-fsd-component.ts.bak
Component Template
import type { IComponentNameProps } from "./ComponentName.d"
import type { JSX } from "react"
import { FunctionComponent } from "react"
import styles from "./ComponentName.module.scss"
export const ComponentName: FunctionComponent<
IComponentNameProps
> = (): JSX.Element => {
return (
<div className={styles.root} data-testid="ComponentName">
ComponentName
</div>
)
}
Props Interface Template (ComponentName.d.ts)
export interface IComponentNameProps {
className?: string
}
Generate Component
Use one of these commands to generate new project-wide standardized component, don't create new component file by file by yourself
bun run gc <layer> <ComponentName>
# Examples:
bun run gc shared Button
bun run gc feature AuthForm
bun run gc entity UserCard
bun run gc page HomePage
bun run gc widget Sidebar
Best Practices
Code Style
- Small Functions — max 20-30 lines; extract helpers
- Single Responsibility — one function = one purpose
- Descriptive Names —
getUserByIdnotgetData - Early Returns — reduce nesting with guard clauses
- No Magic Values — use constants:
const MAX_ITEMS = 10
TypeScript
// ✅ Use interfaces for props
interface IButtonProps {
variant: "primary" | "secondary"
disabled?: boolean
onClick: () => void
}
// ✅ Prefer explicit types over `any`
// ✅ Use `type` for unions/intersections, `interface` for objects
React Patterns
// ✅ Functional components with explicit return type
export const Button: FC<IButtonProps> = ({ variant, onClick }): JSX.Element => { ... }
// ✅ Destructure props
// ✅ Use data-testid for testing
// ✅ Colocate styles with components (CSS Modules)
FSD Rules
- Import Direction — only downward:
features → entities → shared - Public API — export only through
index.ts - No Cross-Slice Imports — features cannot import from other features
- Shared is Agnostic — no business logic in shared layer
- Features are module-aware — group features by domain inside module folders (see below)
When to Split Files
Split into separate files only when:
- Hook/API is reused by multiple components
- File exceeds ~200 lines
- Props interface is shared across 3+ components
File Naming
| Type | Convention | Example |
|---|---|---|
| Component | PascalCase | UserCard.tsx |
| Module | PascalCase.module | UserCard.module.scss |
| Types | PascalCase.d | UserCard.d.ts |
| Hook | camelCase (use-) | useAuth.ts |
| Utility | camelCase | formatDate.ts |
| Constant | UPPER_SNAKE_CASE | API_ENDPOINTS.ts |
Performance
- Use
React.memofor expensive renders - Use
useMemo/useCallbackfor derived data and callbacks - Lazy load pages/heavy components with
next/dynamic - Prefer server components where possible (Next.js App Router)
Testing
- Place tests next to components:
ComponentName.test.tsx - Use
data-testidattributes for queries - Test behavior, not implementation
Features Layer — Module-Aware Structure
Features must be grouped by domain module. Never place feature folders flat at the top of src/features/.
src/features/
├── profile/ # Profile domain module
│ ├── index.ts # Barrel export for all features in this module
│ ├── AvatarUpload/
│ ├── EditProfileForm/
│ └── LogoutButton/
└── project/ # Project domain module
├── index.ts
├── CreateProjectModal/
└── ...
Rules:
- Each module folder has an
index.tsbarrel that re-exports all its features - Import via the module barrel:
import { AvatarUpload } from "@features/profile" - When creating a new feature, place it inside the relevant domain folder
- After running
bun run gc feature <Name>, move the generated folder into the correct module - Create a new module folder + barrel if the domain doesn't exist yet
Shared Utilities
Reusable operations should live in src/shared/ — do not inline shared logic inside feature components.
File Upload
Use uploadFile() from @shared/api/uploadFile for any file upload:
import { uploadFile } from "@shared/api/uploadFile"
const result = await uploadFile(file, "avatars")
// result.file_url — URL of the uploaded file
// result.file_path — storage path
This handles FormData construction, Content-Type header override, and JWT auth automatically.
Date Formatting
Use date-fns with Russian locale via shared utilities in src/shared/lib/dates.ts. Never use moment.js or inline Date formatting in components.
import { formatDate, formatRelativeTime } from "@shared/lib/dates"
formatDate(user.date_joined) // "21.02.2026" (default: "dd.MM.yyyy")
formatDate(date, "dd MMM yyyy") // "21 февр. 2026"
formatRelativeTime(project.updated_at) // "2 дня назад"
Add new date helpers to src/shared/lib/dates.ts, not to individual components.
API Client
- In React components: use
api.useQuery()/api.useMutation()from@shared/api - Outside React (utilities, event handlers): use
fetchClientfrom@shared/api - File uploads: use
uploadFile()from@shared/api/uploadFile
Icons Workflow
- Place raw SVG in
src/shared/assets/raw-icons/ - Run
bun run gicons - Import from
@shared/ui/Icons/IconName
Notes
- Keep
pages/folder in root — removing causes Next.js build errors - Bun only — use
buncommands, not npm/yarn - SCSS Modules — all styles are scoped via
.module.scss - Strict TypeScript —
strict: truein tsconfig
Quick Reference
| Task | Command / Location |
|---|---|
| Add dependency | bun add <package> |
| Add dev dependency | bun add -d <package> |
| Create component | bun run gc <layer> <Name> |
| Global styles | src/app/styles/global.scss |
| API client | Use Axios/Xior with React Query |
| State management | TanStack Query (server state) |
Localization
All user-facing UI text must be in Russian. This includes: labels, headings, buttons, placeholders, tooltips, aria-labels, error messages, breadcrumbs, and any other text visible to the user. The only exception is the brand name "Coffee Project" / "Cofee Project" — it stays in English.
Implementation sentiments
Write less complicated code, simple but readable code
Less overhead - better
Write all components with html semantics in mind
To import classNames lib use
import cs from 'classnames'
Always install packages using
bun install <package>
To test is project have no errors use
bunx tsc --noEmit
Common Mistakes to Avoid
- Flat features folder — never place feature component folders directly in
src/features/. Always group them inside a domain module folder (profile/,project/, etc.). - Inlining reusable logic — if an operation (file upload, date formatting, etc.) could be used by multiple features, extract it to
src/shared/. Features should be thin wrappers around shared utilities. - Wrong StaticLoader import — it lives at
@shared/ui/Loader, not@shared/ui/Loader/StaticLoader. There is no subdirectory. - multipart/form-data with fetchClient — the default
fetchClientsetsContent-Type: application/json. For file uploads you must override headers and body serializer. Use the shareduploadFile()utility instead. - Broken lint scripts —
bun run lintcallslint:esandlint:prettierwhich are not defined inpackage.json. Usebunx tsc --noEmitfor type checking until lint is fixed. - Generator output needs moving —
bun run gc feature <Name>creates the folder flat insrc/features/. You must manually move it into the correct domain module folder afterward. - Raw
fetch/useEffectfor API calls — never use plainfetchoruseEffect-based polling for API requests. Always useapi.useQuery()/api.useMutation()from@shared/apiwhich wraps TanStack Query + openapi-fetch. For polling, userefetchInterval. Rawfetchbypasses typed routes, auth middleware, and query caching.