# Coding Conventions & Style Guide — MeshiTrack

> Cross-cutting conventions that apply to all packages in the monorepo.

## General Rules

### No emojis

Never use emoji characters anywhere in this codebase: not in source code, UI text, console output, log messages, comments, or documentation. Use plain text instead.

### Language: TypeScript everywhere

- All packages use TypeScript with `strict: true`
- No `.js` files in source (except config files: `jest.config.js`, `next.config.js`)
- All files use `.ts` or `.tsx` extension

### Formatting: Prettier

```json
// .prettierrc
{
  "semi": true,
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 100,
  "tabWidth": 2,
  "arrowParens": "always",
  "endOfLine": "lf"
}
```

### Linting: ESLint

Use a flat config (`eslint.config.js`) with TypeScript support:

- `@typescript-eslint/recommended`
- `@typescript-eslint/no-explicit-any` → error
- `@typescript-eslint/no-unused-vars` → error (with `_` prefix exception)
- `import/order` → enforce consistent import ordering

### Import ordering

```typescript
// 1. Node built-ins
import { readFile } from 'fs/promises';

// 2. External packages
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';

// 3. Monorepo packages
import type { Product } from '@meshitrack/shared';
import { ProductCategory } from '@meshitrack/shared';

// 4. Internal (relative) imports
import { ProductsRepository } from './products.repository';
import type { ProductQueryDto } from './dto/product-query.dto';
```

Separate each group with a blank line.

## Naming Conventions

### Files

| Kind              | Pattern                                | Example                     |
| ----------------- | -------------------------------------- | --------------------------- |
| Module            | `kebab-case.module.ts`                 | `products.module.ts`        |
| Controller        | `kebab-case.controller.ts`             | `products.controller.ts`    |
| Service           | `kebab-case.service.ts`                | `products.service.ts`       |
| Repository        | `kebab-case.repository.ts`             | `products.repository.ts`    |
| Schema (Mongoose) | `kebab-case.schema.ts`                 | `product.schema.ts`         |
| DTO               | `kebab-case.dto.ts`                    | `create-product.dto.ts`     |
| Guard             | `kebab-case.guard.ts`                  | `keycloak-auth.guard.ts`    |
| Decorator         | `kebab-case.decorator.ts`              | `current-user.decorator.ts` |
| Interface         | `kebab-case.interface.ts`              | `llm-provider.interface.ts` |
| React component   | `PascalCase.tsx`                       | `ProductCard.tsx`           |
| React hook        | `use-kebab-case.ts`                    | `use-products.ts`           |
| Test file         | `*.spec.ts` (API) / `*.test.tsx` (Web) | `products.service.spec.ts`  |
| Zod schema        | `kebab-case.schemas.ts`                | `product.schemas.ts`        |
| Enum file         | `kebab-case.enums.ts`                  | `product.enums.ts`          |

### Code

| Kind                                  | Style                            | Example                                |
| ------------------------------------- | -------------------------------- | -------------------------------------- |
| Class                                 | PascalCase                       | `ProductsService`, `KeycloakAuthGuard` |
| Interface                             | PascalCase + I prefix (optional) | `ILlmProvider` or `LlmProvider`        |
| Type alias                            | PascalCase                       | `ProductQuery`, `CreateProductInput`   |
| Enum                                  | PascalCase                       | `ProductCategory`                      |
| Enum member                           | UPPER_SNAKE                      | `ProductCategory.DAIRY`                |
| Function                              | camelCase                        | `calculateNutrition()`                 |
| Variable                              | camelCase                        | `servingSize`, `totalCalories`         |
| Constant                              | UPPER_SNAKE                      | `LLM_PROVIDER`, `MAX_RETRY_COUNT`      |
| Private field (backing getter/setter) | `_camelCase`                     | `_accessToken`, `_householdId`         |
| React component                       | PascalCase                       | `ProductCard`, `NutritionBadge`        |
| React hook                            | camelCase                        | `useProducts`, `usePantryItems`        |
| CSS class (Tailwind)                  | kebab-case                       | via Tailwind utilities                 |

## Implementation Workflow (TDD-First)

Every feature or phase implementation must follow this exact test-first sequence, strictly practicing the Red-Green-Refactor cycle:

1.  **Shared Layer (`packages/shared`)**: Write validation schema and enum tests first in `tests/`. Implement schemas and types inside `src/`.
2.  **Database Layer (`packages/api`)**: Write schema and repository integration tests first in `tests/` (using `mongodb-memory-server`). Create Mongoose schema and Repository in `src/`. All reads must use `.lean().exec()`.
3.  **Service Layer (`packages/api`)**: Write service unit tests first in `tests/` using the `createMockRepository` helper to mock repository methods. Implement business logic Service class inside `src/` using Awilix constructor injection.
4.  **Route Layer (`packages/api`)**: Write Fastify route plugin tests first in `tests/` using `app.inject` and resolving mocks. Implement the Fastify route plugin inside `src/`.
5.  **Web API Client (`packages/web`)**: Write service unit tests first in `tests/` utilizing MSW to mock backend requests. Implement frontend service in `src/`.
6.  **Web UI (`packages/web`)**: Write component and page tests first in `tests/` using React Testing Library role queries. Create Next.js pages and components inside `src/` (Server Components by default).

