Files
everythingbutwork/_bmad-output/planning-artifacts/architecture.md
T
dejanandClaude Opus 4.7 4f33509a0f
ci / quality (push) Has been cancelled
ci / build (push) Has been cancelled
Initial commit: everythingbutwork MVP
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>
2026-05-25 22:47:26 +02:00

59 KiB
Raw Blame History

stepsCompleted, lastStep, status, completedAt, inputDocuments, workflowType, project_name, user_name, date, binding_frame, prd_status
stepsCompleted lastStep status completedAt inputDocuments workflowType project_name user_name date binding_frame prd_status
1
2
3
4
5
6
7
8
8 complete 2026-05-25
prds/prd-projectmanagement-2026-05-25/prd.md
prds/prd-projectmanagement-2026-05-25/addendum.md
prds/prd-projectmanagement-2026-05-25/.decision-log.md
prds/prd-projectmanagement-2026-05-25/inputs/ToolCTRL_Projektmanagement-Leitfaden_v3.0.pdf
prds/prd-projectmanagement-2026-05-25/inputs/unternehmens-cockpit.html
prds/prd-projectmanagement-2026-05-25/extract-prototype-html.md
architecture projectmanagement Dejancosic 2026-05-25 ToolCTRL Projektmanagement-Leitfaden v3.0 (Mai 2026) final

Architecture Decision Document — ToolCTRL Unternehmens-Cockpit

Dieses Dokument wächst kollaborativ Schritt für Schritt. Sektionen werden im Verlauf der Discovery angehängt.


Project Context Analysis

Requirements Overview

Funktionale Anforderungen (49 FRs in 10 Gruppen):

Das Cockpit ist eine Drei-Tier-Übersichtsanwendung mit drei klar getrennten Sichten:

  1. Puls (FR-1 bis FR-7): Aggregierte Unternehmenssicht — Bereichsstatus, Heute-Quote, Pflicht-Minimum-Übersicht, Jourfixe-Hinweis, bereichsübergreifende Blocker.
  2. Bereichs-Detail-Views (FR-8 bis FR-20): Drei fachspezifische Sichten — Entwicklung (Projekte+Meilensteine), Vertrieb (Kanban+gewichtete Pipeline), Marketing (Funnel+Kampagnen). Crosslink Marketing↔Vertrieb.
  3. Tagesrhythmus (FR-21 bis FR-33): Zweistufiger Workflow — Morgen-Einstempeln+ToDos, Abend-Reflexion mit Ampel/Trend, nächster-Werktag-Publikation.

Querschnittsbereiche: Streak (F), Pflicht-Minimum (G), Blocker (H), Projekt-Abschluss (I), Navigation (J).

Nicht-funktionale Anforderungen (16 NFRs):

Architekturtreibend sind primär:

  • NFR-1/2 (Performance): Puls < 1,5 s, Update-Speichern < 500 ms — moderate Anforderungen bei 6-15 Concurrent Users; kein Skalierungsdruck.
  • NFR-5 (Auth): Pflicht-Integration mit Authentik (OIDC) — vorhandene Infrastruktur, kein eigenes Identity-Management.
  • NFR-7 (Audit): Mutationsereignisse mit actor / timestamp / payload — Audit-Tabelle Pflicht.
  • NFR-10 (Plattform): Desktop-first, mobil-lesbar — keine Mobile-App im MVP.
  • NFR-12/13 (Voice & Tone): Mikrocopy-Regeln im UI-Schicht verankern.
  • NFR-14/15 (Daten): Postgres + tägliches Backup — Pflicht-Stack.
  • NFR-16 (Erweiterbarkeit): Datenmodell muss Phase-2-Erweiterungen ohne Migrationen > 2 h tragen (Plane/Outline-API-Sync, Mobile, weitere Bereiche, Kommentare).

Skalierung & Komplexität:

  • Primärdomäne: Full-Stack-Web-Anwendung (React-Frontend, DB-backed Backend, OIDC).
  • Komplexitätsstufe: mittel — kein Hyperscale, aber nicht-triviales Datenmodell mit einer interessanten Sichtbarkeitslogik (private DayEntry vor published_at → konsolidiert ab nächstem Werktagsmorgen).
  • Architektonisch geschätzte Komponenten: ~6–8 (Frontend-SPA, Auth-Adapter, REST-API, DB-Schicht, Aggregations-/Caching-Schicht, optionaler Worker für Publikation/Streak, Audit-Logger, Hosting/Deployment).

Technische Constraints & Abhängigkeiten

Hart vorgegeben (Pflicht-Stack):

  • Frontend: React (PRD-Default, Team-Kompetenz).
  • Persistenz: Postgres (PRD NFR-14).
  • Auth: Authentik OIDC auf auth.tool-ctrl.de (PRD NFR-5, Addendum §1.3).
  • Hosting: vermutlich identische interne Infrastruktur wie Plane/Outline/Gitea (PRD §1.1 Tool-Aufteilung; Addendum §1.1).
  • Sprache: Deutsch durchgängig im UI (PRD NFR-11).

Methodisch verbindlich (Leitfaden v3.0):

  • Cockpit muss die Tool-Aufteilung respektieren — kein Spiegel von Plane, kein Steckbrief-Editor (PRD Anti-Ziele A3/A4/A5).
  • Pflicht-Minimum-Check (Leitfaden §9) ist Erstklass-Feature, nicht optional.

Phase-2-Vorbereitung (architektonisch zu berücksichtigen):

  • Plane-API-Sync (Pflicht-Punkt 3 Auto-Validierung).
  • Outline-API-Sync (Pflicht-Punkt 1 Auto-Validierung).
  • Lessons-Learned-Vorlage automatisch in Outline anlegen beim Abschluss.

Externe Systeme im laufenden MVP:

  • Authentik (Pflicht, kritisch).
  • Plane/Outline (nur Verlinkung, keine API-Aufrufe im MVP — aber URL-Felder schon im Schema).

Cross-Cutting Concerns

  1. Identität (Authentik OIDC). Affiziert: alle Routen, User-Mapping, Default-Bereich per Group-Claim (OQ-1).
  2. Audit-Trail. Affiziert: jede mutierende Operation (Update-Reflexion, Blocker-Lifecycle, Meilenstein-Statuswechsel, Pflicht-Minimum-Änderungen).
  3. Sichtbarkeitsmodell der DayEntries. Sonderfall: pre-publication privat (nur Self + Projekt-P1); post-publication für alle Kollegen. Erfordert Filter-Logik in der API-Schicht, kann nicht reine Row-Level-Security sein.
  4. Status-Caches (status_cache, trend_cache). Denormalisierungen, die aus letzter Reflexion abgeleitet sind. Konsistenz: bei jeder Reflexion Cache aktualisieren.
  5. Pflicht-Minimum-State. Pro Projekt ein JSON-Block mit 6 Booleans. Punkt 5 (Status-Ampel aktuell) wird zeitabhängig (≤ 7 Werktage) → muss periodisch oder lazy berechnet werden.
  6. Werktags-/Tageszeitlogik. Streak (FR-34), „Heute fehlt"-Liste (FR-35), Tages-Status (FR-33), Publikation (FR-29) — alle abhängig von einer konsistenten Werktagsdefinition (Mo-Fr, ohne Feiertage im MVP). Server-Zeit Europe/Berlin als Wahrheit.
  7. Internationalisierung. Bewusst keine — Deutsch durchgängig (NFR-11). Brand-konsistente UI-Texte (NFR-12/13) als Constraint im Frontend.

Architekturrelevante Open Questions aus PRD

  • OQ-1 Authentik-Group-Claim → Cockpit-Bereich-Mapping. Architektur entscheidet Default-Strategie (z. B. erste passende Group, manuelle Korrektur im Profil).
  • OQ-5 Archivierungs-Policy: Historie für immer behalten oder nach X Monaten löschen? Hat Auswirkungen auf Datenwachstum, Backup-Strategie, evtl. Soft-Delete vs. Hard-Delete.
  • OQ-6 Pflicht-Minimum-Punkt 1 — manuelle Checkbox vs. Outline-API-Polling. PRD setzt manuelle Checkbox im MVP; Architektur baut so, dass spätere API-Validierung ohne Schemaänderung möglich ist.

Starter Template Evaluation

Primary Technology Domain

Full-stack Web-Anwendung mit React-SPA-Frontend, Fastify-Backend, Postgres-Persistenz und OIDC-Auth. Self-hosted in der ToolCTRL-internen Infrastruktur.

