# CONVENTIONS — ToolCTRL everythingbutwork > **Bindend** für alle Code-erzeugenden Agenten und Menschen. Spiegelt Architektur-Spec § Implementation Patterns. Pflicht-Lesen vor jeder Story. ## Naming ### Datenbank (Drizzle + Postgres) - Tabellen: Singular, `snake_case` (englisch) — `user`, `project`, `day_entry`, `todo`. Domain-Begriffe in Spalten dürfen deutsch sein (`abwesend_bis`, `arbeitsmodus`). - Primärschlüssel: immer `id` (UUID v7). - Fremdschlüssel: `_id` — `user_id`, `project_id`. - Indizes: `idx__`; unique: `uniq_
_`. - Enums: TypeScript-Union in Drizzle + `text` mit `check`-Constraint in Postgres. - Zeitstempel: `created_at`, `updated_at`, `_at` als `timestamptz`, Default `now()`. ### tRPC Backend - Router-Datei: `apps/api/src/routers/.ts` — `day-entry.ts`, `project.ts`. - Router-Variable: `Router` (camelCase) — `dayEntryRouter`. - Procedure-Naming: `.` (camelCase) — `dayEntry.checkIn`, `dayEntry.submitReflection`, `project.archive`, `blocker.resolve`. - Reads: `list`, `byId`, `mine`, `forProject`. - Mutations: `create`, `update`, `archive` (statt `delete`), `resolve`, `checkIn`, `submitReflection`. - Procedure-Inputs: **immer** Zod-Schema aus `@cockpit/shared/schemas/`. ### Frontend (React) - Komponenten: PascalCase. Dateiname matched (`DayEntryForm.tsx`). - Hooks: `use` camelCase. - Konstanten: `SCREAMING_SNAKE_CASE`. - Route-Pfade: kebab-case, deutsch wo Domain — `/puls`, `/mein-heute`, `/bereich/entwicklung`. - Komponenten-Organisation: **nach Feature**, nicht nach Typ. ## Struktur - Tests **neben** dem Code: `Foo.tsx` ↔ `Foo.test.tsx` (Backend: `foo.ts` ↔ `foo.test.ts`). - E2E: zentral in `apps/web/e2e/.spec.ts`. - Shared zwischen Frontend + Backend → `packages/shared/`. Backend-only Helper → `apps/api/src/lib/`. - Konfig-Dateien im Repo-Root, pro App nur Overrides. ## Format - JSON-Felder über die Wire: `camelCase` (Drizzle mapt DB-`snake_case` → TS-`camelCase`). - Datumsformat: **ISO 8601 mit Zeitzone**. Reine Werktage `YYYY-MM-DD` (Typ `date`). - Geldbeträge: Cent als `integer` in DB; Frontend formatiert via `Intl.NumberFormat('de-DE')`. - Booleans: echtes `boolean`. Nie `0/1` oder `"yes"/"no"`. - Null vs. Undefined: DB nutzt `null`. Code: `null` = explizit nicht gesetzt, `undefined` = existiert nicht. - IDs in URL: UUIDs (`/projekt/:id`), keine numerischen IDs öffentlich. ## Errors - Backend: `TRPCError` mit klaren Codes — `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BAD_REQUEST`, `INTERNAL_SERVER_ERROR`. - Message: **immer deutsch**, menschenlesbar. Nicht „NULL constraint violated" sondern „Bitte wähle einen Verantwortlichen aus". - Frontend: zentraler `onError`-Hook in tRPC-Provider zeigt `error.message` als Toast. - Logging: `INTERNAL_SERVER_ERROR` mit Stack → `pino.error`. `BAD_REQUEST` → `pino.info`. ## State (Frontend) - Server-State: **ausschließlich React Query** via tRPC (`isPending`, `isError`, `data`). **Nie** eigene `useState`-Booleans für Server-Loading. - Loading: Skeleton-Komponenten, keine Spinner-Only. - Optimistic Updates für Reflexion, Todo-Toggle, Blocker-Auflösung. Sonst nicht. - Lokaler UI-State: `useState`/`useReducer` in der Komponente. Hochziehen, wenn Sibling-Sharing nötig — kein Context. ## Audit & Authorization - Mutations: `withAudit()`-Middleware um jede schreibende Procedure. Schreibt `audit_event` in derselben Transaktion. - AuditEvent-Naming: `.` (lowercase, dot-separated) — `day_entry.reflection_submitted`, `blocker.resolved`. - Procedure-Typen: `publicProcedure`, `protectedProcedure`, `dayEntryVisibleProcedure`, `projectOwnerProcedure`. ## Werktag / Zeit - Server-Zeit: **`Europe/Berlin`**. - Werktage: Mo-Fr (MVP, ohne Feiertage). - **Alle Datumslogik im Backend** — Frontend bekommt fertige Felder (`isWorkday`, `nextWorkday`, `streakDays`). ## Voice & Tone - UI-Strings zentral in `apps/web/src/lib/voice.ts`. - Tonalität: locker, direkt, deutsch, kurze Imperative — „Stark.", „Wie läuft es?", „Du hast heute noch nicht gepingt.". - Keine Corporate-Sprech, keine englischen Floskeln (Fachbegriffe aus Leitfaden erlaubt). - Error-Messages im Backend ebenfalls deutsch. ## Tests - Unit & Procedures: **Vitest**. - E2E: **Playwright** auf UJ-1, UJ-2, UJ-5 (CI nur auf Tag/Release). - A11y: `axe-core` in Vitest pro Komponente, `@axe-core/playwright` für E2E, Lighthouse-A11y ≥ 95 im CI. - Tests neben Code (`*.test.ts(x)`). ## DB-Änderungen - **Immer** Drizzle-Migration committen — kein Schema-Drift. - Migration entstehen je Story-Bedarf, nicht upfront. - TypeScript strict, kein `any`. Bei unklarer Form: `unknown` + Zod-Validation. ## Don'ts - ❌ Inline-Zod im Procedure (gehört nach `@cockpit/shared/schemas`). - ❌ Englisches Error-Message. - ❌ snake_case-Komponentennamen. - ❌ Server-Loading via `useState`. - ❌ Drag-and-Drop außerhalb des Sales-Kanban. - ❌ Pulsing-/Spin-Animationen auf dem Puls. - ❌ Auto-Refresh / Background-Polling.