7.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
See also the monorepo-level ../CLAUDE.md for full architecture overview and backend docs.
Commands
bun dev # Dev server (localhost:3000)
bun run build # Production build
bun run lint # ESLint + Prettier (concurrent)
bunx tsc --noEmit # Type-check without emitting
bun run gc <layer> <Name> # Generate FSD component (e.g. bun run gc shared Button)
bun run gicons # Convert raw SVGs → React icon components
bun run gen:api-types # Regenerate API types from OpenAPI schema (backend must be running)
Architecture
Next.js 16 App Router with Feature-Sliced Design. Strict unidirectional imports: pages → widgets → features → entities → shared.
- App directory is at
app/(project root), notsrc/app/. Thesrc/app/layer holds global styles and providers. app/template.tsxwraps all routes with Header (conditionally hidden on auth routes).- All components are
"use client"unless explicitly marked otherwise.
API & Data Layer
fetchClient(openapi-fetch) — typed HTTP client with JWT middleware that readsaccess_tokenfrom cookies. Defined insrc/shared/api/index.ts.api(openapi-react-query) — wrapsfetchClientfor use with TanStack Query hooks in components. Import asimport api from "@shared/api".- Generated types live in
src/shared/api/__generated__/openapi.types.ts— never edit manually. - Server actions in
src/shared/api/server.ts— used for server-side API calls (ping, token verification).
Styling
- SCSS Modules (
.module.scss) for all component styles. - SCSS partials auto-injected via
next.config.mjsusing@use:_variables.scss,_breakpoints.scss,_typography.scss,_mixins.scss. No need to import them manually in.module.scssfiles. - Radix UI Themes wraps the app (
accentColor="iris",grayColor="slate"). Some components use Radix primitives directly (e.g., Dropdown uses@radix-ui/react-dropdown-menu, not Radix Themes). - Class composition:
import cs from "classnames". - Design tokens defined as CSS custom properties in
src/shared/styles/global.scss, mirrored as SCSS vars in_variables.scss.
State Management
- Server state: TanStack React Query (primary for all API data).
- Client state: Redux Toolkit with two slices:
appStateanduser(insrc/shared/store/). - Provider hierarchy (in
src/shared/context/AppProviders.tsx): Redux → QueryClient → UserSync → Radix Theme.
Component Convention
Generate new components with bun run gc <layer> <Name> — never create component files manually. Each component folder contains:
index.ts— public re-export onlyComponentName.tsx— implementationComponentName.module.scss— scoped stylesComponentName.d.ts— props interface (IComponentNameProps)
Code Style
- Prettier: tabs (width 2), no semicolons, double quotes, sorted imports.
- Imports: use path aliases (
@shared/*,@entities/*, etc.), never relative paths across layers. - Forms:
react-hook-formfor form state management. - Icons: Lucide React for standard icons. Custom icons: place SVG in
src/shared/assets/raw-icons/, runbun run gicons, import from@shared/ui/Icons/IconName.
Features Layer — Module-Aware Structure
Features are grouped by domain module, not placed flat at the top level. Each module folder has a barrel index.ts:
src/features/
├── profile/ # Profile domain
│ ├── index.ts # Barrel: re-exports all features in module
│ ├── AvatarUpload/
│ ├── EditProfileForm/
│ └── LogoutButton/
└── project/ # Project domain
├── index.ts
├── CreateProjectModal/
├── DeleteProjectModal/
├── EditProjectModal/
└── RenameProjectModal/
Import via module barrel: import { AvatarUpload, EditProfileForm } from "@features/profile".
When adding a new feature, place it inside the relevant domain module folder (create one if needed).
File Uploads
Use the shared uploadFile utility for any file upload — do not inline FormData logic in components:
import { uploadFile } from "@shared/api/uploadFile"
const result = await uploadFile(file, "avatars")
// result.file_url, result.file_path
The utility handles FormData construction, Content-Type override, and auth middleware automatically.
Date Formatting
Use date-fns with Russian locale for all date formatting — never use moment.js or inline Date logic:
import { formatDate, formatRelativeTime } from "@shared/lib/dates"
formatDate(user.date_joined) // "21.02.2026"
formatDate(date, "dd MMM yyyy") // "21 февр. 2026"
formatRelativeTime(project.updated_at) // "2 дня назад"
Utilities live in src/shared/lib/dates.ts. Add new date helpers there, not in components.
Localization
All user-facing UI text must be in Russian — labels, headings, buttons, placeholders, tooltips, aria-labels, error messages, breadcrumbs. The brand name "Coffee Project" / "Cofee Project" stays in English.
Gotchas
- The
pages/directory in the project root must exist (even if empty) — removing it causes Next.js build errors. - Dropdown component's
asChildtrigger applies both the Dropdown's trigger class AND the child's class. Avoidall: unseton Dropdown triggers — it strips child flex/display styles. - SCSS auto-import uses
@use(not@import), so variables/mixins are namespaced (e.g.,variables.$color). The files are injected asadditionalDatainnext.config.mjs. - openapi-fetch + multipart:
fetchClientdefaults toContent-Type: application/json. For file uploads, you must override the header and body serializer. Use the shareduploadFile()utility instead of doing this manually. - StaticLoader is exported from
@shared/ui/Loader(file isLoader.tsx), not from@shared/ui/Loader/StaticLoader— there is no subdirectory. lint:es/lint:prettierscripts are referenced bybun run lintbut not defined inpackage.json. Linting is currently broken — usebunx tsc --noEmitfor type checking.next/imageremote hosts: External image hostnames must be listed innext.config.mjsimages.remotePatterns. MinIO (localhost:9000) is already configured. If you add another storage backend, add its hostname there too.- Stale OpenAPI types: Always run
bun run gen:api-typesbefore implementing against the API if the backend has changed. Stale types cause silent 404s at runtime. - Never use raw
fetch/useEffectfor API calls — always useapi.useQuery()/api.useMutation()from@shared/api(TanStack Query + openapi-fetch wrapper). For polling, use therefetchIntervaloption. Rawfetchbypasses typed routes, auth middleware, and query caching.
Always use Context7 MCP when I need library/API documentation, code generation, setup or configuration steps without me having to explicitly ask.
Testing Standards
- All E2E tests use Playwright with TypeScript
- Test files live in tests/e2e/
- Use
getByRoleas primary locator strategy - Every PR must include error-state tests, not just happy paths