Constraint: React (SPA), nicht Next.js

Per Team-Vorgabe wird React als SPA eingesetzt, kein Next.js. Das schließt Next.js-zentrierte Starter (T3 Stack, RedwoodJS, Blitz) aus und führt zu einer zweiteiligen Architektur: ein Vite-React-Frontend und ein separates Fastify-Backend.

Starter Options Considered

Option Bewertung
T3 Stack (Next.js) ❌ Ausgeschlossen — Team-Vorgabe React-SPA, kein Next.js
Custom Vite + React + Fastify-Backend Monorepo ✅ Gewählt — passt zur React-SPA-Vorgabe; kein passender vorgefertigter Starter, also definieren wir die Struktur selbst
Create React App (CRA) ❌ Nicht mehr aktiv gewartet (2026), Vite ist Standard
Remix ❌ Auch ein Full-Stack-React-Framework wie Next.js — gleicher Ausschlussgrund

Selected Starter: Custom Monorepo (Vite + React + Fastify)

Es gibt keinen einzelnen npm create …-Befehl, der genau diesen Schnitt liefert. Wir initialisieren die Repo-Struktur selbst und ziehen die Tools über pnpm rein.

Rationale:

  • Erfüllt die Team-Vorgabe React-SPA, kein Next.js.
  • tRPC v11 funktioniert unverändert über den Fastify-Adapter — End-to-End-Typsicherheit bleibt erhalten (Frontend und Backend teilen sich den AppRouter-Typ via packages/shared).
  • Sauberer Schnitt zwischen Frontend (apps/web) und Backend (apps/api) erlaubt unabhängige Deploys und Background-Job-Endpoints im Backend ohne Frontend-Build.
  • Drizzle bleibt als ORM — leichter, schnellere Migrationen (NFR-16: max 2 h).
  • Authentik-OIDC über openid-client direkt im Fastify (Auth.js v5 hat keinen ausgereiften Fastify-Adapter, Express-Adapter wäre eine Brücke).
  • Tailwind im Vite-Frontend für Brand-Token-Theming.

Repo-Struktur (Monorepo, pnpm workspaces)

cockpit/
├── apps/
│   ├── web/                 # Vite + React + TypeScript + Tailwind
│   │   ├── src/
│   │   │   ├── routes/      # React Router v7 Routes
│   │   │   ├── components/
│   │   │   ├── lib/trpc.ts  # tRPC-Client
│   │   │   └── main.tsx
│   │   └── vite.config.ts
│   └── api/                 # Node + Fastify + tRPC server + Drizzle
│       ├── src/
│       │   ├── routers/     # tRPC-Router (procedures)
│       │   ├── auth/        # Authentik OIDC via openid-client
│       │   ├── cron/        # Background-Job-Endpoints
│       │   ├── server.ts
│       │   └── trpc.ts
│       └── tsconfig.json
├── packages/
│   ├── db/                  # Drizzle-Schema + Migrationen
│   │   ├── schema.ts
│   │   ├── drizzle.config.ts
│   │   └── migrations/
│   └── shared/              # AppRouter-Typ, Zod-Schemata, Konstanten
│       └── src/
├── docker-compose.yml
├── Caddyfile
├── pnpm-workspace.yaml
└── package.json

Initialization Commands

# Repo anlegen
mkdir cockpit && cd cockpit
pnpm init
git init

# Workspaces
mkdir -p apps/web apps/api packages/db packages/shared
echo 'packages:\n  - "apps/*"\n  - "packages/*"' > pnpm-workspace.yaml

# Frontend
cd apps/web
pnpm create vite . --template react-ts
pnpm add react-router @trpc/client @trpc/react-query @tanstack/react-query \
        tailwindcss postcss autoprefixer zod
pnpm add -D @types/react @types/react-dom

# Backend
cd ../api
pnpm init
pnpm add fastify @fastify/cookie @trpc/server openid-client \
        drizzle-orm pg pino zod
pnpm add -D typescript tsx @types/node @types/pg drizzle-kit

# Shared
cd ../../packages/shared
pnpm init
pnpm add zod

Architectural Decisions Provided by Custom Starter

  • Language: TypeScript (strict mode, ES2022 target).
  • Frontend Framework: React 18+ via Vite 5+.
  • Frontend Routing: React Router v7.
  • Backend Framework: Fastify 5+.
  • API Layer: tRPC v11 (typsicher, Frontend ↔ Backend per AppRouter-Typ-Sharing).
  • ORM: Drizzle (Postgres-Adapter).
  • Auth: openid-client (Authentik OIDC, Authorization Code Flow + PKCE), Session-Cookies (HttpOnly, SameSite=Lax), Session-Store in Postgres.
  • Styling: Tailwind CSS.
  • Build: Vite für Frontend, tsx (Dev) + tsc (Prod) für Backend.
  • Validation: Zod (im shared-Package, beidseitig genutzt).
  • State Management: React Query (Server-State), React Context + useState/useReducer (Client-State).

Note: Die erste Implementation-Story (in bmad-create-epics-and-stories) ist das Bootstrap dieser Monorepo-Struktur mit obigen Befehlen.

Bewusst NICHT im Starter:

  • Realtime/WebSockets (kein Realtime-Bedarf im MVP).
  • i18n (Deutsch durchgängig, NFR-11).
  • SSR (per Vorgabe SPA).
  • Background-Worker-Service — wir nutzen System-Cron → Fastify-Endpoint (siehe Step 4).

Core Architectural Decisions

Decision Priority Analysis

Critical Decisions (blocken Implementierung — alle entschieden):

  • Backend-Framework, ORM, Auth-Flow, Repo-Struktur, Hosting, Background-Jobs.

Important Decisions (formen die Architektur — alle entschieden):

  • Authorization-Pattern, Status-Cache-Strategie, Logging, Tests, CI/CD, Reverse-Proxy.

Deferred Decisions (Post-MVP):

  • Monitoring/Alerting jenseits Healthcheck (Loki/Grafana — wenn das Tool wirklich genutzt wird).
  • Rate-Limiting (im internen Tool aktuell nicht nötig).
  • Outline-/Plane-API-Sync (Phase 2 — Skeleton im Schema vorhanden, Umsetzung später).

Data Architecture

  • DB-Engine: PostgreSQL 16+ (verifiziert für pg_cron-Verfügbarkeit, JSONB-Support).
  • ORM: Drizzle (vom Starter) — Schema in packages/db/schema.ts, Migrationen via drizzle-kit generate und drizzle-kit migrate. Migrationen im Repo eingecheckt.
  • Validation: Zod, im packages/shared definiert; beidseitig (Backend tRPC-Input + Frontend Form-Resolver) genutzt.
  • Status-Caches: Project.status_cache, Project.trend_cache, Project.pflicht_minimum_state als reguläre Postgres-Spalten (jsonb für den Pflicht-Minimum-State). Kein Redis — nicht nötig bei 6–15 Usern.
  • Encryption at Rest: Postgres-Volume-Encryption via Docker-Volume auf verschlüsseltem Host-Filesystem.
  • Backup: pg_dump nightly via System-Cron im Postgres-Container, Dumps auf separates Volume; Retention 14 Tage. Restore-Drill quartalsweise (siehe Operative Notes).

