Internes Project-Management-Tool für ToolCTRL — ergänzend zu Plane (Tickets) und Outline (Doku). Umfasst Puls-Übersicht, Tagesrhythmus (Lock-in + Reflexion), Bereichs-Detail-Views (Entwicklung/Vertrieb/Marketing), Pflicht-Minimum-Check und Blocker-Lifecycle gemäß Projektmanagement-Leitfaden v3.0. Stack: pnpm-Monorepo · Vite + React 18 + Tailwind · Fastify + tRPC v11 + Drizzle ORM · Postgres 16 · Authentik OIDC (Phase 2, Dev-Auto-Login aktiv). Planungs-Artefakte unter _bmad-output/planning-artifacts/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5.0 KiB
5.0 KiB
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:
<entity>_id—user_id,project_id. - Indizes:
idx_<table>_<column>; unique:uniq_<table>_<column>. - Enums: TypeScript-Union in Drizzle +
textmitcheck-Constraint in Postgres. - Zeitstempel:
created_at,updated_at,<verb>_atalstimestamptz, Defaultnow().
tRPC Backend
- Router-Datei:
apps/api/src/routers/<feature>.ts—day-entry.ts,project.ts. - Router-Variable:
<feature>Router(camelCase) —dayEntryRouter. - Procedure-Naming:
<feature>.<verbNoun>(camelCase) —dayEntry.checkIn,dayEntry.submitReflection,project.archive,blocker.resolve. - Reads:
list,byId,mine,forProject. - Mutations:
create,update,archive(stattdelete),resolve,checkIn,submitReflection. - Procedure-Inputs: immer Zod-Schema aus
@cockpit/shared/schemas/<feature>.
Frontend (React)
- Komponenten: PascalCase. Dateiname matched (
DayEntryForm.tsx). - Hooks:
use<Name>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/<journey>.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(Typdate). - Geldbeträge: Cent als
integerin DB; Frontend formatiert viaIntl.NumberFormat('de-DE'). - Booleans: echtes
boolean. Nie0/1oder"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:
TRPCErrormit 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 zeigterror.messageals Toast. - Logging:
INTERNAL_SERVER_ERRORmit Stack →pino.error.BAD_REQUEST→pino.info.
State (Frontend)
- Server-State: ausschließlich React Query via tRPC (
isPending,isError,data). Nie eigeneuseState-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/useReducerin der Komponente. Hochziehen, wenn Sibling-Sharing nötig — kein Context.
Audit & Authorization
- Mutations:
withAudit()-Middleware um jede schreibende Procedure. Schreibtaudit_eventin derselben Transaktion. - AuditEvent-Naming:
<entity>.<verb>(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-corein Vitest pro Komponente,@axe-core/playwrightfü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.