update agents.md

This commit is contained in:
simon.franken
2026-02-23 10:59:17 +01:00
parent d56eed8dde
commit 59eda58ee6

250
AGENTS.md
View File

@@ -2,203 +2,125 @@
This document describes the structure, conventions, and commands for the `vibe_coding_timetracker` monorepo. Read it in full before making changes. This document describes the structure, conventions, and commands for the `vibe_coding_timetracker` monorepo. Read it in full before making changes.
---
## Repository Structure ## Repository Structure
This is a monorepo with three sub-projects: ```text
```
/ /
├── frontend/ # React SPA (Vite + TypeScript + Tailwind) ├── frontend/ # React SPA (Vite + TypeScript + Tailwind)
│ └── src/
│ ├── api/ # Axios API client modules
│ ├── components/# Shared UI components (PascalCase .tsx)
│ ├── contexts/ # React Context providers
│ ├── hooks/ # TanStack React Query hooks (useXxx.ts)
│ ├── pages/ # Route-level page components
│ ├── types/ # TypeScript interfaces (index.ts)
│ └── utils/ # Pure utility functions
├── backend/ # Express REST API (TypeScript + Prisma + PostgreSQL) ├── backend/ # Express REST API (TypeScript + Prisma + PostgreSQL)
│ └── src/
│ ├── auth/ # OIDC + JWT logic
│ ├── errors/ # AppError subclasses
│ ├── middleware/# Express middlewares
│ ├── prisma/ # Prisma client singleton
│ ├── routes/ # Express routers (xxx.routes.ts)
│ ├── schemas/ # Zod validation schemas
│ └── services/ # Business logic classes (xxx.service.ts)
├── ios/ # Native iOS app (Swift/Xcode) ├── ios/ # Native iOS app (Swift/Xcode)
├── timetracker-chart/ # Helm chart for Kubernetes deployment ├── timetracker-chart/ # Helm chart for Kubernetes deployment
── docker-compose.yml ── docker-compose.yml
└── project.md # Product requirements document
``` ```
### Frontend layout (`frontend/src/`)
```
api/ # Axios API client modules (one file per resource)
components/ # Shared UI components (PascalCase .tsx)
contexts/ # React Context providers: AuthContext, TimerContext
hooks/ # TanStack React Query custom hooks (useXxx.ts)
pages/ # Route-level page components (XxxPage.tsx)
types/ # All TypeScript interfaces (index.ts)
utils/ # Pure utility functions (dateUtils.ts)
```
### Backend layout (`backend/src/`)
```
auth/ # OIDC + JWT authentication logic
config/ # Environment variable configuration
errors/ # Custom AppError subclasses
middleware/ # auth, errorHandler, validation middleware
prisma/ # Prisma client singleton
routes/ # Express routers (xxx.routes.ts)
schemas/ # Zod validation schemas (index.ts)
services/ # Business logic classes (xxx.service.ts)
types/ # TypeScript interfaces + Express augmentation
utils/ # timeUtils.ts
```
---
## Build, Lint, and Dev Commands ## Build, Lint, and Dev Commands
### Frontend (`frontend/`) ### Frontend (`frontend/`)
```bash - **Dev Server:** `npm run dev` (port 5173)
npm run dev # Start Vite dev server (port 5173) - **Build:** `npm run build` (tsc & vite build)
npm run build # Type-check (tsc) then bundle (vite build) - **Lint:** `npm run lint` (ESLint, zero warnings allowed)
npm run preview # Preview production build locally - **Preview:** `npm run preview`
npm run lint # ESLint over .ts/.tsx, zero warnings allowed
```
### Backend (`backend/`) ### Backend (`backend/`)
```bash - **Dev Server:** `npm run dev` (tsx watch)
npm run dev # Hot-reload dev server via tsx watch - **Build:** `npm run build` (tsc to dist/)
npm run build # Compile TypeScript to dist/ - **Start:** `npm run start` (node dist/index.js)
npm run start # Run compiled output (node dist/index.js) - **Database:**
npm run db:migrate # Run Prisma migrations - `npm run db:migrate` (Run migrations)
npm run db:generate # Regenerate Prisma client - `npm run db:generate` (Regenerate client)
npm run db:seed # Seed the database - `npm run db:seed` (Seed database)
```
### Full stack (repo root) ### Full Stack (Root)
```bash - **Run all:** `docker-compose up`
docker-compose up # Start all services (frontend, backend, postgres)
```
### Testing ### Testing
**There is no test framework configured.** No test runner (`jest`, `vitest`, etc.) is installed and no `.spec.ts` / `.test.ts` files exist. When adding tests, set up Vitest (already aligned with Vite) and add a `test` script to `package.json`. To run a single test file with Vitest once installed: **No test framework is currently configured.** No test runner (`jest`, `vitest`) is installed and no `.spec.ts` or `.test.ts` files exist.
```bash - When adding tests, set up **Vitest** (aligned with Vite).
npx vitest run src/path/to/file.test.ts - Add a `test` script to `package.json`.
``` - **To run a single test file with Vitest once installed:**
```bash
--- npx vitest run src/path/to/file.test.ts
```
## TypeScript Configuration
### Frontend (`frontend/tsconfig.json`)
- `strict: true`, `noUnusedLocals: true`, `noUnusedParameters: true`
- `noEmit: true` — Vite handles all output
- Path alias `@/*``src/*` (use `@/` for all internal imports)
- `target: ES2020`, `module: ESNext`, `moduleResolution: bundler`
- `isolatedModules: true`, `resolveJsonModule: true`
### Backend (`backend/tsconfig.json`)
- `strict: true`, `esModuleInterop: true`
- `target: ES2022`, `module: Node16`, `moduleResolution: Node16`
- `outDir: ./dist`, `rootDir: ./src`
- `declaration: true` (emits `.d.ts` files)
---
## Code Style Guidelines ## Code Style Guidelines
### Imports ### Imports & Exports
- Use the `@/` alias for all internal frontend imports: `import { useAuth } from "@/contexts/AuthContext"` - Use `@/` for all internal frontend imports: `import { useAuth } from "@/contexts/AuthContext"`
- Use `import type { ... }` for type-only imports: `import type { User } from "@/types"` - Use `import type { ... }` for type-only imports. Order external libraries first.
- Order: external libraries first, then internal `@/` imports - Named exports are standard. Avoid default exports (except in `App.tsx`).
- Named exports are the standard; avoid default exports (only `App.tsx` uses one)
### Formatting ### Formatting
- 2-space indentation throughout - 2-space indentation. No Prettier config exists; maintain consistency with surrounding code.
- No Prettier config exists — maintain consistency with surrounding code - Prefer double quotes. Trailing commas in multi-line objects/arrays.
- Trailing commas in multi-line objects and arrays
- Quote style is mixed across the codebase (no enforcer); prefer double quotes to match the majority of files
### Types and Interfaces ### Types & Naming Conventions
- Define all shared types as `interface` (not `type` aliases) in the relevant `types/index.ts` - Define shared types as `interface` in `types/index.ts`.
- Suffix input/mutation types: `CreateClientInput`, `UpdateProjectInput` - Suffix input types: `CreateClientInput`.
- Use `?` for optional fields, not `field: T | undefined` - Use `?` for optional fields, `string | null` for nullable fields (not `undefined`).
- Use `string | null` for nullable fields (not `undefined`) - **Components:** `PascalCase.tsx` (`DashboardPage.tsx`)
- Backend Zod schemas live in `backend/src/schemas/index.ts`, named `<Entity>Schema` (e.g., `CreateClientSchema`) - **Hooks/Utils/API:** `camelCase.ts` (`useTimeEntries.ts`, `dateUtils.ts`)
- Backend custom errors extend `AppError`: `NotFoundError`, `BadRequestError`, `ConflictError`, `UnauthorizedError` - **Backend Routes/Services:** `camelCase.routes.ts`, `camelCase.service.ts`
- **Backend Schemas:** Zod schemas in `backend/src/schemas/index.ts` (e.g., `CreateClientSchema`).
### Naming Conventions
| Category | Convention | Example |
|---|---|---|
| React components | `PascalCase.tsx` | `TimerWidget.tsx`, `Modal.tsx` |
| Page components | `PascalCasePage.tsx` | `DashboardPage.tsx`, `LoginPage.tsx` |
| Context files | `PascalCaseContext.tsx` | `AuthContext.tsx`, `TimerContext.tsx` |
| Custom hooks | `useXxx.ts` | `useTimeEntries.ts`, `useClients.ts` |
| API modules | `camelCase.ts` | `timeEntries.ts`, `clients.ts` |
| Utility files | `camelCaseUtils.ts` | `dateUtils.ts`, `timeUtils.ts` |
| Backend routes | `camelCase.routes.ts` | `timeEntry.routes.ts` |
| Backend services | `camelCase.service.ts` | `timeEntry.service.ts` |
| Types / schemas | `index.ts` (aggregated) | `src/types/index.ts` |
| Directories | `camelCase` | `api/`, `hooks/`, `routes/`, `services/` |
### React Components ### React Components
- Use named function declarations, not arrow functions assigned to `const`: - Use named function declarations: `export function DashboardPage() { ... }`
```ts - Context hooks throw an error if called outside their provider.
// correct
export function DashboardPage() { ... }
// avoid
export const DashboardPage = () => { ... }
```
- Context hooks (`useAuth`, `useTimer`) throw an error if called outside their provider — maintain this pattern for all new contexts
### State Management ### State Management
- **Server state**: TanStack React Query (all remote data). Never use `useState` for server data. - **Server state:** TanStack React Query. Never use `useState` for server data.
- Custom hooks encapsulate `useQuery` + `useMutation` + cache invalidation - Use `mutateAsync` so callers can await and handle errors.
- Query keys are arrays: `["timeEntries", filters]`, `["projects", clientId]` - Invalidate related queries after mutations: `queryClient.invalidateQueries`.
- Use `mutateAsync` (not `mutate`) so callers can `await` and handle errors - **Shared client state:** React Context.
- Invalidate related queries after mutations via `queryClient.invalidateQueries` - **Local UI state:** `useState`.
- **Shared client state**: React Context (`AuthContext`, `TimerContext`) - **NO Redux or Zustand.**
- **Local UI state**: `useState` per component (modals, form data, error messages)
- No Redux or Zustand — do not introduce them
### Error Handling ### Error Handling
- **Frontend:**
**Frontend:** ```typescript
```ts
try {
await someAsyncOperation()
} catch (err) {
setError(err instanceof Error ? err.message : "An error occurred")
}
```
- Store errors in local `useState<string | null>` and render inline as red text
- No global error boundary exists; handle errors close to where they occur
**Backend:**
```ts
router.get("/resource/:id", async (req, res, next) => {
try { try {
const result = await service.getById(req.params.id) await someAsyncOperation()
res.json(result) } catch (err) {
} catch (error) { setError(err instanceof Error ? err.message : "An error occurred")
next(error) // always forward to errorHandler middleware
} }
}) ```
``` Store errors in local state and render inline as red text. No global error boundary exists.
- Throw `AppError` subclasses from services; never send raw error responses from route handlers - **Backend:** Throw `AppError` subclasses from services.
- The global `errorHandler` middleware handles Prisma error codes (P2002, P2025, P2003) and `AppError` subclasses ```typescript
router.get("/:id", async (req, res, next) => {
try {
res.json(await service.getById(req.params.id))
} catch (error) {
next(error) // Always forward to errorHandler middleware
}
})
```
### Styling ### Styling
- **Tailwind CSS v3** for all styling — no CSS modules, no styled-components - **Tailwind CSS v3** only. No CSS modules or styled-components.
- Use `clsx` + `tailwind-merge` for conditional class merging when needed - Use `clsx` + `tailwind-merge` for class merging. Icons from `lucide-react` only.
- Icons from `lucide-react` only
### Backend Validation ### Backend Validation & Database
- All incoming request data validated with Zod schemas before reaching service layer - Validate all incoming request data with Zod schemas in middleware.
- Schemas defined in `backend/src/schemas/index.ts` - Prisma v6 with PostgreSQL. Use the Prisma client singleton from `backend/src/prisma/`.
- Validation middleware applied per-route; never trust `req.body` without parsing through a schema - DB columns are `snake_case`, mapped to `camelCase` TypeScript via `@map`.
### Database
- Prisma v6 with PostgreSQL
- Database column names are `snake_case`, mapped to `camelCase` TypeScript via `@map` in the Prisma schema
- Always use the Prisma client singleton from `backend/src/prisma/`
---
## Key Architectural Decisions ## Key Architectural Decisions
- The frontend communicates with the backend exclusively through the typed Axios modules in `frontend/src/api/` - Frontend communicates with Backend exclusively via typed Axios modules in `frontend/src/api/`.
- Authentication supports two flows: OIDC (web, via `express-session`) and JWT (iOS client, via `jsonwebtoken`) - iOS app shares no code with the web frontend.
- The iOS app lives in `ios/` and shares no code with the web frontend — do not couple them - Backend routes only handle HTTP concerns (parsing, validation, formatting); business logic belongs purely in services.
- All business logic belongs in service classes; routes only handle HTTP concerns (parsing, validation, response formatting)