Authentication & Security

  • Provider: Authentik (auth.tool-ctrl.de) als externer OIDC-IdP.
  • Client-Library: openid-client v6+ direkt im Fastify-Backend (Auth.js v5 hat keinen reifen Fastify-Adapter; openid-client ist die referenzierte Library hinter den meisten OIDC-Implementierungen).
  • Flow: Authorization Code Flow + PKCE.
  • Session: Server-seitige Session im Postgres (Session-Tabelle), Browser bekommt nur session_id als HttpOnly + SameSite=Lax + Secure Cookie. Keine JWTs im Cookie — einfacher zu invalidieren, weniger Tokens im Umlauf.
  • User-Mapping: beim ersten Login wird User-Zeile angelegt mit authentik_sub als externer Schlüssel; Name/Email aus dem ID-Token-Claim; Default-Bereich aus dem Authentik-Group-Claim (Mapping in Implementation festzulegen — siehe OQ-1).
  • Authorization-Pattern (tRPC-Procedures):
    • publicProcedure — nur Login-Routen.
    • protectedProcedure — Standard, prüft Session.
    • dayEntryVisibleProcedure — wrapt protectedProcedure, filtert DayEntries: vor published_at nur eigene + (falls Bezug=Projekt) Projektverantwortliche/r; ab published_at für alle Kollegen.
    • projectOwnerProcedure — wrapt protectedProcedure, prüft, dass aktueller User der P1 des in input referenzierten Projekts ist (z. B. für Blocker-Auflösung FR-44, Projekt-Abschluss FR-45).
  • CSRF-Schutz: SameSite=Lax-Cookies + Origin-Header-Prüfung im Fastify-Pre-Handler. Kein separates Anti-CSRF-Token nötig, da keine Cross-Origin-Requests vorgesehen.
  • TLS: über Caddy als Reverse-Proxy (Automatic HTTPS via Let's Encrypt).
  • Rate-Limiting: im MVP nicht eingebaut. Authentik selbst limitiert Login-Versuche. Falls später nötig: @fastify/rate-limit.

API & Communication Patterns

  • API-Layer: tRPC v11 — Backend exportiert AppRouter aus apps/api/src/trpc.ts; Frontend importiert den Typ aus packages/shared und nutzt createTRPCReact<AppRouter>().
  • Transport: HTTP/HTTPS, POST für Mutations und Queries (tRPC-Standard).
  • Error Handling: TRPCError-Subklassen mit deutschen message-Strings für menschenlesbare Fehler; Frontend zeigt sie via einem zentralen onError-Hook in React Query als Toast (Voice & Tone: NFR-12/13).
  • API-Doku: Tdie tRPC-Typen sind die Dokumentation. Keine separate OpenAPI/Swagger-Schicht im MVP.
  • Versionierung: keine — interner Monolith, Frontend und Backend immer im Gleichschritt deployt.
  • Inter-Service-Kommunikation: entfällt — Monolith.

Frontend Architecture

  • Framework: React 18+ als SPA via Vite 5+.
  • Routing: React Router v7 (Data Router API, loader/action für serverseitige Datenvorbereitung sofern sinnvoll; Standard sind aber tRPC + React Query).
  • Server-State: React Query via @trpc/react-query. Cache-Invalidierung pro tRPC-Procedure-Key.
  • Client-State: React Context für user (aktueller eingeloggter User) und Theme-/Brand-Tokens. Lokaler UI-State via useState/useReducer. Kein globaler Store (Redux/Zustand) — bewusst nicht nötig.
  • Forms: React Hook Form + Zod-Resolver (Schemata aus packages/shared).
  • Styling: Tailwind CSS mit Custom-Theme in tailwind.config.ts — Brand-Tokens (weiß primär, schwarz sekundär) als CSS-Variablen + Tailwind-Erweiterungen. Konkrete Token-Werte aus bmad-create-ux-design (OQ-2 im PRD).
  • Components: Headless-UI-Library (@headlessui/react oder radix-ui Primitives) für komplexe Interaktionen (Modals, Dropdowns, Tabs). Konkrete Wahl in UX-Phase.
  • Performance: Code-Splitting per Route via React Router lazy-Loading; Tailwind purging im Vite-Build; Bundle-Größe-Budget < 300 kB initial.

Infrastructure & Deployment

  • Hosting: Bestehende ToolCTRL-interne Infrastruktur — gleicher Docker-Host wie Plane/Outline/Authentik/Gitea.
  • Container: Multi-Stage-Dockerfiles pro App.
    • apps/web → Vite-Build → Static-Files-Serving via Caddy.
    • apps/api → Node 22+ Container (LTS), startet Fastify auf Port 3000.
    • postgres:16 als separates Container (oder bestehende Postgres-Instance, falls vorhanden — in Implementation entscheiden).
  • Reverse-Proxy: Caddy 2.x mit Subdomain cockpit.tool-ctrl.de. Caddyfile:
    • Statische Web-Assets aus apps/web/dist.
    • /api/* und /trpc/* reverse-proxy auf api:3000.
    • Automatic HTTPS.
  • Background-Jobs: System-Cron im API-Container (oder Host-Crontab) → ruft authentifizierten Fastify-Endpoint POST /internal/cron/<job> mit Bearer-Token aus Env-Variable auf.
    • Jobs im MVP:
      • publish-day-entries — täglich 00:05 Europe/Berlin, setzt published_at auf alle DayEntries des Vortags.
      • recompute-pflicht-minimum-5 — stündlich, prüft pro Projekt, ob letzter Status-Update ≤ 7 Werktage alt.
      • recompute-streaks — täglich 00:10, aktualisiert User-Streaks.
      • pg-dump-backup — täglich 02:00.
    • Rationale für die Wahl: User hat "weiß nicht" geantwortet → Architekt wählt den einfachsten, wartbarsten Pfad. Cron + Endpoint hat den besten Debug- und Observability-Workflow (Logs im API-Container, Endpoint manuell anstoßbar).
  • CI/CD: Gitea Actions (Gitea ist im Authentik-Stack vorhanden). Pipeline:
    1. pnpm install
    2. Typecheck (tsc --noEmit)
    3. Lint (eslint)
    4. Tests (vitest run, Playwright nur bei Tag/Release)
    5. Build (Vite + tsc)
    6. Docker-Images bauen und in lokale Registry pushen
    7. Deploy via docker-compose pull && up -d
  • Environment Config: .env.local (dev, ignored), .env.production (prod, Docker-Secret-Mount). Validation via zod beim Backend-Startup (Fail-Fast bei fehlenden Env-Variablen).
  • Logging: strukturierte JSON-Logs via pino → stdout → Docker-Logs. Aggregation (Loki) Post-MVP.
  • Monitoring: GET /api/health (Postgres-Ping + Authentik-Reachability) + Docker-Healthcheck. Externe Uptime-Überwachung optional Post-Launch.
  • Scaling: kein Skalierungspfad nötig (≤ 30 User in 12 Monaten — A-3).

Testing-Strategie

  • Unit-/Integration-Tests: Vitest. Schwerpunkt auf tRPC-Procedures (Backend) und Zod-Schemas/Pure-Funktionen (Frontend). DB-nahe Tests gegen eine Test-Postgres-Instance (testcontainers oder lokaler Docker).
  • E2E: Playwright auf die kritischen User Journeys UJ-1 (Tagesrhythmus), UJ-2 (Jourfixe), UJ-5 (Projekt anlegen). Laufen in CI nur auf Tag/Release.
  • Test-Daten: Seed-Skript in packages/db/seed.ts, idempotent. Erzeugt 5 Test-User, 3 Projekte pro Bereich, einige DayEntries.

Operative Notes

  • Werktage / Zeitzone: Server-Zeit Europe/Berlin. Werktage hart in einer Konstante (MO-FR), keine Feiertags-Erkennung im MVP (OQ aus PRD, Phase 2).
  • Backup-Restore-Drill: quartalsweise — Dump auf separaten Container restoren, Smoke-Test der App gegen die Kopie.
  • Datenwachstum: Schätzung 10 DayEntries × 15 User × 220 Werktage = 33.000 Zeilen/Jahr in DayEntry; vergleichbar in Todo (3-5× mehr). Selbst nach 5 Jahren < 1 Mio. Zeilen — keine Partitionierung nötig.

Decision Impact Analysis

Implementation Sequence (Story-1 → Story-N):

  1. Repo-Bootstrap (Monorepo mit pnpm-Workspaces, Vite, Fastify, Drizzle, Tailwind).
  2. Drizzle-Schema + erste Migration (alle Entitäten aus Addendum §1.2).
  3. Fastify-Server mit Health-Endpoint.
  4. Authentik-OIDC-Login-Flow (openid-client, Session in DB).
  5. tRPC-Bootstrap mit protectedProcedure und User-Context.
  6. Frontend-Shell (React Router + Layout + Login-Redirect).
  7. Tagesrhythmus-Slice (Einstempeln + ToDos + Reflexion) — UJ-1.
  8. Puls-Seite.
  9. Bereichs-Detail-Views (Dev/Sales/Marketing).
  10. Pflicht-Minimum-Feature + Blocker.
  11. Background-Jobs (Cron-Endpoints, Cron-Definition im Docker-Compose).
  12. Projekt-Abschluss + Archiv.
  13. Brand-Theming (auf Basis der UX-Design-Tokens).
  14. CI/CD-Pipeline + Deployment.

Cross-Component Dependencies:

  • Auth (Schritt 4) ist Voraussetzung für jede protectedProcedure (alle Folgesteps).
  • Tagesrhythmus (Schritt 7) erzeugt DayEntries, die der Puls (Schritt 8) und Bereichs-Detail-Views (Schritt 9) anzeigen.
  • Pflicht-Minimum-Feature (Schritt 10) braucht Projekt-Entität (Schritt 9).
  • Background-Jobs (Schritt 11) brauchen alle vorherigen Schemata und sind orthogonal zur UI.

Implementation Patterns & Consistency Rules

Diese Regeln sind bindend für alle Code-erzeugenden Agenten und Menschen. Sie verhindern Konflikte, wenn parallel an mehreren Stories gearbeitet wird.

Naming Patterns

Database (Drizzle + Postgres)

  • Tabellen: Singular, snake_case, englisch — user, project, day_entry, todo, blocker. Nicht users, nicht DayEntry.
  • Spalten: snake_case — user_id, created_at, pflicht_minimum_state. Domain-Begriffe dürfen deutsch sein (abwesend_bis, arbeitsmodus), Audit-Felder bleiben englisch.
  • Primärschlüssel: immer id (UUID v7 — zeitgeordnet, indextauglich). Nicht <entity>_id.
  • Fremdschlüssel: <entity>_id — user_id, project_id. Nicht fk_user.
  • Indizes: idx_<table>_<column> — idx_day_entry_user_id. Eindeutige: uniq_<table>_<column>.
  • Enums: als TypeScript-Union in Drizzle-Schema, in Postgres als text mit check-Constraint (einfacher zu erweitern als pg_enum).
  • Zeitstempel: created_at, updated_at, <verb>_at (reflected_at, published_at). Type timestamptz. Default now().

tRPC (Backend-API)

  • Router-Datei: apps/api/src/routers/<feature>.ts — day-entry.ts, project.ts, blocker.ts.
  • Router-Variable: <feature>Router als camelCase — dayEntryRouter, projectRouter.
  • Procedure-Naming: Verb-Substantiv in 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, importiert aus packages/shared/schemas/<feature>.ts. Nicht inline.
  • Procedure-Outputs: immer explizit typisiert via Zod (vermeidet versehentliche Schema-Drift).

Frontend (React)

  • Komponenten: PascalCase — DayEntryForm, ProjectCard, BlockerList. Dateiname matched: DayEntryForm.tsx.
  • Hooks: use<Name> camelCase — useCurrentUser, useTodaysEntry, useProjectAmpel.
  • Utility-Funktionen: camelCase, Dateiname kebab-case — format-werktag.ts exportiert formatWerktag().
  • Konstanten: SCREAMING_SNAKE_CASE — MAX_TODO_TEXT_LENGTH = 200, WERKTAGE = ['MO','DI','MI','DO','FR'].
  • Route-Pfade: kebab-case, deutsch wo Domain-Begriff — /puls, /bereich/entwicklung, /mein-heute, /projekt/:id.
  • CSS-Klassen via Tailwind: keine eigenen Klassennamen außer Brand-Token-Variablen (bg-brand-primary).

Files & Folders

  • Frontend-Komponenten: apps/web/src/components/<feature>/<ComponentName>.tsx.
  • Frontend-Routen-Komponenten: apps/web/src/routes/<route-name>.tsx.
  • Backend-Router: apps/api/src/routers/<feature>.ts.
  • Shared Schemas: packages/shared/schemas/<feature>.ts.
  • DB-Schema: alle Tabellen in einer Datei: packages/db/schema.ts. Bei > 500 Zeilen splitten nach Feature.

Structure Patterns

  • Tests liegen neben dem Code: DayEntryForm.tsx ↔ DayEntryForm.test.tsx. Backend: day-entry.ts ↔ day-entry.test.ts.
  • E2E-Tests: zentral in apps/web/e2e/<journey>.spec.ts (UJ-bezogen — uj-1-tagesrhythmus.spec.ts).
  • Komponenten-Organisation: nach Feature, nicht nach Typ. Nicht components/forms/ und components/cards/, sondern components/day-entry/, components/project/.
  • Shared-Utility-Funktionen: apps/web/src/lib/ (Frontend), apps/api/src/lib/ (Backend). Wirklich shared zwischen beiden → packages/shared/.
  • Konfig-Dateien im Repo-Root (tsconfig.json, .eslintrc.cjs, prettier.config.js, tailwind.config.ts, drizzle.config.ts). Pro App nur überschreibende Configs.

Format Patterns

  • JSON-Felder über die Wire: camelCase (tRPC serialisiert TS-Property-Namen 1:1). Drizzle mapt DB-snake_case → TS-camelCase automatisch via columnName Option.
  • Datums-Format: ISO 8601 mit Zeitzone — 2026-05-25T14:30:00+02:00. Im Frontend mit Intl.DateTimeFormat('de-DE') formatiert (nicht moment, nicht dayjs — Intl reicht).
  • Werktage-Format: Pures YYYY-MM-DD (kein Zeitanteil) für DayEntry.date, AbsenceDay.date. In Drizzle als date-Typ (nicht timestamptz).
  • Geldbeträge: value_eur als integer in Cent (bigint wenn > 21 Mio. €). Frontend formatiert via Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).
  • Booleans: echtes boolean. Nicht 0/1, nicht "yes"/"no".
  • Null vs. Undefined: Datenbank speichert null, TypeScript-Code nutzt null für „explizit nicht gesetzt", undefined für „existiert nicht". Konsistent durchziehen.
  • IDs in der URL: UUIDs als Param — /projekt/:id mit id = UUID-String. Keine numerischen IDs öffentlich.

Communication Patterns

Error Handling

  • Backend: TRPCError mit klaren codes:
    • UNAUTHORIZED — kein Login
    • FORBIDDEN — Login aber falsche Rolle (z. B. nicht P1 für Blocker-Auflösung)
    • NOT_FOUND — Entität existiert nicht oder ist außerhalb der Sicht
    • BAD_REQUEST — Zod-Validation gescheitert (tRPC mappt das automatisch)
    • INTERNAL_SERVER_ERROR — alles Unerwartete
  • Error-Message: immer deutsch, menschenlesbar, nicht technisch. Nicht „NULL constraint violated on user_id", sondern „Bitte wähle einen Verantwortlichen aus".
  • Frontend: zentraler onError-Hook in der tRPC-Client-Provider-Config zeigt error.message als Toast (Voice & Tone NFR-12/13). Bei UNAUTHORIZED → Redirect auf Login.
  • Logging: Backend loggt jeden INTERNAL_SERVER_ERROR mit Stack via pino Level error. BAD_REQUEST ist Level info, nicht error.

Loading States

  • Server-State: ausschließlich React Query (isPending, isError, data). Niemals eigene useState-Booleans für Server-Loading.
  • Skeleton-Pattern: Listen und Cards zeigen Skeletons (SkeletonCard), nicht „Lädt …"-Text.
  • Optimistic Updates: für Tagesupdate-Reflexion und Blocker-Auflösung — fühlt sich schneller an. Für Projekt-Anlegen nicht (User braucht Server-bestätigte ID).
  • Mutation-Loading: Button wird disabled mit kleinem Spinner. Kein Modal-Overlay.

State Management

  • Server-State: React Query via tRPC. Cache-Key = Procedure-Path + Input. Invalidierung pro Procedure.
  • Auth-State: React Context <UserProvider> mit aktuellem User-Objekt. Geladen via protectedProcedure user.me einmal beim Mount.
  • Brand/Theme: CSS-Variablen im :root, kein State-Container.
  • Lokaler UI-State: useState/useReducer in der Komponente. Wenn State zwischen Sibling-Komponenten gebraucht wird → in Parent hochziehen, kein Context.
  • Forms: React Hook Form pro Form, Zod-Resolver mit Schema aus packages/shared.

Events / Audit

  • AuditEvent-Naming: <entity>.<verb> (lowercase, dot-separated) — day_entry.reflection_submitted, blocker.resolved, project.archived.
  • Payload: strukturiertes JSONB mit before und after (für Mutations). Erzeugt im Backend mittels eines withAudit-Middleware-Wrappers um tRPC-Mutations.

Process Patterns

  • Werktag-Berechnungen: Alle Datumslogik im Backend, niemals im Frontend. Frontend bekommt fertige Felder (isWorkday, nextWorkday, streakDays) aus dem API.
  • Sichtbarkeitsfilter (DayEntry): immer im Backend in der dayEntryVisibleProcedure, nie als Frontend-Filter. Frontend zeigt, was die API liefert.
  • Pflicht-Minimum-Berechnung: lazy im Backend beim Lesen des Projekts (keine periodische Re-Berechnung der Punkte 1-4; Punkt 5 wird im Cron-Job aktualisiert).
  • Status-Cache-Aktualisierung: bei jeder Reflexions-Mutation in derselben DB-Transaktion. Niemals separat.

Enforcement Guidelines

Alle Code-erzeugenden Agenten MÜSSEN:

  • TypeScript strict mode beachten; keine any. Bei wirklich unbekannter Form: unknown + Zod-Validation.
  • Zod-Schemas aus packages/shared importieren, niemals duplizieren.
  • Bei DB-Änderung immer eine Drizzle-Migration committen (kein Schema-Drift).
  • Tests neben Code schreiben (*.test.ts).
  • Voice & Tone aus PRD NFR-12/13 in jeder neuen UI-Copy.
  • Audit-Wrapper für jede schreibende tRPC-Procedure verwenden.

Pattern-Verstöße werden in PR-Review angesprochen. Ein zentrales CONVENTIONS.md im Repo-Root spiegelt diese Sektion und ist Pflicht-Lesen vor Story-Bearbeitung.

Pattern Examples

Good:

// packages/shared/schemas/day-entry.ts
export const submitReflectionInput = z.object({
  dayEntryId: z.string().uuid(),
  reflectionText: z.string().max(1000),
  ampel: z.enum(['green','amber','red']),
  trend: z.enum(['besser','gleich','schlechter']).nullable(),
  blockerText: z.string().optional(),
  url: z.string().url().optional(),
});
export type SubmitReflectionInput = z.infer<typeof submitReflectionInput>;

// apps/api/src/routers/day-entry.ts
export const dayEntryRouter = router({
  submitReflection: protectedProcedure
    .input(submitReflectionInput)
    .mutation(async ({ ctx, input }) => { /* … */ }),
});