**Mandatory Verification**: Every task must end with `npm run build`, `npm run test:cov`, and `npm run lint` all passing with the defined coverage thresholds.


## Code Organization Rules

### API (NestJS)

1. **One module per feature** — self-contained folder with controller, service, repository, schemas, DTOs, tests
2. **No cross-module imports of internal files** — only import from exported module API
3. **Services contain business logic** — controllers are thin, repositories handle data access
4. **Throw NestJS exceptions** — `NotFoundException`, `ConflictException`, etc.
5. **Use custom decorators for request context** — `@CurrentUser()`, `@CurrentHousehold()`

### Web (Next.js)

1. **App Router conventions** — `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`
2. **Server Components by default** — only add `'use client'` when interactive
3. **Feature components** in `components/features/` — organized by domain (products, pantry, etc.)
4. **UI components** in `components/ui/` — generic, reusable, no domain logic
5. **Hooks** in `hooks/` — custom hooks for data fetching, state management
6. **Services** in `services/` — API client wrappers

### Shared

1. **Only pure TypeScript** — no Node.js, no browser, no framework code
2. **Types, enums, Zod schemas, pure utils** — nothing else
3. **Barrel exports** — every directory has `index.ts`
4. **Use `type` modifier for type-only exports/imports**

## Getters and Setters

Use TypeScript `get`/`set` accessors when a property simply exposes or lightly wraps a private backing field. Use a plain method only when the operation is async, takes multiple parameters, or has meaningful side effects beyond assignment.

```typescript
// Good:Simple exposure of a private field — use accessor
class ApiClient {
  private _accessToken: string | null = null;

  set accessToken(token: string) {
    this._accessToken = token;
  }

  get accessToken(): string | null {
    return this._accessToken;
  }
}

// Good:Side effects / async / multiple params — use a method
class TokenManager {
  async setTokenFromCode(code: string, redirectUri: string) {
    const token = await exchangeCode(code, redirectUri);
    this._accessToken = token;
  }
}
```

Backing fields use the `_camelCase` prefix to avoid name collisions with the accessor.

## Error Messages

### API error response format

```json
{
  "statusCode": 404,
  "error": "Not Found",
  "message": "Product abc123 not found in household xyz",
  "timestamp": "2026-01-15T10:30:00.000Z",
  "path": "/api/v1/products/abc123"
}
```

### Rules

- Messages are **end-user readable** — no stack traces, no internal IDs in production
- Include **resource type and identifier** in not-found messages
- Log full error details server-side with a correlation ID
- Never leak database field names or query details

## Git Conventions

### Branch naming

```
feature/MESH-001-product-crud
fix/MESH-042-barcode-duplicate
chore/update-dependencies
docs/phase-2-recipe-notes
```

### Commit messages (Conventional Commits)

```
feat(products): add barcode lookup via Open Food Facts
fix(pantry): correct freshness calculation for opened items
docs: update phase-2 recipe instructions
chore(deps): bump @nestjs/core to 10.4.0
test(api): add integration tests for product search
refactor(api): extract repository pattern from service
```

### PR template

```markdown
## What

Brief description of the change.

## Why

Link to issue or explanation of the need.

## How

Key implementation decisions.

## Testing

- [ ] Unit tests added/updated
- [ ] Integration tests (if DB changes)
- [ ] Manual testing done

## Checklist

- [ ] Types updated in `packages/shared`
- [ ] Zod schemas match new fields
- [ ] API docs (Swagger decorators) updated
- [ ] No `any` types introduced
```

## Logging

### Use NestJS Logger

```typescript
import { Logger } from '@nestjs/common';

@Injectable()
export class ProductsService {
  private readonly logger = new Logger(ProductsService.name);

  async create(householdId: string, userId: string, dto: CreateProductDto) {
    this.logger.log(`Creating product "${dto.name}" for household ${householdId}`);
    // ...
    this.logger.debug(`Product created with ID ${product.id}`);
    return product;
  }
}
```

### Log levels

- `error` — unexpected failures, unhandled exceptions
- `warn` — recoverable issues, deprecated usage, slow queries
- `log` — significant operations (create, delete, status transitions)
- `debug` — detailed flow information (only shown in dev)
- `verbose` — very detailed (disabled by default)

### Never log:

- Passwords, tokens, API keys
- Full request bodies with sensitive user data
- PII (unless specifically needed and compliant with privacy policy)
