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>
1002 lines
59 KiB
Markdown
1002 lines
59 KiB
Markdown
---
|
||
stepsCompleted: [1, 2, 3, 4, 5, 6, 7, 8]
|
||
lastStep: 8
|
||
status: 'complete'
|
||
completedAt: '2026-05-25'
|
||
inputDocuments:
|
||
- 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
|
||
workflowType: 'architecture'
|
||
project_name: 'projectmanagement'
|
||
user_name: 'Dejancosic'
|
||
date: '2026-05-25'
|
||
binding_frame: 'ToolCTRL Projektmanagement-Leitfaden v3.0 (Mai 2026)'
|
||
prd_status: '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
|
||
|
||
```bash
|
||
# 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 `code`s:
|
||
- `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:**
|
||
|
||
```ts
|
||
// 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):**
|
||
|
||
```ts
|
||
// ❌ 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**
|
||
|
||
- [x] Project context thoroughly analyzed
|
||
- [x] Scale and complexity assessed
|
||
- [x] Technical constraints identified
|
||
- [x] Cross-cutting concerns mapped
|
||
|
||
**Architectural Decisions**
|
||
|
||
- [x] Critical decisions documented with versions
|
||
- [x] Technology stack fully specified
|
||
- [x] Integration patterns defined
|
||
- [x] Performance considerations addressed
|
||
|
||
**Implementation Patterns**
|
||
|
||
- [x] Naming conventions established
|
||
- [x] Structure patterns defined
|
||
- [x] Communication patterns specified
|
||
- [x] Process patterns documented
|
||
|
||
**Project Structure**
|
||
|
||
- [x] Complete directory structure defined
|
||
- [x] Component boundaries established
|
||
- [x] Integration points mapped
|
||
- [x] 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:**
|
||
|
||
```bash
|
||
# 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.
|