8.1 KiB
8.1 KiB
AGENTS.md — Codebase Guide for AI Coding Agents
This document describes the structure, conventions, and commands for the vibe_coding_timetracker monorepo. Read it in full before making changes.
Repository Structure
This is a monorepo with three sub-projects:
/
├── frontend/ # React SPA (Vite + TypeScript + Tailwind)
├── backend/ # Express REST API (TypeScript + Prisma + PostgreSQL)
├── ios/ # Native iOS app (Swift/Xcode)
├── timetracker-chart/ # Helm chart for Kubernetes deployment
├── 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
Frontend (frontend/)
npm run dev # Start Vite dev server (port 5173)
npm run build # Type-check (tsc) then bundle (vite build)
npm run preview # Preview production build locally
npm run lint # ESLint over .ts/.tsx, zero warnings allowed
Backend (backend/)
npm run dev # Hot-reload dev server via tsx watch
npm run build # Compile TypeScript to dist/
npm run start # Run compiled output (node dist/index.js)
npm run db:migrate # Run Prisma migrations
npm run db:generate # Regenerate Prisma client
npm run db:seed # Seed the database
Full stack (repo root)
docker-compose up # Start all services (frontend, backend, postgres)
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:
npx vitest run src/path/to/file.test.ts
TypeScript Configuration
Frontend (frontend/tsconfig.json)
strict: true,noUnusedLocals: true,noUnusedParameters: truenoEmit: true— Vite handles all output- Path alias
@/*→src/*(use@/for all internal imports) target: ES2020,module: ESNext,moduleResolution: bundlerisolatedModules: true,resolveJsonModule: true
Backend (backend/tsconfig.json)
strict: true,esModuleInterop: truetarget: ES2022,module: Node16,moduleResolution: Node16outDir: ./dist,rootDir: ./srcdeclaration: true(emits.d.tsfiles)
Code Style Guidelines
Imports
- Use the
@/alias for all internal frontend imports:import { useAuth } from "@/contexts/AuthContext" - Use
import type { ... }for type-only imports:import type { User } from "@/types" - Order: external libraries first, then internal
@/imports - Named exports are the standard; avoid default exports (only
App.tsxuses one)
Formatting
- 2-space indentation throughout
- No Prettier config exists — maintain consistency with surrounding code
- 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
- Define all shared types as
interface(nottypealiases) in the relevanttypes/index.ts - Suffix input/mutation types:
CreateClientInput,UpdateProjectInput - Use
?for optional fields, notfield: T | undefined - Use
string | nullfor nullable fields (notundefined) - Backend Zod schemas live in
backend/src/schemas/index.ts, named<Entity>Schema(e.g.,CreateClientSchema) - Backend custom errors extend
AppError:NotFoundError,BadRequestError,ConflictError,UnauthorizedError
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
- Use named function declarations, not arrow functions assigned to
const:// 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
- Server state: TanStack React Query (all remote data). Never use
useStatefor server data.- Custom hooks encapsulate
useQuery+useMutation+ cache invalidation - Query keys are arrays:
["timeEntries", filters],["projects", clientId] - Use
mutateAsync(notmutate) so callers canawaitand handle errors - Invalidate related queries after mutations via
queryClient.invalidateQueries
- Custom hooks encapsulate
- Shared client state: React Context (
AuthContext,TimerContext) - Local UI state:
useStateper component (modals, form data, error messages) - No Redux or Zustand — do not introduce them
Error Handling
Frontend:
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:
router.get("/resource/:id", async (req, res, next) => {
try {
const result = await service.getById(req.params.id)
res.json(result)
} catch (error) {
next(error) // always forward to errorHandler middleware
}
})
- Throw
AppErrorsubclasses from services; never send raw error responses from route handlers - The global
errorHandlermiddleware handles Prisma error codes (P2002, P2025, P2003) andAppErrorsubclasses
Styling
- Tailwind CSS v3 for all styling — no CSS modules, no styled-components
- Use
clsx+tailwind-mergefor conditional class merging when needed - Icons from
lucide-reactonly
Backend Validation
- All incoming request data validated with Zod schemas before reaching service layer
- Schemas defined in
backend/src/schemas/index.ts - Validation middleware applied per-route; never trust
req.bodywithout parsing through a schema
Database
- Prisma v6 with PostgreSQL
- Database column names are
snake_case, mapped tocamelCaseTypeScript via@mapin the Prisma schema - Always use the Prisma client singleton from
backend/src/prisma/
Key Architectural Decisions
- The frontend communicates with the backend exclusively through the typed Axios modules in
frontend/src/api/ - Authentication supports two flows: OIDC (web, via
express-session) and JWT (iOS client, viajsonwebtoken) - The iOS app lives in
ios/and shares no code with the web frontend — do not couple them - All business logic belongs in service classes; routes only handle HTTP concerns (parsing, validation, response formatting)