Anti-Pattern (verboten):

// ❌ Inline-Zod im Procedure, kein shared Schema
.input(z.object({ id: z.string(), text: z.string() }))

// ❌ Englisches Error-Message
throw new TRPCError({ code: 'BAD_REQUEST', message: 'Invalid input' });

// ❌ snake_case-Component
export const day_entry_form = () => { … }

// ❌ Server-Loading via useState
const [loading, setLoading] = useState(false);
// statt React Query's isPending

Project Structure & Boundaries

Complete Project Directory Structure

cockpit/
├── README.md
├── CONVENTIONS.md                    # Spiegel der Patterns-Sektion. Pflicht-Lesen.
├── package.json                      # Root package.json (private, workspaces)
├── pnpm-workspace.yaml
├── pnpm-lock.yaml
├── tsconfig.base.json                # Geerbt von allen Paketen
├── .eslintrc.cjs
├── .prettierrc
├── .editorconfig
├── .gitignore
├── .env.example                      # Template für alle Apps
├── docker-compose.yml                # web + api + postgres + caddy
├── docker-compose.dev.yml            # Override für lokale Entwicklung
├── Caddyfile
├── .gitea/
│   └── workflows/
│       ├── ci.yml                    # Typecheck, Lint, Test, Build
│       └── deploy.yml                # Manuelle Deploy-Trigger
├── apps/
│   ├── web/                          # ───── FRONTEND (Vite + React SPA) ─────
│   │   ├── package.json
│   │   ├── vite.config.ts
│   │   ├── tsconfig.json
│   │   ├── tailwind.config.ts        # Brand-Tokens hier (weiß primär, schwarz sekundär)
│   │   ├── postcss.config.js
│   │   ├── index.html
│   │   ├── Dockerfile                # Multi-Stage Vite-Build → Caddy serves dist/
│   │   ├── public/
│   │   │   ├── favicon.svg
│   │   │   └── assets/               # Brand-Logos (von ToolCTRL Brand Identity)
│   │   ├── src/
│   │   │   ├── main.tsx              # ReactDOM root, Router, tRPC Provider
│   │   │   ├── App.tsx               # Outer Layout + Auth-Gate
│   │   │   ├── styles/
│   │   │   │   └── globals.css       # Tailwind + CSS-Variablen
│   │   │   ├── lib/
│   │   │   │   ├── trpc.ts           # createTRPCReact<AppRouter>()
│   │   │   │   ├── router.tsx       # React Router v7 routes config
│   │   │   │   ├── format.ts        # Datums-/EUR-Formatierung via Intl
│   │   │   │   └── voice.ts          # UI-Strings (NFR-12/13 Voice & Tone)
│   │   │   ├── context/
│   │   │   │   └── user-context.tsx  # <UserProvider>
│   │   │   ├── routes/               # React-Router-Page-Components
│   │   │   │   ├── login.tsx
│   │   │   │   ├── puls.tsx                       # FR-1 bis FR-7
│   │   │   │   ├── bereich.$slug.tsx              # FR-6 → FR-8…FR-20
│   │   │   │   ├── projekt.$id.tsx                # FR-10 + FR-38 Pflicht-Minimum
│   │   │   │   ├── mein-heute.tsx                 # UJ-1, FR-22…FR-25
│   │   │   │   ├── reflexion.tsx                  # FR-26…FR-28
│   │   │   │   ├── profil.tsx
│   │   │   │   ├── archiv.tsx                     # FR-47, FR-48
│   │   │   │   └── 404.tsx
│   │   │   ├── components/
│   │   │   │   ├── layout/
│   │   │   │   │   ├── TopNav.tsx                 # FR-48
│   │   │   │   │   ├── AuthGate.tsx               # OIDC-Redirect
│   │   │   │   │   └── ToastProvider.tsx          # Error-Toast (NFR-12/13)
│   │   │   │   ├── puls/
│   │   │   │   │   ├── BereichOverviewStrip.tsx   # FR-1, FR-1a
│   │   │   │   │   ├── BlockerList.tsx            # FR-2
│   │   │   │   │   ├── HeuteQuote.tsx             # FR-3
│   │   │   │   │   ├── JourfixeHint.tsx           # FR-4
│   │   │   │   │   ├── KeyMetrics.tsx             # FR-5
│   │   │   │   │   └── PflichtMinimumOverview.tsx # FR-7
│   │   │   │   ├── bereich/
│   │   │   │   │   ├── EntwicklungDetail.tsx      # FR-8 … FR-11
│   │   │   │   │   ├── VertriebDetail.tsx         # FR-12 … FR-16
│   │   │   │   │   ├── MarketingDetail.tsx        # FR-17 … FR-20
│   │   │   │   │   └── CrosslinkBanner.tsx        # FR-19
│   │   │   │   ├── projekt/
│   │   │   │   │   ├── ProjektCard.tsx            # FR-9
│   │   │   │   │   ├── ProjektDetail.tsx          # FR-10
│   │   │   │   │   ├── MeilensteinChecklist.tsx   # FR-11
│   │   │   │   │   ├── PflichtMinimumPanel.tsx    # FR-38 - FR-40
│   │   │   │   │   ├── DeadlineProgressBar.tsx
│   │   │   │   │   └── AmpelTrendBadge.tsx        # FR-9 Ampel + Trend
│   │   │   │   ├── deal/
│   │   │   │   │   ├── DealKanban.tsx             # FR-12
│   │   │   │   │   ├── DealCard.tsx               # FR-13
│   │   │   │   │   └── VertriebKpiHeader.tsx      # FR-14
│   │   │   │   ├── gtm/
│   │   │   │   │   └── GtmStepList.tsx            # FR-15
│   │   │   │   ├── campaign/
│   │   │   │   │   ├── LeadFunnel.tsx             # FR-17
│   │   │   │   │   └── CampaignList.tsx           # FR-18
│   │   │   │   ├── day-entry/
│   │   │   │   │   ├── CheckInButton.tsx          # FR-21
│   │   │   │   │   ├── TodoEditor.tsx             # FR-22, FR-25
│   │   │   │   │   ├── ReflectionForm.tsx         # FR-26
│   │   │   │   │   ├── ConsolidatedDayEntry.tsx   # FR-29 (Vortags-Eintrag)
│   │   │   │   │   └── DayStatusBadge.tsx         # FR-33
│   │   │   │   ├── streak/
│   │   │   │   │   ├── StreakBadge.tsx            # FR-34
│   │   │   │   │   ├── FehltHeuteList.tsx         # FR-35
│   │   │   │   │   └── AbwesendDialog.tsx         # FR-37
│   │   │   │   ├── blocker/
│   │   │   │   │   ├── BlockerCard.tsx            # FR-43
│   │   │   │   │   ├── BlockerCreateDialog.tsx    # FR-41
│   │   │   │   │   └── BlockerResolveDialog.tsx   # FR-44 (nur P1)
│   │   │   │   ├── abschluss/
│   │   │   │   │   ├── ProjektAbschlussDialog.tsx # FR-45, FR-46
│   │   │   │   │   └── ArchivList.tsx             # FR-47
│   │   │   │   └── ui/                            # Primitive Building-Blocks
│   │   │   │       ├── Button.tsx
│   │   │   │       ├── Card.tsx
│   │   │   │       ├── SkeletonCard.tsx
│   │   │   │       ├── Dialog.tsx
│   │   │   │       ├── Tooltip.tsx
│   │   │   │       └── Form/                      # React Hook Form Wrappers
│   │   │   ├── hooks/
│   │   │   │   ├── useCurrentUser.ts
│   │   │   │   ├── useTodaysEntry.ts
│   │   │   │   └── useToast.ts
│   │   │   └── e2e/                               # Playwright-Specs (UJ-1, UJ-2, UJ-5)
│   │   │       ├── uj-1-tagesrhythmus.spec.ts
│   │   │       ├── uj-2-jourfixe.spec.ts
│   │   │       └── uj-5-projekt-anlegen.spec.ts
│   ├── api/                          # ───── BACKEND (Fastify) ─────
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── Dockerfile                # Multi-Stage Node 22+ + Cron
│   │   ├── crontab                   # System-Cron-Definitionen (FR-29, Streak, Pflicht-Min-5)
│   │   ├── src/
│   │   │   ├── server.ts             # Fastify-App-Bootstrap
│   │   │   ├── env.ts                # Zod-validierte Env-Vars
│   │   │   ├── trpc.ts               # createTRPCBuilder + Context
│   │   │   ├── context.ts            # Per-Request-Context (user, db, req)
│   │   │   ├── auth/
│   │   │   │   ├── oidc.ts           # openid-client Setup, Authentik
│   │   │   │   ├── session.ts        # Session-Store in Postgres
│   │   │   │   ├── login-route.ts    # GET /api/auth/login, /callback, /logout
│   │   │   │   └── middleware.ts     # session-cookie → ctx.user
│   │   │   ├── procedures/
│   │   │   │   ├── public.ts                 # publicProcedure
│   │   │   │   ├── protected.ts              # protectedProcedure
│   │   │   │   ├── day-entry-visible.ts      # dayEntryVisibleProcedure (Sichtbarkeitsfilter)
│   │   │   │   └── project-owner.ts          # projectOwnerProcedure (P1-Check)
│   │   │   ├── middleware/
│   │   │   │   └── with-audit.ts             # Audit-Wrapper für Mutations
│   │   │   ├── routers/                      # tRPC-Router pro Feature
│   │   │   │   ├── _app.ts                   # appRouter = mergeRouters(...)
│   │   │   │   ├── user.ts                   # user.me, user.list, user.markAbsent
│   │   │   │   ├── project.ts                # FR-8 … FR-11, FR-45, FR-47
│   │   │   │   ├── milestone.ts              # FR-11
│   │   │   │   ├── deal.ts                   # FR-12 … FR-14
│   │   │   │   ├── gtm.ts                    # FR-15
│   │   │   │   ├── campaign.ts               # FR-17 … FR-18
│   │   │   │   ├── crosslink.ts              # FR-19
│   │   │   │   ├── day-entry.ts              # FR-21 … FR-33
│   │   │   │   ├── todo.ts                   # FR-22 … FR-25
│   │   │   │   ├── streak.ts                 # FR-34, FR-35
│   │   │   │   ├── blocker.ts                # FR-41 … FR-44
│   │   │   │   ├── pflicht-minimum.ts        # FR-38 … FR-40
│   │   │   │   └── puls.ts                   # FR-1 … FR-7 Aggregate
│   │   │   ├── cron/                         # Background-Jobs (POST /internal/cron/*)
│   │   │   │   ├── publish-day-entries.ts    # FR-29
│   │   │   │   ├── recompute-pflicht-min-5.ts# FR-38 Punkt 5
│   │   │   │   ├── recompute-streaks.ts      # FR-34
│   │   │   │   └── pg-dump-backup.ts         # NFR-15
│   │   │   ├── lib/
│   │   │   │   ├── werktag.ts                # Werktags-Logik (Mo-Fr Europe/Berlin)
│   │   │   │   ├── ampel.ts                  # Ampel-Aggregations-Algorithmus (FR-1a)
│   │   │   │   ├── pflicht-minimum.ts        # Pflicht-Minimum-Berechnung
│   │   │   │   └── logger.ts                 # pino-Instanz
│   │   │   └── health.ts                     # GET /api/health
│   │   └── test/
│   │       ├── fixtures/                     # Test-User, Test-Projekte
│   │       └── helpers/                      # testcontainers-Postgres
├── packages/
│   ├── db/                           # ───── DRIZZLE-SCHEMA & MIGRATIONEN ─────
│   │   ├── package.json
│   │   ├── tsconfig.json
│   │   ├── drizzle.config.ts
│   │   ├── schema.ts                 # ALLE Tabellen (siehe Addendum §1.2)
│   │   ├── seed.ts                   # Test-Daten (5 User, 3 Projekte/Bereich)
│   │   ├── migrations/               # drizzle-kit generierte SQL-Files
│   │   │   ├── 0000_init.sql
│   │   │   └── meta/
│   │   └── client.ts                 # createDbClient(connStr) – verwendet von api
│   └── shared/                       # ───── SHARED TYPES & SCHEMAS ─────
│       ├── package.json
│       ├── tsconfig.json
│       └── src/
│           ├── index.ts              # Re-exports
│           ├── schemas/              # Zod-Schemas (Backend + Frontend)
│           │   ├── user.ts
│           │   ├── project.ts
│           │   ├── milestone.ts
│           │   ├── deal.ts
│           │   ├── campaign.ts
│           │   ├── day-entry.ts
│           │   ├── todo.ts
│           │   ├── blocker.ts
│           │   └── pflicht-minimum.ts
│           ├── enums.ts              # Ampel, Trend, Stage, Status …
│           ├── constants.ts          # MAX_TODO_TEXT_LENGTH, JOURFIXE_DAY etc.
│           └── types/
│               └── app-router.ts     # export type AppRouter (re-exported from api)
└── docs/                             # Optionale Devs-Dokus, nicht User-facing
    ├── operations.md                 # Backup-Drill, Restore-Verfahren, Cron-Übersicht
    └── adr/                          # Architecture Decision Records, später optional

Architectural Boundaries

API Boundary

  • Externe API: keine. Cockpit ist intern.
  • Interne API (Frontend ↔ Backend):
    • Auth-Routes: klassisches REST: GET /api/auth/login (Redirect zu Authentik), GET /api/auth/callback, POST /api/auth/logout, GET /api/health.
    • Geschäfts-API: ausschließlich tRPC über POST /api/trpc/<procedurePath>.
    • Cron-Trigger: POST /internal/cron/<job> mit Bearer-Token-Header (nur über internes Netz erreichbar; Caddy blockt externen Zugriff auf /internal/*).
  • Datenbank-Zugriff: ausschließlich aus apps/api. apps/web greift niemals direkt auf die DB zu.

Component Boundary (Frontend)

  • Page-Components (routes/) holen ihre Daten via tRPC + React Query. Sie übergeben die Daten an Feature-Components (components/<feature>/).
  • Feature-Components sind nicht-routing-bewusst, nehmen Props, geben Callbacks zurück. Sie nutzen ui/-Primitives und domain-spezifische Hooks.
  • UI-Primitives (components/ui/) sind atomar, kennen weder Domain noch tRPC.
  • Hooks (hooks/) kapseln wiederverwendbare Logik (z. B. useTodaysEntry ruft mehrere tRPC-Procedures und merged sie).
  • Context ist Notfall, nicht Standard. Nur für user, theme, toast.

Service Boundary (Backend)

  • Router-Schicht (routers/<feature>.ts) übersetzt nur zwischen tRPC-Input und Domain-Logik. Keine Geschäftslogik.
  • Domain-Logik lebt in lib/<topic>.ts (werktag.ts, ampel.ts, pflicht-minimum.ts).
  • Datenzugriff über Drizzle-Client direkt im Router/Lib — kein separates Repository-Pattern (Overkill).
  • Audit-Spur via withAudit(...)-Middleware um jede Mutation. Schreibt parallel zur Mutation in derselben Transaktion in audit_event.

Data Boundary

  • Schema in packages/db/schema.ts ist Single Source of Truth.
  • Drizzle-Typen werden automatisch im API verwendet.
  • Frontend kennt keine DB-Typen, nur tRPC-Output-Typen (via Drizzle inferierte TS-Typen, die durch tRPC + Zod laufen).
  • Migrationen sind die Schnittstelle für Schema-Änderungen. Hand-Manipulation der DB außerhalb von Migrationen verboten.

Requirements to Structure Mapping

Feature-Gruppe (PRD) FRs Frontend Backend
A: Unternehmens-Puls FR-1 … FR-7 routes/puls.tsx, components/puls/ routers/puls.ts, lib/ampel.ts
B: Bereich Entwicklung FR-8 … FR-11 components/bereich/EntwicklungDetail.tsx, components/projekt/ routers/project.ts, routers/milestone.ts
C: Bereich Vertrieb FR-12 … FR-16 components/bereich/VertriebDetail.tsx, components/deal/, components/gtm/ routers/deal.ts, routers/gtm.ts
D: Bereich Marketing FR-17 … FR-20 components/bereich/MarketingDetail.tsx, components/campaign/ routers/campaign.ts, routers/crosslink.ts
E: Tagesrhythmus FR-21 … FR-33 routes/mein-heute.tsx, routes/reflexion.tsx, components/day-entry/ routers/day-entry.ts, routers/todo.ts
F: Streak FR-34 … FR-37 components/streak/ routers/streak.ts, cron/recompute-streaks.ts
G: Pflicht-Minimum FR-38 … FR-40 components/projekt/PflichtMinimumPanel.tsx routers/pflicht-minimum.ts, lib/pflicht-minimum.ts, cron/recompute-pflicht-min-5.ts
H: Blocker FR-41 … FR-44 components/blocker/ routers/blocker.ts, procedures/project-owner.ts
I: Projekt-Abschluss FR-45 … FR-47 components/abschluss/, routes/archiv.tsx routers/project.ts (archive-Procedures)
J: Navigation FR-48 … FR-49 components/layout/TopNav.tsx, lib/router.tsx —

Cross-Cutting Concerns Mapping

Concern Frontend Backend
Auth (Authentik OIDC) context/user-context.tsx, components/layout/AuthGate.tsx auth/oidc.ts, auth/session.ts, auth/middleware.ts
Audit-Trail — (nicht UI-relevant) middleware/with-audit.ts
Werktags-/Zeit-Logik nur Anzeige (lib/format.ts) lib/werktag.ts (ist die Wahrheit)
Sichtbarkeit DayEntry — (Frontend zeigt, was API liefert) procedures/day-entry-visible.ts
Voice & Tone lib/voice.ts, alle UI-Komponenten TRPCError-Messages in Routern (deutsch)
Brand-Theme tailwind.config.ts, styles/globals.css —

Integration Points

Internal Communication

  • Frontend ↔ Backend: tRPC über HTTPS (Same-Origin via Caddy).
  • Backend ↔ Postgres: Drizzle-Client über Connection-Pool (pg).
  • Cron ↔ Backend: Lokaler HTTP-Call vom Cron-Daemon im selben Container (oder Host-Cron) an localhost:3000/internal/cron/* mit Bearer-Token.

External Integrations (MVP)

  • Authentik (OIDC): auth/oidc.ts führt Authorization Code Flow + PKCE durch. Discovery-Endpoint https://auth.tool-ctrl.de/application/o/cockpit/.well-known/openid-configuration.
  • Plane/Outline: nur als URL-Referenzen. Keine API-Calls im MVP (PRD §8). Phase-2-Hook: lib/plane-client.ts, lib/outline-client.ts werden in Phase 2 ergänzt, ohne Schema-Änderung.

Data Flow (Hot-Path: Tagesrhythmus)

Browser ── Click "Einstempeln" ──▶ tRPC: dayEntry.checkIn
                                      │
                                      ▼
                                Fastify routers/day-entry.ts
                                      │
                            (protectedProcedure)
                                      │
                                      ▼
                              withAudit wrapper
                                      │
                                      ▼
                          DB-Transaktion (Drizzle):
                          INSERT DayEntry, INSERT Todos[], INSERT audit_event
                                      │
                                      ▼
                              Response ──▶ React Query Cache-Invalidierung
                                          ──▶ UI rerender (Skeleton → echte Daten)

File Organization Patterns

  • Configuration im Root: tsconfig.base.json, .eslintrc.cjs, prettier.config.js, tailwind.config.ts, drizzle.config.ts. Pro App nur Overrides (extends Root).
  • Environment: .env.example im Root als Master-Template; jede App liest die für sie relevanten Variablen. Validation via Zod in apps/api/src/env.ts und apps/web/src/env.ts (Vite import.meta.env).
  • Tests neben Code: *.test.ts(x) für Unit. E2E zentral in apps/web/e2e/.
  • Static Assets: apps/web/public/ (Vite-Standard). Brand-Logos hier.

Development Workflow Integration

  • Lokaler Dev-Server:
    • pnpm --filter web dev → Vite auf :5173.
    • pnpm --filter api dev → Fastify + tsx watch auf :3000.
    • Postgres via docker compose -f docker-compose.dev.yml up postgres.
    • Vite-Proxy schickt /api/* an :3000.
  • Build:
    • pnpm --filter web build → apps/web/dist/ (static).
    • pnpm --filter api build → apps/api/dist/ (Node-Server-Bundle).
  • Deployment:
    • docker compose up -d --build baut alle 3 Container und startet sie.
    • Caddy serviert apps/web/dist/ statisch und reverse-proxyt /api/* + /trpc/* an api:3000.
    • Postgres als persistenter Container mit eigenem Volume.

Architecture Validation Results

Coherence Validation ✅

Decision Compatibility: Vite 5+ und React 18+ bilden ein etabliertes SPA-Fundament; Fastify 5+ trägt Node-22-Workloads; tRPC v11 hat einen offiziellen Fastify-Adapter; Drizzle ORM ist Postgres-nativ; openid-client v6+ ist die Referenz-Library für OIDC-Flows und arbeitet sauber mit Authentik. Alle Versionen sind 2026 stabil und parallel im Ökosystem ausgereift.

Pattern Consistency: Die Naming-Konventionen ziehen sich durch DB (snake_case), Backend (camelCase tRPC-Procedures, deutsche Domain-Begriffe in Feldnamen wo sinnvoll), Frontend (PascalCase-Komponenten, kebab-case-Routen mit deutschen Domain-Pfaden). Error-Handling-Pattern (TRPCError + zentraler Toast) ist im Code-Stack durchsetzbar. State-Management-Pattern (React Query + Context + lokal) ist konsistent.

Structure Alignment: Die Monorepo-Struktur unterstützt die Stack-Wahl (pnpm-Workspaces sind 2026-Standard für Node-Monorepos). packages/shared ist Pflicht für End-to-End-Typsicherheit von tRPC. packages/db ist Single Source of Truth — Backend importiert, Frontend nicht. Boundaries sind technisch erzwingbar via TS-Paths und ESLint-Regeln.

Requirements Coverage Validation ✅

Functional Requirements (49 FRs): Alle 49 FRs sind in der Mapping-Tabelle (§ Project Structure → Requirements to Structure Mapping) konkret auf Frontend-Komponenten und Backend-Router gemappt. Keine FR ohne Heimat.

Stichproben:

  • FR-1a (Ampel-Aggregation-Algorithmus) → apps/api/src/lib/ampel.ts (Domain-Logik), aufgerufen aus routers/puls.ts.
  • FR-26 (Reflexions-Formular) → apps/web/src/components/day-entry/ReflectionForm.tsx + routers/day-entry.ts:submitReflection.
  • FR-29 (Publikation am nächsten Werktagsmorgen) → apps/api/src/cron/publish-day-entries.ts + System-Cron-Definition in crontab.
  • FR-38–40 (Pflicht-Minimum-Check) → routers/pflicht-minimum.ts + lib/pflicht-minimum.ts (Backend), components/projekt/PflichtMinimumPanel.tsx (Frontend).
  • FR-44 (nur P1 löst Blocker) → procedures/project-owner.ts (projectOwnerProcedure) wrapt die Blocker-Resolve-Mutation.

Non-Functional Requirements (16 NFRs):

NFR Architektur-Antwort Status
NFR-1 (Puls < 1,5 s) Server-Komponenten-frei (SPA), tRPC + React Query, Postgres-Indizes auf day_entry(date, user_id), project(area_id, archiviert) ✅ Plan adressiert
NFR-2 (Save < 500 ms) Single tRPC-Roundtrip, In-Memory Postgres-Connection-Pool, kein externer Dienst im Hot-Path ✅ Plan adressiert
NFR-3 (99 % Mo-Fr 8–19) Docker-Healthcheck + Caddy-Restart-Policy, pg-dump-backup ✅
NFR-4 (Wartungsfenster) manuelle docker compose down/up, gating via Caddy ✅
NFR-5 (Authentik OIDC) auth/oidc.ts mit openid-client v6+, Code-Flow + PKCE ✅
NFR-6 (keine externe PII) Datenmodell enthält nur intern bekannte Felder ✅ Konzeptuell adressiert
NFR-7 (Audit-Trail) withAudit-Middleware, audit_event-Tabelle ✅
NFR-8 (Brand) Tailwind-Theme-Tokens, finale Werte aus UX-Phase ⚠️ verweist auf OQ-2
NFR-9 (≤ 3 Klicks ab Puls zum Update) routes/mein-heute.tsx ist Direktlink in TopNav (FR-48) ✅
NFR-10 (Desktop-first, mobil-lesbar) Tailwind Responsive Utility-Classes, kein Custom-Mobile-Layout im MVP ✅
NFR-11 (Deutsch durchgängig) lib/voice.ts zentralisiert UI-Strings, keine i18n-Lib ✅
NFR-12/13 (Voice & Tone) lib/voice.ts + Error-Messages deutsch in Routern; bindend per CONVENTIONS.md ✅
NFR-14 (Postgres) Postgres 16+, Drizzle-Adapter ✅
NFR-15 (Daily Backup) cron/pg-dump-backup.ts täglich 02:00 ✅
NFR-16 (≤ 2 h Migration) Drizzle migrations sind klein, generierte SQL-Files, lokal testbar ✅

Implementation Readiness Validation ✅

Decision Completeness: Alle kritischen Entscheidungen sind mit Tech-Stack-Wahl, Versions-Hinweis und konkretem Library-Pfad (Bsp. openid-client v6+, Fastify 5+, Drizzle ORM) dokumentiert. Background-Job-Mechanik festgelegt (System-Cron → Bearer-geschützter Endpoint). Hosting festgelegt (ToolCTRL-interne Infra + Caddy).

Structure Completeness: Project-Tree umfasst alle Apps (apps/web, apps/api), alle Packages (db, shared), Konfig-Dateien, Cron-Container-Skripte, Caddyfile, docker-compose. Pro FR-Gruppe ist ein konkretes Verzeichnis benannt.

Pattern Completeness: Naming-, Struktur-, Format-, Kommunikations- und Prozess-Pattern sind in CONVENTIONS.md reflektiert. Anti-Pattern sind explizit aufgelistet.

Gap Analysis Results

Critical Gaps: keine. Die Architektur ist implementierungsbereit.

Important Gaps (sollten vor MVP-Bau geschlossen werden):

  1. OQ-1 Authentik-Group → Bereich-Mapping: Default-Strategie wird in der ersten Architektur-Story (auth/oidc.ts) entschieden. Vorschlag: ein AUTHENTIK_GROUP_TO_AREA-Mapping in der .env (z. B. entwicklung:dev,vertrieb:sales,marketing:marketing); Fallback auf allgemein, User kann im Profil manuell wechseln. Nicht-Blocker, in 1 Story lösbar.
  2. OQ-5 Archivierungs-Policy: PRD-Default jetzt fixiert → Soft-Delete via archiviert-Flag + archiviert_am-Zeitstempel; keine automatische Löschung; manuell auf Anfrage durch DB-Admin. Aufnahme in docs/operations.md bei MVP-Launch.
  3. OQ-2 Brand-Tokens: kommen aus bmad-create-ux-design (Folge-Skill). Tailwind-Config wird vor dem Story-Bau für Theme befüllt.

Nice-to-have Gaps (Post-MVP):

  1. Zentrale Log-Aggregation (Loki/Grafana) — sinnvoll, sobald > 3 Container-Instanzen.
  2. Error-Tracking (Sentry oder gleichwertig) — Post-MVP.
  3. CSP-Headers im Caddyfile feinjustieren — vor Production-Launch.
  4. Migrations-Rollback-Strategie: Drizzle hat kein eingebautes Rollback; Operational-Notes festhalten („immer Rollback-Migration als nächste Migration schreiben").
  5. Postgres-Restore-Drill — quartalsweise (steht in operations.md).

Validation Issues Addressed

  • OQ-5 Archivierungs-Policy — in der Validation auf Soft-Delete via archiviert-Flag fixiert, da das Schema-konform und am wenigsten destruktiv ist.

Architecture Completeness Checklist

Requirements Analysis

  • Project context thoroughly analyzed
  • Scale and complexity assessed
  • Technical constraints identified
  • Cross-cutting concerns mapped

Architectural Decisions

  • Critical decisions documented with versions
  • Technology stack fully specified
  • Integration patterns defined
  • Performance considerations addressed

Implementation Patterns

  • Naming conventions established
  • Structure patterns defined
  • Communication patterns specified
  • Process patterns documented

Project Structure

  • Complete directory structure defined
  • Component boundaries established
  • Integration points mapped
  • Requirements to structure mapping complete

Architecture Readiness Assessment

Overall Status: READY FOR IMPLEMENTATION

Alle 16 Checklist-Items sind erfüllt. Keine kritischen Gaps. Drei Important Gaps (OQ-1, OQ-5, OQ-2) sind entweder bereits in dieser Validierung mit einem Default belegt (OQ-5) oder werden im Lauf der ersten Stories bzw. in der folgenden UX-Phase aufgelöst (OQ-1, OQ-2) und sind kein Blocker für Story-Schneidung.

Confidence Level: hoch. Die Architektur entspricht einer ausgereiften, im Ökosystem 2026 gut belegten Schnitt-Strategie für interne Tools dieser Größe.

Key Strengths:

  • End-to-End-Typsicherheit (Drizzle → tRPC → React Query) reduziert Klassen von Bugs.
  • Klar getrennte Wahrheits-Ebenen — Plane (Arbeit), Outline (Gedächtnis), Cockpit (Übersicht) — werden architektonisch nicht verletzt.
  • Pflicht-Minimum-Check als Erstklass-Feature ist sauber gekapselt (eigene Lib + eigener Router + eigener Cron).
  • Sichtbarkeitslogik der DayEntries hat eine eigene Procedure (dayEntryVisibleProcedure), wodurch die Privacy-Regel nicht als Anti-Muster über die Codebase verteilt wird.

Areas for Future Enhancement:

  • Plane- und Outline-API-Integration (Phase 2) ist im Schema vorbereitet, aber nicht implementiert.
  • Mobile-schreibbar (Phase 2).
  • Reaktionen/Kommentare auf Tagesupdates (Phase 2).
  • Reminders per Element/Matrix/Mail (Phase 2).
  • Volltextsuche (FR-49, Phase 2).

Implementation Handoff

AI Agent Guidelines:

  • Folge allen Entscheidungen in diesem Dokument exakt.
  • Nutze die Patterns in CONVENTIONS.md konsequent — Anti-Patterns sind verboten.
  • Respektiere die Boundaries (apps/web kennt die DB nicht; nur tRPC).
  • Bei Unsicherheit: PRD ist die Quelle der Wahrheit für was, dieses Dokument für wie.

First Implementation Priority:

# Story 1: Repo-Bootstrap
mkdir cockpit && cd cockpit
pnpm init
mkdir -p apps/web apps/api packages/db packages/shared
echo 'packages:\n  - "apps/*"\n  - "packages/*"' > pnpm-workspace.yaml
# Frontend bootstrappen
cd apps/web && pnpm create vite . --template react-ts
# Backend, packages/db, packages/shared per Hand (s. Initialization Commands oben)

Story-Sequenz im Detail siehe Sektion „Decision Impact Analysis → Implementation Sequence" weiter oben.