commit 4f33509a0f84abe1357ccef4d03b48833da2a02f Author: DeeCee03 Date: Mon May 25 22:47:26 2026 +0200 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) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..4039ff1 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a0dc663 --- /dev/null +++ b/.env.example @@ -0,0 +1,24 @@ +# --- Postgres --- +DATABASE_URL=postgres://cockpit:cockpit_dev@localhost:55432/cockpit + +# --- Authentik OIDC (Story 1.3) --- +# Anlage als „Provider" und „Application" in Authentik unter https://auth.tool-ctrl.de +# → siehe docs/operations.md (kommt in Story 1.7) +AUTHENTIK_ISSUER=https://auth.tool-ctrl.de/application/o/cockpit/ +AUTHENTIK_CLIENT_ID= +AUTHENTIK_CLIENT_SECRET= +AUTHENTIK_REDIRECT_URI=http://localhost:3000/api/auth/callback + +# --- Session --- +SESSION_COOKIE_NAME=cockpit_session +SESSION_TTL_HOURS=24 +SESSION_COOKIE_SECRET=please-change-me-in-prod-at-least-32-chars + +# --- Cron Bearer (Story 2.6 + 5.1 + 6.3) --- +CRON_BEARER_TOKEN=please-change-me-in-prod + +# --- App --- +NODE_ENV=development +LOG_LEVEL=info +PORT=3000 +PUBLIC_BASE_URL=http://localhost:5173 diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..0210001 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,47 @@ +# Gitea Actions CI für das Cockpit-Repo. +# Läuft bei Push und PR. + +name: ci + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - uses: pnpm/action-setup@v4 + with: + version: 10.28.0 + - name: Install + run: pnpm install --frozen-lockfile + - name: Typecheck + run: pnpm typecheck + - name: Lint + run: pnpm lint + - name: Format check + run: pnpm format:check + - name: Test + run: pnpm test + + build: + runs-on: ubuntu-latest + needs: quality + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + - uses: pnpm/action-setup@v4 + with: + version: 10.28.0 + - run: pnpm install --frozen-lockfile + - run: pnpm --filter web build + - run: pnpm --filter api build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..15fd1a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Dependencies +node_modules/ +.pnpm-store/ + +# Build outputs +dist/ +build/ +*.tsbuildinfo + +# Environment +.env +.env.local +.env.*.local + +# Logs +logs/ +*.log +npm-debug.log* +pnpm-debug.log* + +# Editor / OS +.DS_Store +.idea/ +.vscode/* +!.vscode/extensions.json +!.vscode/settings.json + +# Testing +coverage/ +playwright-report/ +test-results/ + +# Database +*.sqlite +*.sqlite-journal + +# Misc +.cache/ +.turbo/ + +# Brand-Assets (binär, im Repo Pull-Quelle: original Designer-Übergabe) +ToolCTRL Brand Identity/ + +# Lokale Claude-Code-Konfiguration +.claude/ + +# Lokale Hilfs-Dateien +/tmp-seed*.sql diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ffd6dfb --- /dev/null +++ b/.prettierignore @@ -0,0 +1,11 @@ +node_modules +dist +build +.next +coverage +pnpm-lock.yaml +_bmad +_bmad-output +.claude +ToolCTRL Brand Identity +pnpm-workspace.yaml diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..f800d4e --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 100, + "tabWidth": 2, + "arrowParens": "always", + "endOfLine": "lf" +} diff --git a/CONVENTIONS.md b/CONVENTIONS.md new file mode 100644 index 0000000..e14ac6b --- /dev/null +++ b/CONVENTIONS.md @@ -0,0 +1,103 @@ +# CONVENTIONS — ToolCTRL everythingbutwork + +> **Bindend** für alle Code-erzeugenden Agenten und Menschen. Spiegelt Architektur-Spec § Implementation Patterns. Pflicht-Lesen vor jeder Story. + +## Naming + +### Datenbank (Drizzle + Postgres) + +- Tabellen: Singular, `snake_case` (englisch) — `user`, `project`, `day_entry`, `todo`. Domain-Begriffe in Spalten dürfen deutsch sein (`abwesend_bis`, `arbeitsmodus`). +- Primärschlüssel: immer `id` (UUID v7). +- Fremdschlüssel: `_id` — `user_id`, `project_id`. +- Indizes: `idx__`; unique: `uniq_
_`. +- Enums: TypeScript-Union in Drizzle + `text` mit `check`-Constraint in Postgres. +- Zeitstempel: `created_at`, `updated_at`, `_at` als `timestamptz`, Default `now()`. + +### tRPC Backend + +- Router-Datei: `apps/api/src/routers/.ts` — `day-entry.ts`, `project.ts`. +- Router-Variable: `Router` (camelCase) — `dayEntryRouter`. +- Procedure-Naming: `.` (camelCase) — `dayEntry.checkIn`, `dayEntry.submitReflection`, `project.archive`, `blocker.resolve`. +- Reads: `list`, `byId`, `mine`, `forProject`. +- Mutations: `create`, `update`, `archive` (statt `delete`), `resolve`, `checkIn`, `submitReflection`. +- Procedure-Inputs: **immer** Zod-Schema aus `@cockpit/shared/schemas/`. + +### Frontend (React) + +- Komponenten: PascalCase. Dateiname matched (`DayEntryForm.tsx`). +- Hooks: `use` camelCase. +- Konstanten: `SCREAMING_SNAKE_CASE`. +- Route-Pfade: kebab-case, deutsch wo Domain — `/puls`, `/mein-heute`, `/bereich/entwicklung`. +- Komponenten-Organisation: **nach Feature**, nicht nach Typ. + +## Struktur + +- Tests **neben** dem Code: `Foo.tsx` ↔ `Foo.test.tsx` (Backend: `foo.ts` ↔ `foo.test.ts`). +- E2E: zentral in `apps/web/e2e/.spec.ts`. +- Shared zwischen Frontend + Backend → `packages/shared/`. Backend-only Helper → `apps/api/src/lib/`. +- Konfig-Dateien im Repo-Root, pro App nur Overrides. + +## Format + +- JSON-Felder über die Wire: `camelCase` (Drizzle mapt DB-`snake_case` → TS-`camelCase`). +- Datumsformat: **ISO 8601 mit Zeitzone**. Reine Werktage `YYYY-MM-DD` (Typ `date`). +- Geldbeträge: Cent als `integer` in DB; Frontend formatiert via `Intl.NumberFormat('de-DE')`. +- Booleans: echtes `boolean`. Nie `0/1` oder `"yes"/"no"`. +- Null vs. Undefined: DB nutzt `null`. Code: `null` = explizit nicht gesetzt, `undefined` = existiert nicht. +- IDs in URL: UUIDs (`/projekt/:id`), keine numerischen IDs öffentlich. + +## Errors + +- Backend: `TRPCError` mit klaren Codes — `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BAD_REQUEST`, `INTERNAL_SERVER_ERROR`. +- Message: **immer deutsch**, menschenlesbar. Nicht „NULL constraint violated" sondern „Bitte wähle einen Verantwortlichen aus". +- Frontend: zentraler `onError`-Hook in tRPC-Provider zeigt `error.message` als Toast. +- Logging: `INTERNAL_SERVER_ERROR` mit Stack → `pino.error`. `BAD_REQUEST` → `pino.info`. + +## State (Frontend) + +- Server-State: **ausschließlich React Query** via tRPC (`isPending`, `isError`, `data`). **Nie** eigene `useState`-Booleans für Server-Loading. +- Loading: Skeleton-Komponenten, keine Spinner-Only. +- Optimistic Updates für Reflexion, Todo-Toggle, Blocker-Auflösung. Sonst nicht. +- Lokaler UI-State: `useState`/`useReducer` in der Komponente. Hochziehen, wenn Sibling-Sharing nötig — kein Context. + +## Audit & Authorization + +- Mutations: `withAudit()`-Middleware um jede schreibende Procedure. Schreibt `audit_event` in derselben Transaktion. +- AuditEvent-Naming: `.` (lowercase, dot-separated) — `day_entry.reflection_submitted`, `blocker.resolved`. +- Procedure-Typen: `publicProcedure`, `protectedProcedure`, `dayEntryVisibleProcedure`, `projectOwnerProcedure`. + +## Werktag / Zeit + +- Server-Zeit: **`Europe/Berlin`**. +- Werktage: Mo-Fr (MVP, ohne Feiertage). +- **Alle Datumslogik im Backend** — Frontend bekommt fertige Felder (`isWorkday`, `nextWorkday`, `streakDays`). + +## Voice & Tone + +- UI-Strings zentral in `apps/web/src/lib/voice.ts`. +- Tonalität: locker, direkt, deutsch, kurze Imperative — „Stark.", „Wie läuft es?", „Du hast heute noch nicht gepingt.". +- Keine Corporate-Sprech, keine englischen Floskeln (Fachbegriffe aus Leitfaden erlaubt). +- Error-Messages im Backend ebenfalls deutsch. + +## Tests + +- Unit & Procedures: **Vitest**. +- E2E: **Playwright** auf UJ-1, UJ-2, UJ-5 (CI nur auf Tag/Release). +- A11y: `axe-core` in Vitest pro Komponente, `@axe-core/playwright` für E2E, Lighthouse-A11y ≥ 95 im CI. +- Tests neben Code (`*.test.ts(x)`). + +## DB-Änderungen + +- **Immer** Drizzle-Migration committen — kein Schema-Drift. +- Migration entstehen je Story-Bedarf, nicht upfront. +- TypeScript strict, kein `any`. Bei unklarer Form: `unknown` + Zod-Validation. + +## Don'ts + +- � Inline-Zod im Procedure (gehört nach `@cockpit/shared/schemas`). +- � Englisches Error-Message. +- � snake_case-Komponentennamen. +- � Server-Loading via `useState`. +- � Drag-and-Drop außerhalb des Sales-Kanban. +- � Pulsing-/Spin-Animationen auf dem Puls. +- � Auto-Refresh / Background-Polling. diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..5629dac --- /dev/null +++ b/Caddyfile @@ -0,0 +1,29 @@ +# Caddy 2.x — Reverse-Proxy für das Cockpit. +# Lokal: http://localhost +# Produktion: cockpit.tool-ctrl.de + +{$COCKPIT_HOST:cockpit.tool-ctrl.de} { + encode zstd gzip + + # /internal/* darf nur über das interne Netz erreichbar sein. + @internal path /internal/* + respond @internal 403 + + # API + tRPC → Backend + @api path /api/* /trpc/* + reverse_proxy @api api:3000 + + # Static frontend + root * /srv + try_files {path} /index.html + file_server + + # Security-Headers (CSP wird in Story 6.x feinjustiert) + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains" + X-Content-Type-Options "nosniff" + Referrer-Policy "strict-origin-when-cross-origin" + Permissions-Policy "camera=(), microphone=(), geolocation=()" + -Server + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..487e50c --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# ToolCTRL everythingbutwork + +Internes Web-Tool zur täglichen Übersicht über alle Projekte und Bereiche. Ergänzt **Plane** (Tickets) und **Outline** (Doku) als drittes Werkzeug gemäß ToolCTRL Projektmanagement-Leitfaden v3.0. + +## Stack + +- **Frontend:** Vite + React 18 + TypeScript + Tailwind (kommt in Story 1.5) +- **Backend:** Fastify + tRPC v11 + Drizzle (kommt in Story 1.4 + 1.2) +- **DB:** Postgres 16+ (kommt in Story 1.2) +- **Auth:** Authentik OIDC (kommt in Story 1.3) +- **Hosting:** Docker + Caddy auf interner ToolCTRL-Infrastruktur + +## Verzeichnisstruktur + +``` +. +├── apps/ +│ ├── web/ # Vite + React SPA +│ └── api/ # Fastify Backend +├── packages/ +│ ├── db/ # Drizzle-Schema + Migrationen +│ └── shared/ # Zod-Schemas + Konstanten + Typen +├── _bmad-output/ # Planungs-Artefakte (PRD, Architektur, UX, Epics) +└── CONVENTIONS.md # Bindende Code-Konventionen (Pflicht-Lesen) +``` + +## Loslegen + +```bash +pnpm install +pnpm --filter web dev # Frontend auf http://localhost:5173 +pnpm --filter api dev # Backend auf http://localhost:3000 +pnpm typecheck # Alle Workspaces typecheken +pnpm lint # ESLint +pnpm format # Prettier +``` + +## Planungs-Artefakte + +- [PRD](./_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/prd.md) — 49 Functional Requirements, 16 NFRs +- [Architektur](./_bmad-output/planning-artifacts/architecture.md) — Stack, Schnitt, Patterns +- [UX-Spec](./_bmad-output/planning-artifacts/ux-design-specification.md) — Brand-Tokens, Komponenten, User Journeys +- [Epics & Stories](./_bmad-output/planning-artifacts/epics.md) — 6 Epics, 32 Stories +- [Konventionen](./CONVENTIONS.md) — Naming, Struktur, Formate, Errors diff --git a/_bmad-output/planning-artifacts/architecture.md b/_bmad-output/planning-artifacts/architecture.md new file mode 100644 index 0000000..c2bf705 --- /dev/null +++ b/_bmad-output/planning-artifacts/architecture.md @@ -0,0 +1,1001 @@ +--- +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()`. +- **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/` 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 `_id`. +- **Fremdschlüssel:** `_id` — `user_id`, `project_id`. Nicht `fk_user`. +- **Indizes:** `idx_
_` — `idx_day_entry_user_id`. Eindeutige: `uniq_
_`. +- **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`, `_at` (`reflected_at`, `published_at`). Type `timestamptz`. Default `now()`. + +#### tRPC (Backend-API) + +- **Router-Datei:** `apps/api/src/routers/.ts` — `day-entry.ts`, `project.ts`, `blocker.ts`. +- **Router-Variable:** `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/.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` 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//.tsx`. +- **Frontend-Routen-Komponenten:** `apps/web/src/routes/.tsx`. +- **Backend-Router:** `apps/api/src/routers/.ts`. +- **Shared Schemas:** `packages/shared/schemas/.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/.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 `` 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:** `.` (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; + +// 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() +│ │ │ │ ├── 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 # +│ │ │ ├── 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/`. + - **Cron-Trigger:** `POST /internal/cron/` 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-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/.ts`) übersetzt nur zwischen tRPC-Input und Domain-Logik. Keine Geschäftslogik. +- **Domain-Logik** lebt in `lib/.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. diff --git a/_bmad-output/planning-artifacts/epics.md b/_bmad-output/planning-artifacts/epics.md new file mode 100644 index 0000000..fbc4d59 --- /dev/null +++ b/_bmad-output/planning-artifacts/epics.md @@ -0,0 +1,966 @@ +--- +stepsCompleted: [1, 2, 3, 4] +status: 'complete' +completedAt: '2026-05-25' +inputDocuments: + - prds/prd-projectmanagement-2026-05-25/prd.md + - prds/prd-projectmanagement-2026-05-25/addendum.md + - architecture.md + - ux-design-specification.md + - prds/prd-projectmanagement-2026-05-25/.decision-log.md +workflowType: 'epics-and-stories' +project_name: 'ToolCTRL Unternehmens-Cockpit' +user_name: 'Dejancosic' +date: '2026-05-25' +--- + +# ToolCTRL Unternehmens-Cockpit - Epic Breakdown + +## Overview + +Dieses Dokument zerlegt das PRD (49 FRs, 16 NFRs), die Architektur (komplett, Vite + React + Fastify + tRPC + Drizzle + Postgres + Authentik) und die UX-Spec (15 Custom-Components, Brand-Tokens, 5 User Journeys) in implementierbare Epics und Stories. + +## Requirements Inventory + +### Functional Requirements + +> 49 FRs aus PRD §6, organisiert in 10 Feature-Gruppen (A–J). + +**A: Unternehmens-Puls (Startseite)** + +- **FR-1** Übersichtsleiste aller Areas (Entwicklung, Vertrieb, Marketing) inkl. aggregiertem Ampelstatus und Anzahl heute-fehlender Updates pro Area. +- **FR-1a** Ampel-Aggregation Algorithmus pro Area: Rot wenn ≥ 1 Projekt rot; Gelb wenn ≥ 1 Projekt gelb und kein rotes; Grün wenn alle grün; Grau wenn keine aktiven Projekte. Pflicht-Minimum-Verstöße heben Ampel mindestens auf Gelb. +- **FR-2** Bereichsübergreifende Liste aller offenen Blocker, sortiert nach Alter. +- **FR-3** Heute-Quote „X von Y Mitarbeitern haben heute gepingt" mit Klick auf Liste der fehlenden Personen. +- **FR-4** Anzeige des nächsten Jourfixe-Termins (statisch konfigurierbar, Default Di 11:00). +- **FR-5** Pro Bereich definierter Key-Metrics-Katalog: + - Entwicklung: # Projekte gesamt / im Plan / gefährdet / blockiert / # überzogene Meilensteine. + - Vertrieb: gewichteter Pipeline-Wert (Σ value × probability) / # offene Deals / gewonnene Deals (Quartal, # + EUR) / # offene GTM-Schritte. + - Marketing: Reichweite (Periode) / # aktive Kampagnen / Leads / MQLs / Übergaben an Vertrieb. +- **FR-6** Klick auf Bereich-Kachel öffnet Bereichs-Detail-View. +- **FR-7** Pflicht-Minimum-Übersicht im Puls: Liste aller aktiven Projekte mit roten/gelben Pflicht-Minimum-Punkten. + +**B: Bereichs-Detail Entwicklung** + +- **FR-8** Liste aller aktiven Projekte, gruppiert nach Projektart (Kundenprojekt / Internes Projekt — Leitfaden §1). +- **FR-9** Pro Projekt-Kachel: Name, Verantwortliche/r (P1), Ampelstatus + Trend (↗︎/→/↘︎), Deadline-Progress-Bar, # offene Meilensteine, Pflicht-Minimum-Score (z. B. „5/6"). +- **FR-10** Projekt-Detail mit Kopfzeile (Name, P1, Projektart, Arbeitsmodus, Ampel + Trend), Pflicht-Minimum-Anzeige, Verlinkungen (Outline-Steckbrief, Plane-Board, Risikoliste), optional DoD-Text, Meilenstein-Checkliste, Update-Feed. +- **FR-11** Meilensteine als grobe Cockpit-Sicht (Titel, Fälligkeit, Status: offen/erledigt/überzogen). Wahrheit bleibt im Outline-Steckbrief. + +**C: Bereichs-Detail Vertrieb** + +- **FR-12** 5-Spalten Kanban: Lead → Erstgespräch → Demo → Angebot → Abschluss, mit Drag-and-Drop. +- **FR-13** Deal-Karten: Name, Verantwortlicher, Wert (€), Stage-Wahrscheinlichkeit (0-100 %), Tage in aktueller Stage. +- **FR-14** Vertriebs-Header-KPIs: gewichteter Pipeline-Wert, gewonnene Deals (# + EUR, laufendes Quartal), aktiver Deal-Count, ⌀ Stage-Verweildauer. +- **FR-15** Pro Produkt eine sortierbare GTM-Schritt-Liste (Schritt, Verantwortlicher, Status). +- **FR-16** Vertrieb-Update-Feed (Filter Bezug = Deal oder Vertrieb-allgemein). + +**D: Bereichs-Detail Marketing** + +- **FR-17** Horizontaler Lead-Funnel mit 5 Stufen (Reichweite → Website → Leads → MQL → Übergabe) + Conversion-%. +- **FR-18** Liste aktiver Kampagnen mit Status (Aktiv / Pausiert / Beendet), Reichweite, Verantwortlicher. +- **FR-19** Crosslink-Banner Marketing↔Vertrieb: „Marketing hat X Leads übergeben in [Zeitraum], Vertrieb hat daraus Y Deals erzeugt (Z €)". +- **FR-20** Marketing-Update-Feed (Filter Bezug = Kampagne oder Marketing-allgemein). + +**E: Tagesrhythmus** + +- **FR-21** „Einstempeln"-Aktion auf Puls (großer CTA), öffnet ToDo-Eingabe für heute. +- **FR-22** ToDo-Liste: 1-N Items, pro Item Titel (max. 200 Z) + optionaler Bezug. +- **FR-23** Eingestempelt = Tag „begonnen", Person verschwindet aus „heute fehlt"-Liste. +- **FR-24** Sichtbarkeit innerhalb des Tages: nur binäre Anwesenheit für Kollegen, ToDo-Inhalte privat. P1 sieht ToDos seiner Projekte. +- **FR-25** ToDos editierbar bis zur Reflexion. +- **FR-26** Reflexions-Formular: ToDos abhaken/verwerfen (mit Grund), Freitext (max. 1000 Z), Ampel (Grün/Gelb/Rot mit Leitfaden-Semantik), Trend (Pflicht bei Bezug=Projekt), optional Blocker, optional URL. +- **FR-27** Reflexion → Tag „voll abgeschlossen", Streak +1. +- **FR-28** Reflexion 30 min editierbar; Reflexions-Erinnerung ab konfigurierbarer Zeit (Default 17:00). +- **FR-29** Konsolidierter Vortags-Eintrag wird am nächsten Werktag morgen publiziert (sichtbar für Kollegen). +- **FR-30** Feed-Sortierung: neueste oben. Pagination 50/Seite. +- **FR-31** Reflexion setzt automatisch Ampel + Trend des verlinkten Projekts/Deals/Kampagne. +- **FR-32** Rückwirkende Erfassung max. 1 Werktag zurück. +- **FR-33** Tages-Status pro Person: 🟢 voll / 🟡 halb / ⚪ nicht angefangen / 🟣 abwesend. + +**F: Streak / Anwesenheit / Disziplin** + +- **FR-34** Pro Person aktuelle Streak (aufeinanderfolgende „volle" Werktage). +- **FR-35** „Heute fehlt"-Liste auf Puls — nach 11:00 für nicht-eingestempelte, nach 17:00 für „halb"-Status. +- **FR-36** Werktage = Mo-Fr (Feiertage Phase 2). +- **FR-37** Person markiert sich für Tag/Zeitraum als „Abwesend" (Urlaub/Krankheit), zählt nicht gegen Streak. + +**G: Pflicht-Minimum-Check (Leitfaden §9)** + +- **FR-38** Pro aktivem Projekt 6-Punkte-Checkliste sichtbar: + 1. Projektsteckbrief in Outline freigegeben → erfüllt wenn `outline_steckbrief_url` gepflegt UND als „freigegeben" markiert. + 2. P1 gesetzt. + 3. Plane-Board mit zerlegter Arbeit → erfüllt wenn `plane_project_url` gepflegt. + 4. Meilensteine mit groben Terminen → erfüllt wenn ≥ 1 Meilenstein. + 5. Status-Ampel aktuell → erfüllt wenn letzte Reflexion mit Projektbezug ≤ 7 Werktage alt. + 6. Abschluss-Retrospektive (nur bei beendeten Projekten). +- **FR-39** MVP: Punkte 1, 2, 3, 4 manuell als Cockpit-Checkbox; Punkt 5 automatisch aus Reflexions-Zeitstempel; Punkt 6 im Abschluss-Workflow. +- **FR-40** Projekte mit unerfülltem Pflicht-Punkt prominent auf Puls. + +**H: Blocker** + +- **FR-41** Blocker entsteht inline in Reflexion oder via „Neuer Blocker"-Button. +- **FR-42** Blocker-Felder: Titel, Beschreibung, Verantwortlicher, Ersteller, Bereich(e), Datum offen, Status. +- **FR-43** Blocker erscheint auf Puls, in betroffenen Bereichs-Detail-Views und in Projekt-Detail. +- **FR-44** Auflösung mit Auflöser, Datum, Kommentar. Nur P1 des bezogenen Projekts darf auflösen; bei projektlosen Blockern auch Ersteller/Verantwortlicher. + +**I: Projekt-Abschluss** + +- **FR-45** P1 setzt Projekt auf „abgeschlossen". +- **FR-46** Beim Abschluss fordert Cockpit Lessons-Learned-URL an. +- **FR-47** Abgeschlossene Projekte verschwinden aus Puls, bleiben in Archiv-Sicht pro Bereich. + +**J: Navigation, Suche & Filter** + +- **FR-48** Globale TopNav: Puls / Entwicklung / Vertrieb / Marketing / Mein heute / Eigenes Profil / Archiv. +- **FR-49** Volltextsuche → Phase 2; MVP: Filter im Feed nach Bereich/Bezug. + +### NonFunctional Requirements + +> 16 NFRs aus PRD §7. + +**Performance** + +- **NFR-1** Puls-Seite < 1,5 s bei 6-15 Nutzern und 90 Tagen Historie. +- **NFR-2** Tagesupdate-Speichern < 500 ms. + +**Verfügbarkeit** + +- **NFR-3** Mo-Fr 8-19: 99 %. +- **NFR-4** Wartungsfenster außerhalb Kernzeit kommunizierbar. + +**Security & Privacy** + +- **NFR-5** Alle Routen hinter Authentik-OIDC. +- **NFR-6** Keine externe PII in Deal-Karten. +- **NFR-7** Audit-Trail für jede mutierende Operation (actor / timestamp / payload). + +**Bedienbarkeit & Visual** + +- **NFR-8** Brand-konform: weiß primär, schwarz sekundär, ToolCTRL Brand Identity. +- **NFR-9** Tagesupdate-Schreib-Workflow ≤ 3 Klicks ab Puls. +- **NFR-10** Desktop-first, mobil-lesbar (nicht mobil-schreibbar). +- **NFR-11** Deutsch durchgängig, keine i18n. + +**Voice & Tone** + +- **NFR-12** Tonalität locker/direkt/deutsch, kurze Imperative ("Stark.", "Wie läuft es?", "Du hast heute noch nicht gepingt."). +- **NFR-13** Keine Corporate-Sprech, keine englischen Floskeln (Fachbegriffe aus Leitfaden ausgenommen). + +**Datenhaltung & Erweiterbarkeit** + +- **NFR-14** Postgres als Persistenz. +- **NFR-15** Tägliches Backup. +- **NFR-16** Datenmodell erweiterbar ohne Migrationen > 2 h. + +### Additional Requirements + +> Aus Architektur-Spec abgeleitete technische Anforderungen. + +- **AR-1** **Starter / Bootstrap**: Custom Monorepo (pnpm-Workspaces) mit `apps/web` (Vite + React + TS + Tailwind), `apps/api` (Fastify + tRPC + Drizzle), `packages/db` (Drizzle-Schema), `packages/shared` (Zod + Types). Erste Story nutzt die in der Architektur §"Initialization Commands" beschriebenen Setup-Befehle. +- **AR-2** **Datenmodell** in `packages/db/schema.ts` mit allen 14 Entitäten (User, Area, Project, Milestone, Deal, GtmStep, Campaign, LeadHandover, DayEntry, Todo, Blocker, AbsenceDay, AuditEvent, Session) gemäß Addendum §1.2 und Architektur §Project Structure. UUID v7 als ID-Primary. +- **AR-3** **Auth-Flow**: `openid-client` v6+ direkt im Fastify, Authorization Code + PKCE gegen Authentik. Server-Side-Session in Postgres, HttpOnly + SameSite=Lax + Secure Cookie. Default-Bereich aus Authentik-Group-Claim. +- **AR-4** **tRPC Procedure-Typen**: `publicProcedure`, `protectedProcedure`, `dayEntryVisibleProcedure` (Sichtbarkeitsfilter pre/post `published_at`), `projectOwnerProcedure` (P1-Check). +- **AR-5** **Audit-Middleware** (`withAudit`) um jede schreibende tRPC-Mutation, schreibt parallel in `audit_event`. +- **AR-6** **Background-Jobs** via System-Cron → authentifizierter Fastify-Endpoint (`POST /internal/cron/*` mit Bearer-Token): + - `publish-day-entries` (tägl. 00:05 Europe/Berlin) + - `recompute-pflicht-minimum-5` (stündlich) + - `recompute-streaks` (tägl. 00:10) + - `pg-dump-backup` (tägl. 02:00) +- **AR-7** **Werktag-Logik** zentral in `apps/api/src/lib/werktag.ts` (Mo-Fr Europe/Berlin). Frontend bekommt vorberechnete Werte, niemals eigene Datumsmagie. +- **AR-8** **Ampel-Aggregation** in `apps/api/src/lib/ampel.ts` (FR-1a-Algorithmus). +- **AR-9** **Pflicht-Minimum-Berechnung** in `apps/api/src/lib/pflicht-minimum.ts`, lazy beim Read. +- **AR-10** **Hosting**: Docker-Compose mit `web`-Container (Caddy-served `apps/web/dist`), `api`-Container (Node 22+), `postgres:16`-Container, `caddy`-Container als Reverse-Proxy auf `cockpit.tool-ctrl.de`. +- **AR-11** **CI/CD**: Gitea Actions Pipeline (Typecheck, Lint, Vitest, Build, Docker-Image-Push, Deploy-Trigger). +- **AR-12** **Tests**: Vitest (Unit + tRPC-Procedures), Playwright (E2E auf UJ-1, UJ-2, UJ-5). Lighthouse-A11y-Score ≥ 95. +- **AR-13** **Logging**: strukturierte JSON-Logs via `pino` → stdout. +- **AR-14** **Healthcheck**: `GET /api/health` (Postgres-Ping + Authentik-Reachability) für Docker-Healthcheck. +- **AR-15** **Konventionen-Datei** `CONVENTIONS.md` im Repo-Root (spiegelt Architektur §Implementation Patterns), Pflicht-Lesen vor Story-Bearbeitung. +- **AR-16** **Backup-Strategie**: `pg_dump` täglich auf separates Volume, Retention 14 Tage. Restore-Drill quartalsweise dokumentiert in `docs/operations.md`. + +### UX Design Requirements + +> Aus UX-Spec abgeleitete UI/Visual/A11y-Anforderungen. + +**Brand-Tokens & Visual Foundation** + +- **UX-DR1** Brand-Tokens als CSS-Variablen in `apps/web/src/styles/globals.css` definieren und in `tailwind.config.ts` als Theme spiegeln. Werte gemäß UX-Spec §Visual Design Foundation: + - `--color-bg: #ffffff`, `--color-surface: #fafafa`, `--color-surface-elevated: #ffffff` + - `--color-border: #e5e5e5`, `--color-border-strong: #d4d4d4` + - `--color-ink: #0a0a0a`, `--color-ink-muted: #525252`, `--color-ink-faint: #a3a3a3` + - `--color-brand: #0a0a0a`, `--color-brand-fg: #ffffff` + - Ampel: green `#15803d`/`#dcfce7`, amber `#a16207`/`#fef3c7`, red `#b91c1c`/`#fee2e2` + - Trend: up `#15803d`, flat `#525252`, down `#b91c1c` +- **UX-DR2** Typografie: Inter (UI, variable) + JetBrains Mono (Werte) selbst-gehostet via `@fontsource/inter` und `@fontsource/jetbrains-mono`. 7-Stufen-Type-Scale aus UX-Spec §Typography. +- **UX-DR3** Spacing-Skala, Radii und Schatten gemäß UX-Spec §Spacing & Layout — als Tailwind-Theme-Erweiterung. +- **UX-DR4** Container-Max-Width 1.280 px, 12-Spalten-Grid mit 24 px Gap; Puls auf Desktop 3-Spalten. + +**Custom-Component-Library (`apps/web/src/components/ui/`)** + +- **UX-DR5** `Button` mit 3 Varianten (primary/secondary/ghost) + Destructive + Icon-Only, 3 Größen (sm/md/lg). +- **UX-DR6** `Card` mit Compound-API (`Card.Root`, `Card.Header`, `Card.Body`, `Card.Footer`). +- **UX-DR7** `Input`, `Textarea` (mit Zeichen-Counter), `Select` (via Radix), `Checkbox` (via Radix mit animiertem Häkchen). +- **UX-DR8** `AmpelButton` (3-Wege-Toggle Grün/Gelb/Rot, große Klick-Targets), `TrendButton` (3-Wege ↗︎/→/↘︎). +- **UX-DR9** `Dialog` (via Radix mit Brand-Header und `radius-xl`), `Tooltip` (via Radix, schwarzer Bubble), `Toast` (via Sonner, Bottom-Right, schwarzer Pill). +- **UX-DR10** `SkeletonCard` — graue Card-Skelette identischer Geometrie wie echte Cards. +- **UX-DR11** `AmpelBadge` — Status-Pill mit Farbpunkt + Label + optionalem Trend-Pfeil. +- **UX-DR12** `PflichtMinimumIndicator` — sechs kleine Quadrate horizontal, gefüllt/leer. +- **UX-DR13** `KeyCaret` — SVG-Komponente für das Brand-`^`-Element, einbindbar in Header/Logo/Toast. + +**Layout & Navigation** + +- **UX-DR14** TopNav fest mit `cockpit ^` Logo links, Tabs (Puls / Entwicklung / Vertrieb / Marketing / Mein heute / Archiv), rechts Streak-Badge + Avatar. Aktiver Tab mit 2 px schwarzem Underline. +- **UX-DR15** Breadcrumbs in Detail-Views (`Puls › Entwicklung › Projekt X`), klickbar pro Segment. +- **UX-DR16** Tastatur-Shortcuts: `g p`, `g h`, `g e`, `g v`, `g m`, `g a`, `n` (Neuer Blocker), `?` (Cheatsheet), `Esc` (Modal schließen). + +**Voice & Tone (Mikrocopy)** + +- **UX-DR17** Zentrale Mikrocopy-Datei `apps/web/src/lib/voice.ts` mit allen UI-Strings. Beispiele: „Stark.", „Wie läuft es?", „Du hast heute noch nicht gepingt.", „Reflektiert.", „Blocker offen.". Pflicht-Anwendung in allen Komponenten. + +**User-Journey-Flows** + +- **UX-DR18** UJ-1 Tagesrhythmus: `/mein-heute` (Morgen-Einstempeln + ToDo-Editor) und `/reflexion` (Abend-Reflexion mit Tagesplan-Echo). +- **UX-DR19** UJ-2 Jourfixe: Puls-Layout muss alle 3 Bereiche in einem Scroll abdeckbar machen. +- **UX-DR20** UJ-5 Projekt anlegen: Modal mit Pflicht-Feldern (Name, P1, Projektart, Arbeitsmodus, Steckbrief-URL, Plane-URL). + +**Responsive & Accessibility** + +- **UX-DR21** Drei Sicht-Ebenen: Desktop (≥ 1280 px, 3-Spalten Puls), Tablet (≥ 768 px, 2-Spalten), Mobile (< 768 px, Single-Column + Bottom-Tab-Bar, lesbar). +- **UX-DR22** WCAG 2.1 AA durchgängig. Keyboard-First (Tab, Enter, Space, Esc). Sichtbarer Fokus-Ring (`outline: 2px solid var(--color-ink)`, 2 px Offset). +- **UX-DR23** Skip-Link „Zum Hauptinhalt springen" oben. Saubere Heading-Hierarchie. Live-Regionen für Toasts. +- **UX-DR24** `prefers-reduced-motion` → Fade-Animationen auf 0 ms. +- **UX-DR25** axe-core in Vitest pro Komponente; `@axe-core/playwright` für E2E. ESlint-`jsx-a11y` mit `recommended` aktiviert. + +**Interaktions-Patterns** + +- **UX-DR26** Optimistic Updates bei Checkbox-Abhaken und Ampel-Setzen (Rollback bei API-Fehler). +- **UX-DR27** Skeleton-Cards beim Initial-Load — nie leerer Bildschirm, nie Spinner alleine. +- **UX-DR28** Empty-States als Voice-konforme Tropfen-Information (z. B. „Hier erscheinen die Updates des Tages, sobald jemand reflektiert hat."). +- **UX-DR29** Reflexions-Erinnerung im Header ab 17:00 (dezenter Pill, klickbar). +- **UX-DR30** Vortrags-Reveal: Fade-In ≤ 200 ms beim ersten Laden am Werktag X+1. + +### FR Coverage Map + +| FR | Epic | Kurztitel | +|---|---|---| +| FR-1, FR-1a, FR-2, FR-3, FR-4, FR-5, FR-6, FR-7 | Epic 3 | Puls & Übersicht | +| FR-8, FR-9, FR-10, FR-11 | Epic 4 | Bereich Entwicklung | +| FR-12, FR-13, FR-14, FR-15, FR-16 | Epic 4 | Bereich Vertrieb | +| FR-17, FR-18, FR-19, FR-20 | Epic 4 | Bereich Marketing | +| FR-21 – FR-33 | Epic 2 | Tagesrhythmus | +| FR-34, FR-35, FR-36, FR-37 | Epic 2 + Epic 3 | Streak + Anwesenheit | +| FR-38, FR-39, FR-40 | Epic 5 | Pflicht-Minimum | +| FR-41, FR-42, FR-43, FR-44 | Epic 5 | Blocker | +| FR-45, FR-46, FR-47 | Epic 6 | Projekt-Abschluss | +| FR-48 | Epic 1 | Navigation (Top-Nav) | +| FR-49 | Phase 2 | Volltextsuche | + +**NFR-Abdeckung:** NFR-5/7/8/9 ⇒ Epic 1 · NFR-1/2 ⇒ über alle Epics validiert · NFR-3/4/14/15/16 ⇒ Epic 6 · NFR-10/11/12/13 ⇒ Epic 1 + Epic 2 (Voice-Anker etabliert) + +## Epic List + +> 6 Epics, jedes liefert ein eigenständig nutzbares Stück. Reihenfolge folgt der in `architecture.md` § "Decision Impact Analysis → Implementation Sequence" festgelegten Story-Sequenz. + +### Epic 1: Fundament — Login, Shell, Brand + +**Ziel:** Eine Person kann sich via Authentik einloggen und landet in einer leeren, brand-konformen Cockpit-Shell mit funktionierender Navigation. Es passiert noch nichts Fachliches — aber das Fundament steht. + +**Was nach Epic 1 nutzbar ist:** +- Login/Logout funktioniert +- TopNav steht (Puls-Tab, Profil, Streak-Badge-Slot leer) +- Brand-Theme aktiv (Farben, Typografie, KeyCaret-Logo) +- Healthcheck + Docker-Compose laufen lokal + +**FRs:** FR-48 +**NFRs:** NFR-5, NFR-7, NFR-8, NFR-9 (vorbereitet), NFR-11, NFR-12, NFR-13, NFR-14 +**AR:** AR-1, AR-2 (Schema-Skeleton User+Session+AuditEvent), AR-3, AR-4 (Procedure-Typen-Skeleton), AR-5 (Audit-Wrapper), AR-10, AR-15, AR-13 (pino), AR-14 (Healthcheck) +**UX-DR:** UX-DR1, UX-DR2, UX-DR3, UX-DR4, UX-DR5, UX-DR6, UX-DR7, UX-DR13, UX-DR14, UX-DR15, UX-DR16, UX-DR17, UX-DR22, UX-DR23, UX-DR24, UX-DR25 + +### Epic 2: Tagesrhythmus — Einstempeln, ToDos, Reflexion + +**Ziel:** Eine Person kann morgens einstempeln + ToDos schreiben, abends reflektieren mit Ampel/Trend, und am nächsten Werktag wird der konsolidierte Eintrag für Kollegen sichtbar. Das Herz des Cockpits. + +**Was nach Epic 2 nutzbar ist:** +- Person kann den vollen Tagesrhythmus durchlaufen (UJ-1) +- Streak und Tages-Status erscheinen im Profil und in der „Heute fehlt"-Liste (basal — volle Puls-Visualisierung kommt in Epic 3) +- Cron-Job publiziert Vortags-Einträge + +**FRs:** FR-21 – FR-33, FR-34, FR-36, FR-37 +**AR:** AR-2 (Schema DayEntry, Todo, AbsenceDay), AR-4 (`dayEntryVisibleProcedure`), AR-5, AR-6 (publish-day-entries cron + recompute-streaks cron), AR-7 +**UX-DR:** UX-DR8 (AmpelButton, TrendButton), UX-DR9 (Dialog für AbwesendDialog), UX-DR10 (SkeletonCard), UX-DR18 (`/mein-heute` + `/reflexion`), UX-DR26 (optimistic), UX-DR27 (Skeleton), UX-DR28 (Empty-State), UX-DR29 (17:00-Hinweis), UX-DR30 (Vortrags-Reveal Fade-In) + +### Epic 3: Puls & Bereichs-Übersicht + +**Ziel:** Eine Person öffnet das Cockpit und sieht in unter 60 Sekunden den Unternehmens-Puls: alle drei Bereiche mit aggregiertem Ampelstatus, Heute-Quote, bereichsübergreifende Blocker, Jourfixe-Hinweis. Vortrags-Einträge im Feed. + +**Was nach Epic 3 nutzbar ist:** +- Puls-Seite ist die Heimat (UJ-2, UJ-3) +- „Heute fehlt"-Liste klickbar mit Personen +- Bereichs-Strip mit Ampel-Aggregation +- Feed konsolidierter Vortrags-Einträge + +**FRs:** FR-1, FR-1a, FR-2, FR-3, FR-4, FR-5 (Schema-Layout, KPIs werden mit Echtdaten in Epic 4 gefüllt), FR-6, FR-7 (Pflicht-Minimum-Slot, gefüllt in Epic 5), FR-35 +**AR:** AR-8 (Ampel-Aggregation), AR-7 (Werktage) +**UX-DR:** UX-DR11 (AmpelBadge), UX-DR19 (Puls-Layout 3-Spalten) + +### Epic 4: Projekte, Deals, Kampagnen — Bereichs-Details + +**Ziel:** Eine Person kann Projekte (Entwicklung), Deals (Vertrieb) und Kampagnen (Marketing) anlegen und pflegen. Bereichs-Detail-Views zeigen Listen, Kanban, Lead-Funnel, Crosslinks. Reflexionen (aus Epic 2) setzen Projekt-Ampeln korrekt. + +**Was nach Epic 4 nutzbar ist:** +- Projekte/Deals/Kampagnen können angelegt, geändert und gelistet werden (UJ-5) +- Sales-Kanban funktioniert (Drag-and-Drop, Stage-Wechsel) +- Lead-Funnel + Crosslink-Banner zeigen Aggregate +- KPIs im Puls-Bereichs-Strip (FR-5) zeigen Echtdaten + +**FRs:** FR-8, FR-9, FR-10, FR-11, FR-12, FR-13, FR-14, FR-15, FR-16, FR-17, FR-18, FR-19, FR-20, FR-31 +**AR:** AR-2 (Schema Project, Milestone, Deal, GtmStep, Campaign, LeadHandover) +**UX-DR:** UX-DR9 (Dialog für Projekt-Anlegen), UX-DR20 (Projekt-Anlege-Modal) + +### Epic 5: Pflicht-Minimum & Blocker + +**Ziel:** Die 6 Leitfaden-Pflicht-Punkte pro Projekt werden sichtbar gemacht. Blocker können erfasst, gelistet und (nur durch P1) aufgelöst werden. + +**Was nach Epic 5 nutzbar ist:** +- Pflicht-Minimum-Indikator in Projekt-Card und -Detail (FR-7 wird voll funktional auf dem Puls) +- Blocker-Lebenszyklus komplett (Erfassen via Reflexion-Inline oder „+ Neuer Blocker"-Button, Anzeige im Puls/Bereich/Projekt, Auflösung) +- Frühwarn-Mechanik aktiv + +**FRs:** FR-38, FR-39, FR-40, FR-41, FR-42, FR-43, FR-44 +**AR:** AR-4 (`projectOwnerProcedure`), AR-6 (recompute-pflicht-minimum-5 cron), AR-9 +**UX-DR:** UX-DR12 (PflichtMinimumIndicator) + +### Epic 6: Projekt-Abschluss, Archiv & Production + +**Ziel:** Projekte können abgeschlossen werden (mit Lessons-Learned-Link), Archiv-Sicht existiert. Production-Pipeline ist scharf: CI/CD, tägliches Backup, Healthcheck-Monitoring, A11y-Tests im CI. + +**Was nach Epic 6 nutzbar ist:** +- Volle Projekt-Lebensspanne von Anlage bis Lessons-Learned +- Cockpit ist deploybar und beobachtbar +- Backup + Restore-Drill dokumentiert +- A11y-Score ≥ 95 im CI gemessen + +**FRs:** FR-45, FR-46, FR-47 +**NFRs:** NFR-1, NFR-2, NFR-3, NFR-4, NFR-10 (Responsive-Validierung), NFR-15, NFR-16 +**AR:** AR-6 (pg-dump-backup), AR-11 (Gitea Actions), AR-12 (Tests, Playwright), AR-14 (Healthcheck-Erweiterung), AR-16 (Backup + Restore-Drill) +**UX-DR:** UX-DR21 (Responsive-Sichtbarkeitsprüfung), UX-DR25 (axe-core + jsx-a11y in CI) + +--- + +**Phase-2-Backlog (nicht im MVP, nicht in Epics):** + +- FR-49 Volltextsuche +- Plane-/Outline-API-Sync +- Mobile schreibbar +- Reaktionen / Kommentare auf Updates +- Element/Matrix/Mail-Reminders +- Loki/Grafana, Sentry, Feiertags-Kalender, Dark Mode + +--- + +## Epic 1: Fundament — Login, Shell, Brand + +Eine Person kann sich via Authentik einloggen und landet in einer leeren, brand-konformen Cockpit-Shell mit funktionierender Navigation. Es passiert noch nichts Fachliches — aber das Fundament steht und alle weiteren Epics können darauf aufsetzen. + +### Story 1.1: Repo-Bootstrap und Tooling + +Als **Entwickler**, +ich möchte **ein lauffähiges Monorepo mit Vite-Frontend, Fastify-Backend, Drizzle-DB-Package und Shared-Package**, +sodass **alle Folge-Stories auf einem konsistenten Setup aufsetzen können**. + +**Acceptance Criteria:** + +**Given** ein leeres `cockpit/`-Repo, +**When** `pnpm install` und `pnpm typecheck` ausgeführt werden, +**Then** alle 4 Workspaces (`apps/web`, `apps/api`, `packages/db`, `packages/shared`) sind installiert und typecheckbar +**And** `pnpm --filter web dev` startet Vite auf Port 5173 +**And** `pnpm --filter api dev` startet Fastify auf Port 3000 +**And** `CONVENTIONS.md` ist im Repo-Root vorhanden und spiegelt die Patterns aus `architecture.md` § Implementation Patterns +**And** ESLint und Prettier sind konfiguriert und liefern keinen Fehler beim Start. + +### Story 1.2: Postgres + Drizzle-Schema-Skeleton mit User, Session, AuditEvent + +Als **Entwickler**, +ich möchte **eine laufende Postgres-Instanz und das Drizzle-Schema für die drei Fundament-Entitäten**, +sodass **Auth und Audit auf einer realen DB-Schicht funktionieren können**. + +**Acceptance Criteria:** + +**Given** Docker auf dem Dev-Host, +**When** `docker compose -f docker-compose.dev.yml up postgres` läuft, +**Then** Postgres 16 ist auf Port 5432 erreichbar +**And** `packages/db/schema.ts` definiert die Tabellen `user`, `session`, `audit_event` mit UUID-v7-IDs, snake_case-Spalten und `timestamptz`-Zeitstempeln +**And** `pnpm --filter db migrate:generate` erzeugt eine Migration `0000_init.sql` +**And** `pnpm --filter db migrate` führt sie aus und die drei Tabellen existieren in der DB. + +### Story 1.3: Authentik-OIDC-Login-Flow + +Als **Mitarbeiter**, +ich möchte **mich via Authentik (SSO) einloggen und nach dem Login automatisch auf der Cockpit-Heimseite landen**, +sodass **ich ohne separate Account-Anlage Zugang zum Cockpit bekomme**. + +**Acceptance Criteria:** + +**Given** ich öffne `cockpit.tool-ctrl.de` ohne Session, +**When** das Backend mich an `auth.tool-ctrl.de` weiterleitet und ich mich dort erfolgreich einlogge, +**Then** werde ich auf `/puls` zurückgeleitet +**And** ein HttpOnly + SameSite=Lax + Secure Cookie `session_id` ist gesetzt +**And** in der DB existiert eine `session`-Zeile mit `expires_at` 24 h in der Zukunft +**And** in der DB existiert eine `user`-Zeile mit `authentik_sub`, `name`, `email` und (sofern Group-Claim vorhanden) `default_area_id` +**And** ein erneutes Aufrufen jeder geschützten Route innerhalb der Session-Gültigkeit funktioniert ohne erneuten Login +**And** `POST /api/auth/logout` invalidiert die Session in der DB und löscht das Cookie. + +### Story 1.4: tRPC-Bootstrap mit `protectedProcedure` und Audit-Wrapper + +Als **Entwickler**, +ich möchte **die tRPC-v11-Infrastruktur mit Context, `protectedProcedure` und `withAudit`-Wrapper laufen haben**, +sodass **alle fachlichen Stories direkt typsicher Procedures bauen können**. + +**Acceptance Criteria:** + +**Given** Story 1.3 ist gemerged, +**When** ein Client `trpc.user.me` ohne Session aufruft, +**Then** liefert das Backend `TRPCError(UNAUTHORIZED)` mit deutscher Message +**And** mit gültiger Session liefert es das `User`-Objekt aus der DB +**And** `withAudit` wrapt jede Mutation und schreibt parallel in `audit_event` in derselben Transaktion (verifiziert via Test) +**And** der Frontend-tRPC-Client in `apps/web/src/lib/trpc.ts` ist konfiguriert mit zentralem `onError`-Toast und nutzt die `AppRouter`-Typdefinition aus `packages/shared`. + +### Story 1.5: Brand-Tokens, Tailwind-Theme und Voice-Datei + +Als **UI-Entwickler**, +ich möchte **die ToolCTRL-Brand-Tokens als CSS-Variablen und Tailwind-Theme-Erweiterung aktiv haben**, +sodass **alle weiteren UI-Komponenten brand-konform stylebar sind**. + +**Acceptance Criteria:** + +**Given** Story 1.1 ist mergeable, +**When** ich `apps/web/src/styles/globals.css` lade, +**Then** sind alle Farb-Tokens, Spacing-, Radii- und Schatten-Variablen aus der UX-Spec definiert +**And** `tailwind.config.ts` spiegelt diese Tokens als Theme-Erweiterung +**And** `@fontsource/inter` und `@fontsource/jetbrains-mono` sind installiert und geladen +**And** `apps/web/src/lib/voice.ts` existiert mit allen Mikrocopy-Strings aus UX-Spec § Voice & Tone als exportierte Konstanten +**And** eine Test-Route `/dev/tokens` (nur in `import.meta.env.DEV`) zeigt eine Palette aller Tokens als visuelle Referenz. + +### Story 1.6: TopNav-Shell mit Logo, Tabs, Profil-Slot und Keyboard-Shortcuts + +Als **Mitarbeiter**, +ich möchte **eine sichtbare, navigierbare Cockpit-Shell mit Brand-Logo, Hauptnavigation und Profil-Bereich**, +sodass **ich die App-Struktur sofort verstehe**. + +**Acceptance Criteria:** + +**Given** ich bin eingeloggt, +**When** ich `cockpit.tool-ctrl.de` öffne, +**Then** sehe ich oben links `cockpit ^` (KeyCaret-SVG + Lockup) und in der Mitte die TopNav-Tabs `Puls`, `Entwicklung`, `Vertrieb`, `Marketing`, `Mein heute`, `Archiv` +**And** rechts erscheint mein Avatar (Initialen) und ein Streak-Badge-Platzhalter +**And** der aktive Tab zeigt einen 2 px dicken schwarzen Underline +**And** Tastatur-Shortcuts `g p`, `g h`, `g e`, `g v`, `g m`, `g a` springen auf die jeweilige Route +**And** `?` öffnet einen Cheatsheet-Dialog (alle Shortcuts gelistet) +**And** WCAG 2.1 AA wird erfüllt (axe-core-Test grün), Fokus-Ring sichtbar auf jedem Tab. + +### Story 1.7: Healthcheck, Docker-Compose und CI-Skeleton + +Als **DevOps**, +ich möchte **`docker compose up` startet Web + API + Postgres + Caddy lokal und der Healthcheck antwortet**, +sodass **Deployments und CI auf einer reproduzierbaren Basis aufsetzen können**. + +**Acceptance Criteria:** + +**Given** `docker-compose.yml` und `Caddyfile` existieren, +**When** `docker compose up -d --build` läuft, +**Then** sind alle vier Container `web`, `api`, `postgres`, `caddy` healthy +**And** `GET /api/health` liefert `200 OK` mit JSON `{ "status": "ok", "db": "ok", "authentik": "ok" }` +**And** `GET /` liefert die SPA-Index (statisch via Caddy) +**And** ein minimales Gitea-Actions-Workflow `ci.yml` läuft Typecheck, Lint und Vitest auf jedem Push. + +--- + +## Epic 2: Tagesrhythmus — Einstempeln, ToDos, Reflexion + +Eine Person kann den vollen Tagesrhythmus durchlaufen: morgens einstempeln + ToDos schreiben, abends reflektieren, am nächsten Werktag wird der Eintrag konsolidiert. Das ist die Kern-Erfahrung des Cockpits. + +### Story 2.1: DayEntry- und Todo-Schema, AbsenceDay, Werktag-Helper + +Als **Entwickler**, +ich möchte **die DB-Tabellen für Tageseinträge, ToDos und Abwesenheiten plus die Werktag-Logik im Backend**, +sodass **die Tagesrhythmus-Procedures auf einer korrekten Datenbasis arbeiten**. + +**Acceptance Criteria:** + +**Given** Epic 1 ist abgeschlossen, +**When** die Migration `0001_day_rhythm.sql` läuft, +**Then** existieren in der DB die Tabellen `day_entry`, `todo`, `absence_day`, `blocker` gemäß Addendum § 1.2 +**And** `apps/api/src/lib/werktag.ts` bietet `isWerktag(date)`, `nextWerktag(date)`, `werktagsDistance(a, b)` mit Mo-Fr-Logik in `Europe/Berlin` +**And** Vitest-Tests decken Grenzfälle ab (Wochenende, Tageswechsel um Mitternacht). + +### Story 2.2: Einstempeln + ToDo-Eingabe auf „Mein heute" + +Als **Mitarbeiter**, +ich möchte **mich morgens einstempeln und meine ToDos für heute eintippen**, +sodass **mein Tag begonnen ist und ich für Kollegen als „eingestempelt" sichtbar bin**. + +**Acceptance Criteria:** + +**Given** ich öffne `/mein-heute` als noch nicht eingestempelte Person, +**When** ich auf „Einstempeln" klicke und 3 ToDos eintippe (Enter erzeugt neue Zeile, optionaler Bezug per Inline-Select), +**Then** wird beim Klick auf „Eingestempelt" ein `day_entry` mit `status='begun'` und `checked_in_at` angelegt sowie 3 `todo`-Zeilen +**And** ich werde auf `/puls` weitergeleitet +**And** im „Heute fehlt"-Modul auf dem Puls erscheine ich nicht mehr (Kollegen sehen nur binäre Anwesenheit, nicht die ToDo-Inhalte — vgl. FR-24) +**And** ein „Eingestempelt."-Toast erscheint 2 s in Bottom-Right +**And** der Median-Speed-Test (manuelle Stoppuhr) zeigt ≤ 60 s für 3 ToDos (CM1a). + +### Story 2.3: Tagsüber ToDos editieren, „Mein heute" als private Sicht + +Als **Mitarbeiter**, +ich möchte **meine ToDos im Verlauf des Tages ergänzen oder ändern können**, +sodass **mein Tagesplan realitätsnah bleibt — und kein Kollege das mitliest, bevor ich reflektiert habe**. + +**Acceptance Criteria:** + +**Given** ich bin eingestempelt mit 3 ToDos, +**When** ich `/mein-heute` erneut öffne, +**Then** sehe ich meine ToDos editierbar (hinzufügen, ändern, löschen) bis zur Reflexion +**And** Edits werden gespeichert via tRPC-Mutation `todo.update`/`todo.create`/`todo.delete` +**And** ein anderer User, der dieselbe Person als „eingestempelt" sieht, kann **keine** ToDo-Inhalte abrufen (tRPC `dayEntry.todosForUser` liefert für andere User vor `published_at` nur `count`) +**And** die Projekt-P1 darf die ToDos sehen, sofern ein ToDo Bezug auf ihr/sein Projekt hat — verifiziert durch tRPC-Test mit zwei verschiedenen User-Sessions. + +### Story 2.4: Abend-Reflexion mit Tagesplan-Echo, Ampel + Trend + +Als **Mitarbeiter**, +ich möchte **abends meine ToDos durchgehen, abhaken oder verwerfen, einen Freitext schreiben und Ampel/Trend setzen**, +sodass **mein Tag offiziell als „voll" abgeschlossen gilt und der Status meines Projekts aktuell ist**. + +**Acceptance Criteria:** + +**Given** ich bin eingestempelt und öffne `/reflexion`, +**When** ich meine ToDos sehe (Tagesplan-Echo mit 200 ms Fade-In) und einige abhake, einige verwerfe (Inline-Grund-Feld) und einen Freitext mit ≤ 1000 Zeichen schreibe, +**Then** kann ich Ampel (🟢/🟡/🔴 als drei große Buttons) und Trend (↗︎/→/↘︎) setzen +**And** Trend ist Pflichtfeld, sofern mindestens ein ToDo Bezug=Projekt hat — Submit ohne Trend zeigt deutsche Inline-Fehlermeldung +**And** Submit setzt `day_entry.status='full'`, `reflected_at`, schreibt `reflection_text`, `ampel`, `trend` +**And** die Ampel + der Trend des verlinkten Projekts werden in derselben DB-Transaktion aktualisiert (`status_cache`, `trend_cache`) +**And** ein „Reflektiert."-Toast erscheint, ich werde auf `/puls` redirected +**And** Reflexion ist 30 min lang editierbar — danach erscheint sie unverändert. + +### Story 2.5: Blocker inline in der Reflexion erfassen + +Als **Mitarbeiter**, +ich möchte **bei der Reflexion einen Blocker mit einem Toggle eintragen**, +sodass **das Team meine Hindernisse am nächsten Werktagsmorgen sieht**. + +**Acceptance Criteria:** + +**Given** ich bin in der Reflexion, +**When** ich den „Blocker?"-Toggle aktiviere und Titel + Beschreibung eintippe, +**Then** wird beim Submit zusätzlich ein `blocker`-Datensatz erzeugt +**And** der Blocker ist mit `opened_by = mein User` und (sofern ToDo-Bezug=Projekt) `project_id` verknüpft +**And** der `day_entry.blocker_id` zeigt auf den neuen Blocker +**And** der Blocker erscheint **erst nach Publikation am nächsten Werktag** im bereichsübergreifenden Blocker-Modul des Puls (Verifizierung mit zweiter Session). + +### Story 2.6: Cron „publish-day-entries" + Streak-Berechnung + +Als **System**, +möchte ich **nachts um 00:05 Europe/Berlin die DayEntries des Vortags publizieren und Streaks neu berechnen**, +sodass **am Morgen X+1 die konsolidierten Vortrags-Einträge sichtbar werden und Streaks korrekt sind**. + +**Acceptance Criteria:** + +**Given** mehrere User haben am Vortag eingestempelt + reflektiert, +**When** der System-Cron `POST /internal/cron/publish-day-entries` mit Bearer-Token aufruft, +**Then** werden alle `day_entry`-Zeilen des Vortags mit `published_at = heute 00:00` versehen +**And** die nachfolgende tRPC-Procedure `dayEntry.feedForArea` liefert diese Einträge inklusive ToDos + Reflexion für alle Kollegen +**And** ein zweiter Cron-Aufruf `recompute-streaks` setzt `user.current_streak` korrekt (vollständige + halbe + abwesende Tage gemäß FR-33/34) +**And** Cron-Aufrufe ohne korrekten Bearer-Token liefern `401`. + +### Story 2.7: Streak-Badge, Abwesenheit, „Halb"-Status + +Als **Mitarbeiter**, +ich möchte **meine aktuelle Streak in der TopNav sehen und mich für Tage als abwesend markieren können**, +sodass **Urlaubstage nicht meine Streak brechen**. + +**Acceptance Criteria:** + +**Given** ich bin eingeloggt, +**When** ich die TopNav betrachte, +**Then** sehe ich rechts neben meinem Avatar einen Pill „N ^" (N = aktuelle Streak, KeyCaret als Suffix) +**And** ich kann via Profil-Modal einen Zeitraum als „abwesend" (Urlaub/Krank/Sonstiges) markieren +**And** Abwesenheits-Tage werden als `absence_day` gespeichert und im Streak-Cron als „nicht gegen die Serie zählend" behandelt +**And** wenn ich morgens nur einstempele aber nicht reflektiere, zeigt `dayEntry.todayStatus` für mich `status='begun'` und mein Tages-Status-Indikator wird gelb („halb") +**And** der nächste Streak-Cron würde diesen Tag nicht für die Serie zählen (verifiziert via Unit-Test in `werktag.ts`). + +--- + +## Epic 3: Puls & Bereichs-Übersicht + +Eine Person öffnet das Cockpit und sieht in unter 60 Sekunden alles Wesentliche: drei Bereiche mit Ampel-Aggregat, Heute-Quote, Blocker, Jourfixe-Hinweis, Vortrags-Feed. + +### Story 3.1: Area-Schema, Aggregations-Algorithmus, Puls-Procedure + +Als **Entwickler**, +ich möchte **die `area`-Tabelle und die `apps/api/src/lib/ampel.ts`-Aggregations-Logik plus eine tRPC-Procedure `puls.overview`**, +sodass **die Puls-Frontend-Seite alle Aggregat-Daten in einem Roundtrip bekommt**. + +**Acceptance Criteria:** + +**Given** die DB enthält drei `area`-Zeilen (dev, sales, marketing) und (zunächst leere) Projekt-Tabelle, +**When** `trpc.puls.overview` ohne Filter aufgerufen wird, +**Then** liefert es ein Objekt mit `areas: [{ id, name, ampel, ampel_count, missing_today_count }]`, `blockers: [...]`, `heuteQuote: { gepingt, gesamt, fehlende: [...] }`, `jourfixe: { nextDate, label }` +**And** die Ampel-Aggregation pro Bereich folgt FR-1a-Algorithmus (verifiziert via Vitest-Test mit Fixtures aus mehreren Status-Kombinationen) +**And** die Procedure liefert in < 200 ms bei 6-15 Usern und 90 Tagen Historie (Performance-Test mit `vitest --bench`). + +### Story 3.2: Puls-Seite Layout mit Bereichs-Strip und Heute-Quote + +Als **Mitarbeiter**, +ich möchte **auf `/puls` in einem Blick die drei Bereiche mit Ampelstatus und die Heute-Quote sehen**, +sodass **ich morgens innerhalb 60 s einen Überblick habe**. + +**Acceptance Criteria:** + +**Given** ich öffne `/puls`, +**When** die Seite geladen hat, +**Then** sehe ich oben einen Hero mit „Stand: Di, 25. Mai 2026" +**And** unter dem Hero ein 3-Spalten-Grid mit den drei Bereichen, jeweils mit AmpelBadge, Bereichsname, Anzahl fehlender Updates heute, und einer Klick-Aktion zur Bereichs-Detail-View +**And** rechts (oder darunter auf Tablet) ein „Heute fehlt"-Modul mit `X/Y eingestempelt`-Stat und Klick auf Detail-Liste der fehlenden Personen (mit Avatar + Name) +**And** wenn ich noch nicht eingestempelt bin, erscheint prominent oberhalb des Folds der CTA „Du hast heute noch nicht gepingt." mit „Einstempeln"-Primary-Button → führt zu `/mein-heute` +**And** Skeleton-Cards beim Initial-Load, Fade-In ≤ 200 ms beim Datenanstieg. + +### Story 3.3: Blocker-Modul, Jourfixe-Hinweis, KPI-Slots + +Als **Mitarbeiter**, +ich möchte **auf `/puls` die offenen Blocker, den nächsten Jourfixe und KPI-Slots pro Bereich sehen**, +sodass **ich den ganzen Unternehmens-Puls auf einer Seite verstehe**. + +**Acceptance Criteria:** + +**Given** ich öffne `/puls`, +**When** die Seite vollständig geladen ist, +**Then** sehe ich unter dem Bereichs-Strip ein Modul „Bereichsübergreifend offen" mit allen offenen Blockern sortiert nach Alter +**And** ein Modul „Jourfixe" zeigt den nächsten Termin (Default „Di, 11:00") +**And** pro Bereich erscheint eine KPI-Kachel mit Slots aus FR-5 (Anzahl Projekte, Status-Verteilung etc.) — Werte sind in Epic 3 ggf. noch 0 (echte Daten kommen in Epic 4) +**And** das Modul „Pflicht-Minimum" ist als Slot vorhanden, zeigt aber bis Epic 5 nur „Keine Projekte mit unerfülltem Pflicht-Minimum" als Empty-State. + +### Story 3.4: Vortrags-Feed im Puls + „Heute fehlt"-Detail + +Als **Mitarbeiter**, +ich möchte **am Morgen X+1 die konsolidierten Vortrags-Einträge meiner Kollegen im Feed sehen**, +sodass **ich weiß, was gestern lief**. + +**Acceptance Criteria:** + +**Given** am Tag X haben N Personen reflektiert und der Publikations-Cron lief um 00:05, +**When** ich am Tag X+1 morgens `/puls` öffne, +**Then** sehe ich unten einen „Gestern im Team"-Feed mit konsolidierten DayEntries (Avatar + Name + Bereich + Ampel + Trend + Reflexionstext + abgehakte/verworfene ToDos) +**And** der Feed sortiert neueste oben, paginiert 50/Seite mit „Mehr laden" +**And** Klick auf einen Eintrag öffnet die Detail-Sicht (read-only) +**And** das „Heute fehlt"-Modul klickt jetzt auf eine vollständige Liste der heute noch nicht eingestempelten Personen +**And** Performance-Budget < 1,5 s gesamtladezeit auf `/puls` mit 90 Tagen Historie (Lighthouse-Test). + +--- + +## Epic 4: Projekte, Deals, Kampagnen — Bereichs-Details + +Eine Person kann Projekte, Deals und Kampagnen anlegen und in den jeweiligen Bereichs-Detail-Views arbeiten. Die KPI-Slots im Puls werden mit Echtdaten gefüllt. + +### Story 4.1: Project-, Milestone- und Verlinkungs-Schema, Anlage-Modal + +Als **Projektverantwortliche/r (P1)**, +ich möchte **ein Projekt im Cockpit anlegen können mit allen Cockpit-Sicht-Feldern und Pflicht-Verlinkungen**, +sodass **das Projekt im Puls und in der Bereichs-Detail-View erscheint**. + +**Acceptance Criteria:** + +**Given** ich bin eingeloggt und in der Bereichs-Detail-View Entwicklung, +**When** ich auf „+ Neues Projekt" klicke, +**Then** öffnet sich ein Dialog mit den Pflichtfeldern Name, Verantwortliche/r (P1, User-Auswahl), Projektart (Kundenprojekt/Internes Projekt), Arbeitsmodus (Cycle/Kanban), Outline-Steckbrief-URL, Plane-Board-URL +**And** optional DoD-Text, Risikoliste-URL, Start, Deadline und 0..N grobe Meilensteine inline +**And** beim Submit werden `project`- und `milestone`-Zeilen geschrieben (Migration `0002_projects.sql`), audit-getracked +**And** das neue Projekt erscheint sofort im Bereich Entwicklung (FR-8/9) und im Puls-KPI-Slot. + +### Story 4.2: Bereichs-Detail Entwicklung mit Projekt-Karten und Detail-View + +Als **Mitarbeiter**, +ich möchte **die Liste aller Entwicklungs-Projekte in zwei Gruppen (Kundenprojekt/Internes Projekt) sehen und in die Detail-Sicht klicken**, +sodass **ich Status, Meilensteine und letzte Updates pro Projekt erfasse**. + +**Acceptance Criteria:** + +**Given** mindestens 2 Projekte (eines Kundenprojekt, eines internes) existieren, +**When** ich `/bereich/entwicklung` öffne, +**Then** sehe ich zwei Sektionen „Kundenprojekte" und „Interne Projekte" mit Projekt-Cards (Name, P1-Avatar, AmpelBadge + Trend, Deadline-Progress-Bar, # offene Meilensteine, Pflicht-Minimum-Score Slot) +**And** Klick auf Projekt-Card öffnet `/projekt/:id` mit der Detail-Sicht: Kopfzeile (Name, P1, Projektart, Arbeitsmodus, Ampel + Trend), Verlinkungen (Outline-Steckbrief, Plane-Board, optional Risikoliste), optional DoD-Text, Meilenstein-Checkliste, Update-Feed dieses Projekts +**And** Meilensteine sind inline editierbar (Status: offen/erledigt/überzogen, Fälligkeit ändern) und werden in der Cockpit-DB persistiert. + +### Story 4.3: Deal-Schema, Sales-Kanban und Pipeline-KPIs + +Als **Vertrieb**, +ich möchte **meine Deals als Kanban in 5 Stages sehen und per Drag-and-Drop verschieben**, +sodass **die Pipeline immer aktuell ist**. + +**Acceptance Criteria:** + +**Given** Migration `0003_sales.sql` ist gelaufen und mind. 3 Deals existieren, +**When** ich `/bereich/vertrieb` öffne, +**Then** sehe ich oben das KPI-Header-Modul mit gewichtetem Pipeline-Wert (Σ value × probability), aktiven Deals-Count, gewonnenen Deals des laufenden Quartals (# + EUR formatiert als `12.500,00 €`), ⌀ Stage-Verweildauer +**And** darunter ein 5-Spalten-Kanban (Lead → Erstgespräch → Demo → Angebot → Abschluss) mit Deal-Karten +**And** Drag-and-Drop einer Karte auf eine andere Spalte ändert `deal.stage` und setzt `entered_stage_at = now()` (optimistic UI + Rollback bei Fehler) +**And** unten ein gefilterter Vertrieb-Update-Feed (Bezug=Deal oder Vertrieb-allgemein). + +### Story 4.4: GTM-Schritte und Vertrieb-Feed + +Als **Vertrieb**, +ich möchte **pro Produkt eine sortierbare GTM-Schritt-Liste pflegen**, +sodass **die Markteinführung jedes Produkts strukturiert ist**. + +**Acceptance Criteria:** + +**Given** im Vertriebs-Detail existiert ein Bereich „GTM-Schritte pro Produkt", +**When** ich ein neues Produkt-Tab hinzufüge und Schritte (Titel + Verantwortlicher + Status) einpflege, +**Then** wird `gtm_step` in der DB persistiert mit `order`-Feld für die Reihenfolge +**And** Schritte sind per Drag-and-Drop oder Up/Down-Tastatur-Buttons umsortierbar (`order` wird neu vergeben in einer Transaktion) +**And** der Status (offen/in Arbeit/erledigt) ist inline änderbar. + +### Story 4.5: Campaign-Schema, Lead-Funnel und Crosslink-Banner + +Als **Marketing**, +ich möchte **den Lead-Funnel mit Conversion-Prozenten und die Kampagnen-Liste sehen, plus einen Crosslink-Banner zur Vertriebs-Übergabe**, +sodass **ich verstehe, wie viele Leads in echte Deals umgewandelt werden**. + +**Acceptance Criteria:** + +**Given** Migration `0004_marketing.sql` ist gelaufen und `lead_handover` enthält Daten, +**When** ich `/bereich/marketing` öffne, +**Then** sehe ich oben einen horizontalen 5-Stufen-Lead-Funnel (Reichweite → Website → Leads → MQL → Übergabe) mit Conversion-% zwischen den Stufen +**And** darunter eine Liste aktiver Kampagnen (Aktiv/Pausiert/Beendet, Reichweite, Verantwortlicher) +**And** ein Crosslink-Banner zeigt „Marketing hat X Leads übergeben in [letzten 30 Tagen], Vertrieb hat daraus Y Deals erzeugt (Z € im Quartal)" +**And** derselbe Banner spiegelt sich umgekehrt im Vertrieb-Detail (FR-19) +**And** unten ein gefilterter Marketing-Update-Feed. + +--- + +## Epic 5: Pflicht-Minimum & Blocker + +Die 6 Leitfaden-Pflicht-Punkte pro Projekt werden sichtbar gemacht. Blocker-Lebenszyklus ist komplett — inkl. der Regel „nur P1 darf auflösen". + +### Story 5.1: Pflicht-Minimum-State, Lazy-Berechnung und Cron-Punkt-5 + +Als **Entwickler**, +ich möchte **die Pflicht-Minimum-Logik im Backend mit der korrekten Punkt-5-Aktualisierung**, +sodass **der Status pro Projekt stets korrekt aus den Daten abgeleitet wird**. + +**Acceptance Criteria:** + +**Given** Migration `0005_pflicht_minimum.sql` ergänzt `project.pflicht_minimum_state` (JSONB) und `project.steckbrief_freigegeben` (bool), +**When** `apps/api/src/lib/pflicht-minimum.ts` `computePflichtMinimum(projectId)` aufgerufen wird, +**Then** liefert es ein Objekt mit 6 Booleans gemäß FR-38 Definition (Punkte 1-4 manuell aus DB-Feldern, Punkt 5 berechnet aus letztem Reflexions-Zeitstempel) +**And** der Cron-Endpoint `POST /internal/cron/recompute-pflicht-minimum-5` setzt Punkt 5 auf `false` für alle Projekte mit letzter Reflexion > 7 Werktage zurück +**And** der Aggregations-Algorithmus aus FR-1a wird ergänzt: Pflicht-Minimum-Verstöße heben Ampel mindestens auf Gelb (Vitest-Test mit Fixtures). + +### Story 5.2: Pflicht-Minimum-Indikator und -Panel in der UI + +Als **P1 / P4**, +ich möchte **pro Projekt-Card und Projekt-Detail die 6 Pflicht-Punkte als visuelle Indikatoren sehen**, +sodass **ich sofort sehe, was bei diesem Projekt fehlt**. + +**Acceptance Criteria:** + +**Given** Stories 5.1 ist abgeschlossen und ein Projekt hat 4/6 Punkte erfüllt, +**When** ich `/bereich/entwicklung` öffne, +**Then** zeigt jede Projekt-Card unten rechts den `PflichtMinimumIndicator` mit 6 horizontalen Quadraten (gefüllt/leer) und Score „4/6" +**And** auf `/projekt/:id` erscheint das `PflichtMinimumPanel` mit allen 6 Punkten als Checkliste, jeweils mit Status, kurzer Beschreibung und (für Punkte 1-4) Quick-Action-Button („Steckbrief verlinken", „P1 setzen", „Plane-Board verlinken", „Meilenstein anlegen") +**And** Punkte 5 und 6 sind read-only mit Erklärungs-Tooltip +**And** Projekte mit ≥ 1 unerfülltem Punkt erscheinen auf `/puls` im Modul „Pflicht-Minimum" (vorher leerer Slot wird gefüllt). + +### Story 5.3: Blocker-Lebenszyklus — Erfassen, Anzeige, P1-Auflösung + +Als **Mitarbeiter**, +ich möchte **Blocker außerhalb der Reflexion erfassen können und P1 kann sie auflösen**, +sodass **das Team auf bereichsübergreifende Hindernisse reagieren kann**. + +**Acceptance Criteria:** + +**Given** der „+ Neuer Blocker"-Button in der TopNav (Shortcut `n`), +**When** ich auf ihn klicke und Titel + Beschreibung + Verantwortlichen + Bereich(e) + optionale Projekt-Verknüpfung eintrage, +**Then** wird `blocker` in der DB persistiert mit `opened_by = ich` +**And** der Blocker erscheint auf `/puls` im bereichsübergreifenden Modul, im jeweiligen Bereichs-Detail und (sofern projektgebunden) im Projekt-Detail +**And** auf der Blocker-Card erscheint ein „Auflösen"-Button **nur sichtbar, wenn ich P1 des bezogenen Projekts bin** (oder bei projektlosen Blockern Ersteller oder Verantwortlicher) +**And** ein nicht-P1-User, der versucht via direktem tRPC-Call aufzulösen, bekommt `TRPCError(FORBIDDEN)` mit deutscher Message +**And** beim Auflösen werden `resolved_by`, `resolved_at` und `resolution_text` persistiert; Blocker verschwindet aus den offenen Listen. + +### Story 5.4: Blocker im Projekt-Detail und Audit der Auflösung + +Als **P1 / P4**, +ich möchte **die Blocker-Historie pro Projekt einsehen können**, +sodass **ich nachvollziehe, was und wann gelöst wurde**. + +**Acceptance Criteria:** + +**Given** ein Projekt hat mehrere Blocker (einige offen, einige gelöst), +**When** ich `/projekt/:id` öffne, +**Then** sehe ich einen Tab oder Bereich „Blocker" mit zwei Listen: offene Blocker oben (mit „Auflösen"-Button für P1), gelöste Blocker darunter (mit Auflöser-Name + Datum + Kommentar) +**And** jede Blocker-Mutation (erstellen, lösen) hat einen entsprechenden `audit_event`-Eintrag (verifiziert per DB-Query in Test). + +--- + +## Epic 6: Projekt-Abschluss, Archiv & Production + +Projekte können abgeschlossen werden inkl. Lessons-Learned-Pflicht. Archiv-Sicht existiert. Production-Pipeline ist scharf: CI/CD, Backup, A11y-Tests. + +### Story 6.1: Projekt-Abschluss-Workflow und Lessons-Learned-Pflicht + +Als **P1**, +ich möchte **ein Projekt auf „abgeschlossen" setzen können — aber nur, wenn ich die Lessons-Learned-URL angegeben habe**, +sodass **die Leitfaden-Pflicht-Minimum-Position 6 garantiert erfüllt wird**. + +**Acceptance Criteria:** + +**Given** ich bin P1 eines aktiven Projekts und öffne den Projekt-Header, +**When** ich auf „Projekt abschließen" klicke, +**Then** öffnet sich ein Dialog, der die Lessons-Learned-URL als Pflichtfeld einfordert (URL-Format-Validierung via Zod) +**And** beim Submit wird `project.archiviert=true`, `archiviert_am=now()`, `lessons_learned_url` gesetzt und `pflicht_minimum_state.6=true` neu berechnet +**And** das Projekt verschwindet sofort aus `/bereich/entwicklung` und `/puls` +**And** Try-To-Submit ohne URL zeigt deutsche Inline-Fehlermeldung +**And** ein nicht-P1-User sieht den „Abschließen"-Button nicht (UI + tRPC-Guard). + +### Story 6.2: Archiv-Sicht pro Bereich + +Als **Mitarbeiter**, +ich möchte **abgeschlossene Projekte in einer Archiv-Sicht einsehen**, +sodass **vergangene Erkenntnisse auffindbar bleiben**. + +**Acceptance Criteria:** + +**Given** mindestens 2 Projekte sind abgeschlossen, +**When** ich `/archiv` öffne (oder via TopNav-Tab), +**Then** sehe ich pro Bereich (Tabs Entwicklung/Vertrieb/Marketing) eine Liste abgeschlossener Items sortiert nach `archiviert_am` (neueste oben) +**And** Klick auf ein Item öffnet die read-only Detail-Sicht mit allen Daten + Lessons-Learned-Link +**And** die Archiv-Sicht hat denselben Sub-Header wie die Bereichs-Detail-Views (Sticky), aber mit zusätzlichem „Archiv"-Marker. + +### Story 6.3: Backup-Cron, Restore-Drill-Dokumentation + +Als **DevOps**, +ich möchte **ein tägliches `pg_dump`-Backup mit 14-Tage-Retention und dokumentiertem Restore-Pfad**, +sodass **Datenverlust auch nach einem schweren Vorfall begrenzt bleibt**. + +**Acceptance Criteria:** + +**Given** der Cron-Endpoint `POST /internal/cron/pg-dump-backup` ist implementiert, +**When** die System-Cron-Definition den Endpoint täglich 02:00 aufruft, +**Then** wird ein gz-komprimiertes Dump-File auf einem separaten Docker-Volume `backups` geschrieben +**And** Dumps älter als 14 Tage werden automatisch gelöscht +**And** `docs/operations.md` enthält eine Restore-Anleitung in 5 Schritten (Stop API, Restore Dump, Migrate, Smoke-Test, Start API) +**And** ein quartalsweiser Restore-Drill ist als Issue im Plane geöffnet (Story-Akzeptanz prüft, dass die Aufgabe dokumentiert ist, nicht den Drill selbst). + +### Story 6.4: CI-Pipeline mit Vitest, Playwright und Lighthouse-A11y + +Als **Entwickler**, +ich möchte **eine CI-Pipeline, die Typecheck, Lint, Vitest, Playwright und Lighthouse-A11y-Test bei jedem PR ausführt**, +sodass **Qualität und Barrierefreiheit nicht erodieren**. + +**Acceptance Criteria:** + +**Given** `.gitea/workflows/ci.yml` erweitert die Pipeline, +**When** ein PR geöffnet wird, +**Then** läuft die Pipeline `pnpm install` → `pnpm typecheck` → `pnpm lint` → `pnpm test` (Vitest mit axe-core auf Komponenten-Snapshots) → `pnpm build` → Playwright-E2E auf den drei UJs (UJ-1, UJ-2, UJ-5) gegen eine ephemere Docker-Compose-Umgebung → Lighthouse-CI auf `/puls`, `/mein-heute`, `/projekt/:id` +**And** Lighthouse-A11y-Score < 95 lässt die Pipeline fehlschlagen +**And** jeder PR-Check zeigt einen Statusbericht mit den erfüllten/gefehlten Punkten. + +### Story 6.5: Responsive-Audit und Mobile-Lesbarkeit + +Als **Mitarbeiter**, +ich möchte **das Cockpit auch unterwegs am Handy lesen können** (nicht schreiben — das bleibt Desktop), +sodass **ich beim Pendeln zumindest sehe, wie es um die Projekte steht**. + +**Acceptance Criteria:** + +**Given** ich öffne `/puls` und `/projekt/:id` auf einem mobilen Browser (Viewport 375×667), +**When** die Seiten geladen sind, +**Then** sind alle Karten und Listen leserlich gestapelt (Single-Column), keine horizontalen Scrolls +**And** die TopNav wird zu einer Bottom-Tab-Bar mit 4 Items (Puls, Mein heute, Bereiche-Dropdown, Profil) +**And** der Einstempel-CTA auf `/mein-heute` zeigt einen sichtbaren Hinweis „Tagesupdate auf Desktop empfohlen" (Voice-konform) +**And** Playwright-Mobile-Viewport-Tests laufen für `/puls` und `/projekt/:id` und prüfen Layout-Integrität. + +--- + +## Validation Summary + +### FR Coverage Check + +| FR | Story | Status | +|---|---|---| +| FR-1, 1a, 2-7 | 3.1-3.4 | ✅ | +| FR-8-11 | 4.1, 4.2 | ✅ | +| FR-12-16 | 4.3, 4.4 | ✅ | +| FR-17-20 | 4.5 | ✅ | +| FR-21-25 | 2.2, 2.3 | ✅ | +| FR-26-28, 31 | 2.4 | ✅ | +| FR-29, 30 | 2.6, 3.4 | ✅ | +| FR-32, 33 | 2.2, 2.4, 2.7 | ✅ | +| FR-34-37 | 2.7 | ✅ | +| FR-38-40 | 5.1, 5.2 | ✅ | +| FR-41-44 | 2.5, 5.3, 5.4 | ✅ | +| FR-45-47 | 6.1, 6.2 | ✅ | +| FR-48 | 1.6 | ✅ | +| FR-49 | — | � Phase 2 (explizit) | + +**Coverage:** 48 von 49 FRs durch Stories abgedeckt; FR-49 (Volltextsuche) bewusst Phase 2 gemäß PRD § 8. + +### NFR Coverage Check + +- **Performance (NFR-1, 2):** 3.1 (Procedure-Budget), 3.4 (Lighthouse), 6.4 (CI-Performance-Gate) +- **Verfügbarkeit (NFR-3, 4):** 6.3 (Backup + Restore-Drill), 1.7 (Healthcheck) +- **Security (NFR-5, 6, 7):** 1.3 (Authentik), 1.4 (Audit-Wrapper) +- **Bedienbarkeit (NFR-8-10):** 1.5 (Brand-Tokens), 6.5 (Mobile-Audit) +- **Voice & Tone (NFR-11, 12, 13):** 1.5 (voice.ts) — alle UI-Stories nutzen die zentrale Datei +- **Daten (NFR-14, 15, 16):** 1.2 (Postgres+Drizzle), 6.3 (Backup), 1.1 (Drizzle-Migrationen) + +### Story-Quality-Validation + +- ✅ **Sized for single dev agent:** keine Story > 1 Arbeitstag in der KI-gestützten Entwicklung. +- ✅ **Given/When/Then-Format** auf jeder AK. +- ✅ **Keine Vorwärts-Abhängigkeiten** — jede Story baut auf vorherigen Stories (gleicher Epic) oder bereits abgeschlossenen Epics auf. +- ✅ **Inkrementelles Schema:** DB-Migrationen entstehen je Story-Bedarf, nicht upfront. (`0000_init`, `0001_day_rhythm` inkl. blocker, `0002_projects`, `0003_sales`, `0004_marketing`, `0005_pflicht_minimum`.) +- ✅ **Custom-Starter** ist Story 1.1 (Repo-Bootstrap), entspricht Architektur § Initialization Commands. + +### Epic-Independence-Check + +| Epic | Depends on | Liefert | +|---|---|---| +| 1 | — | Login + Shell + Brand | +| 2 | 1 | Tagesrhythmus inkl. Cron + Streak | +| 3 | 1, 2 | Puls-Übersicht mit Vortrags-Feed | +| 4 | 1 (unabhängig von 2/3 möglich) | Projekt-/Deal-/Kampagnen-Domain | +| 5 | 4 (Projekte) + 2 (Blocker-Inline) | Pflicht-Minimum + Blocker-Lifecycle | +| 6 | 4 (Archiv), 2 (Cron-Pattern) | Abschluss + Production-Pipeline | + +✅ Jedes Epic liefert eigenständig nutzbaren Wert. + +### File-Churn-Check + +- Epic 2 und Epic 5 berühren beide `blocker`-Schema und -Komponente — bewusst getrennt: Epic 2 etabliert nur Inline-Erfassung in der Reflexion, Epic 5 fügt vollständigen Lifecycle (Standalone-Button + P1-Auflösung + Audit-Tab) hinzu. Aufteilung mitigiert Risiko, dass das volle Blocker-System verzögert wird, bevor der Tagesrhythmus läuft. +- Epic 3 und Epic 4 berühren beide `apps/api/src/routers/puls.ts` und KPI-Slots im Frontend — getrennt, weil Epic 3 die Aggregation für leere Bereiche etabliert (skelettiert) und Epic 4 die Domain-Daten dahinter liefert. Konsolidierung hätte ein zu großes Epic ohne Zwischen-Release-Wert ergeben. + +### UX-DR-Coverage-Spot-Check + +- UX-DR1-4 (Brand-Tokens, Typo, Spacing, Container) → Story 1.5 +- UX-DR5-7 (Button, Card, Input, Textarea, Select, Checkbox) → Story 1.5 + 1.6 (verteilt, je nach Bedarf in Folge-Stories) +- UX-DR8 (AmpelButton, TrendButton) → Story 2.4 +- UX-DR9 (Dialog, Tooltip) → Story 4.1 + 6.1 +- UX-DR10 (SkeletonCard) → Story 3.2 +- UX-DR11 (AmpelBadge) → Story 3.2 / 4.2 +- UX-DR12 (PflichtMinimumIndicator) → Story 5.2 +- UX-DR13 (KeyCaret) → Story 1.6 + 2.7 +- UX-DR14-16 (Navigation, Breadcrumbs, Shortcuts) → Story 1.6 +- UX-DR17 (voice.ts) → Story 1.5 +- UX-DR18-20 (User Journeys) → Stories 2.2/2.4, 3.2, 4.1 +- UX-DR21-25 (Responsive + A11y) → Story 6.4 + 6.5 +- UX-DR26-30 (Interaktions-Patterns) → verteilt über alle UI-Stories + +✅ Alle 30 UX-DRs sind in Stories adressiert. + +### Validation Verdict + +**🟢 READY FOR DEVELOPMENT** + +- 49 FRs · 16 NFRs · 16 AR-Items · 30 UX-DRs → durch 6 Epics und 32 Stories abgedeckt +- Keine Vorwärts-Abhängigkeiten, keine Upfront-Big-Bang-Migrationen +- Epic-Reihenfolge entspricht der Architektur-Implementation-Sequence +- Bauen kann mit **Story 1.1 (Repo-Bootstrap)** sofort starten. diff --git a/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/.decision-log.md b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/.decision-log.md new file mode 100644 index 0000000..367f778 --- /dev/null +++ b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/.decision-log.md @@ -0,0 +1,160 @@ +# Entscheidungs-Log – PRD projectmanagement + +> Kanonisches Gedächtnis und Audit-Trail dieses PRD-Laufs. Jede Entscheidung, Änderung +> und jeder Override (auch headless) wird hier festgehalten, während das Gespräch läuft. + +## Eintraege + +- **2026-05-25** – Lauf gestartet. Intent: *Create*. Workspace: `prd-projectmanagement-2026-05-25/`. + Sprache: Deutsch (communication & document_output). Facilitator: Claude (bmad-prd). +- **2026-05-25** – Inputs eingelesen: `inputs/unternehmens-cockpit.html` (HTML-Prototyp, + 1214 Zeilen, von Nicht-Techniker als Vibe-Code-Ausgangspunkt erstellt). Extract liegt in + `extract-prototype-html.md`. ToolCTRL Brand Identity referenziert (Pfad ausserhalb Repo, + nur als Referenz; konkrete Werte werden in der UX-Phase festgezogen). +- **2026-05-25** – Working Mode: **Fast Path**. Verbleibende Gaps werden gebündelt + abgefragt, dann Draft mit `[ASSUMPTION]`-Tags. User reviewt und iteriert. +- **2026-05-25** – Team-Skalierung: **6-15 Mitarbeiter** (gesamtes ToolCTRL-Team). +- **2026-05-25** – Auth-Strategie: **Authentik (auth.tool-ctrl.de) via OIDC**. Wird als + weitere App im bestehenden SSO-Setup registriert. Konsistent mit Plane, Outline, Gitea, + Kimai, Nextcloud, Element, Jitsi etc., die alle schon hinter Authentik laufen. +- **2026-05-25** – Plane/Outline-Integration: **Phase 2, nicht MVP**. Im MVP nur freie + URL-Links als Strings im Feld `Bezug`. Später API-Sync möglich (auch unter Authentik). +- **2026-05-25** – Tech-Stack-Default für PRD: **React (Frontend) + Postgres (Datenhaltung)**; + Backend-Wahl bleibt der Architektur-Phase überlassen. Tech-Details landen in + `addendum.md`, das PRD selbst bleibt Capability-orientiert. +- **2026-05-25** – Areas-Scope MVP: **nur 3 Detail-Areas** (Entwicklung, Vertrieb, + Marketing). Buchhaltung & Design können Tagesupdates mit Bezug "allgemein" schreiben + und erscheinen im Puls als Update-Quote / Streak, bekommen aber keine eigene Detail-View. + Erweiterung in Phase 2. +- **2026-05-25** – Kern-Mechanik gegen Versandung: **kombinierter Ansatz** — + (a) Daily-Update-Pflicht mit öffentlicher "fehlt heute"-Liste & Streak, + (b) Deadline-/Meilenstein-Verfall als Bar-Visualisierung pro Projekt, + (c) bereichsübergreifende Blocker-Liste im Puls. Diese drei zusammen sind das "Warum + jetzt"-Kernversprechen des Produkts. +- **2026-05-25** – **Major Update: Leitfaden v3.0 als bindender Frame nachgereicht** + (`inputs/ToolCTRL_Projektmanagement-Leitfaden_v3.0.pdf`, Mai 2026). + Konsequenzen, die im PRD und Addendum eingepflegt sind: + - PRD-Frontmatter `binding_frame` gesetzt. + - Tool-Aufteilung verschärft: Plane=Arbeit, Outline=Gedächtnis, Cockpit=Übersicht + (Anti-Ziele A3, A4, A5 ergänzt). + - Ampel-Definitionen exakt aus Leitfaden §5: Grün/Gelb/Rot mit klarer + Semantik, plus Trend (besser/gleich/schlechter) + Freitext max 1000 Zeichen. + - **Neues Major-Feature**: Feature-Gruppe G "Pflicht-Minimum-Check" (FR-30/30a/30b) + leitet sich direkt aus Leitfaden §9 ab; im Puls (FR-7) sichtbar. + - Erfolgsmetrik M6 ergänzt (≥90% Projekte erfüllen alle 6 Pflicht-Punkte). + - Rollen umstrukturiert: §4.1 Leitfaden-Rollen (P1-P4) + §4.2 Querschnittsrollen + (Q1-Q5). Konsistent mit Leitfaden §7. + - Projektarten umbenannt: Kundenprojekt / Internes Projekt (statt + Umsatz/Intern aus Prototyp). Konsistent mit Leitfaden §1. + - Datenmodell-Skizze erweitert: `Project.kind`, `arbeitsmodus`, `dod_text`, + URL-Felder, `pflicht_minimum_state`, `Deal.probability`, + `Deal.won_at/won_value_eur`, neue `LeadHandover`-Entität. + - Addendum §7 "Leitfaden-Bezüge"-Tabelle ergänzt. +- **2026-05-25** – Reconciliation-Subagent Findings eingearbeitet: + - FR-13: `probability` ergänzt; FR-14: gewichteter Pipeline-Wert + Won-Deals-KPI; + - FR-19: Crosslink-Banner Marketing↔Vertrieb; + - NFR-12/13: neue Sektion **Voice & Tone** mit Prototyp-Anker-Phrasen; + - Vokabular im Addendum §6 um "Update-Serie", "Crosslink", + "Bereichsübergreifend offen", "Trend" erweitert. +- **2026-05-25** – Rubric-Walker Verdict: **good** (0 Critical, 0 High, + 3 Medium, 5 Low). Findings teilweise sofort eingearbeitet: + - FR-1a: Ampel-Aggregations-Algorithmus pro Area expliziert. + - FR-5: Key-Metrics als verbindlicher Katalog pro Area (nicht mehr Beispiele). + - §5 UJ: Persona-Linkage via P1-P4 / Q1-Q5-IDs. + - FR-23: Sortierung neueste oben + Pagination 50/Seite. + - NFR-16: Bound "max. 2-stündige Migration". + - §10: Annahmen-Index ergänzt (A-1 bis A-6). + - OQ-4 als Phase-Blocker markiert. + Rest (OQ-3 Bestätigung, OQ-7 Pflicht/Optional Trend, "Bereich/Area"-Synonymie) → + Triage mit User. +- **2026-05-25** – Vorläufige Defaults für offene Fragen im PRD eingetragen: + OQ-3 Default = "rückwirkende Updates gesperrt"; OQ-4 Default = "jeder darf + Blocker als gelöst markieren"; OQ-6 Default = "manuelle Checkbox im MVP"; + OQ-7 Default = "Trend optional". Alle bestätigungsbedürftig in Triage. +- **2026-05-25** – **Major Konzept-Erweiterung: Morgen-ToDos + Abend-Reflexion.** + User hat in der Triage einen bisher nicht erwähnten Kern-Workflow nachgereicht: + Tagesupdate ist nun ein zweistufiger Vorgang — morgens stempelt die Person sich + ein und trägt ihre ToDos für den Tag ein; abends ruft sie die Liste auf, hakt ab, + schreibt eine Reflexion, setzt Ampel + Trend. Kollegen sehen den konsolidierten + Eintrag erst am nächsten Werktagsmorgen. Innerhalb des laufenden Tages sehen + Kollegen nur binäre Anwesenheit, keine ToDo-Inhalte. + Konsequenzen im PRD: + - UJ-1 komplett umgeschrieben (Morgen/Abend/Nächster Morgen). + - Feature-Gruppe E umgebaut: 6.5.1 Morgen (FR-21-25), 6.5.2 Abend (FR-26-28), + 6.5.3 Konsolidierung (FR-29-33). FRs danach von 26-39 auf 34-49 renumbered. + - Tages-Status pro Person (voll/halb/nicht angefangen/abwesend) (FR-33). + - Streak-Definition geändert: zählt nur volle Tage (FR-34). + - CM1 aufgesplittet in CM1a (Morgen ≤60s), CM1b (Abend ≤90s), CM1 (Gesamt ≤3 min). + - Sichtbarkeitsmodell: `published_at` = nächster Werktagsmorgen. + - Addendum: neues Datenmodell `DayEntry` ersetzt `Update`, neue Entität `Todo`, + Vokabular um Einstempeln/Reflexion/„Mein heute"/Konsolidierter Vortagseintrag + erweitert. + - Beschlossene OQs aus Triage: OQ-3 (1 Werktag zurück), OQ-4 (nur P1), + OQ-6 (manuelle Checkbox), OQ-7 (Trend Pflicht bei Projekt-Bezug). + - Neue OQs offen: OQ-8 (Erinnerungs-Zeit Reflexion, Default 17:00), + OQ-9 (≥1 ToDo Pflicht beim Einstempeln), OQ-10 (ToDos nach Reflexion noch + nachtragbar — Default: nein). +- **2026-05-25** – Polish-Pass durchgeführt durch zwei parallele Subagenten + (Struktur + Prosa) auf prd.md und addendum.md. Wichtigste Änderungen: + Vereinheitlichung „Area" → „Bereich"; CM-Tabelle nach Gesamtzeit zuerst, + dann Teilzeiten sortiert; Tippfehler bereinigt; Vokabular synchronisiert + („Tagesupdate" durchgängig). Keine FRs/Anti-Ziele/Metriken inhaltlich + geändert. PRD: 29 206 → 28 633 Zeichen (–2%). Addendum: 9 500 → 9 350 + Zeichen (–1,5%). +- **2026-05-25** – **Finalize abgeschlossen.** Frontmatter `status: final` in + prd.md und addendum.md gesetzt. Phase-Blocker-OQs (OQ-1 Authentik-Mapping, + OQ-2 Brand-Tokens, OQ-4 Blocker-Auflöser) sind im PRD entschieden oder an + konkrete Folge-Phase deferiert. Externe Handoffs (Confluence/Notion) sind im + Skill-Config leer — entfallen. Empfehlung für nächsten Schritt: parallel + bmad-create-architecture (technische Schnitt klar, Datenmodell skizziert) + und bmad-create-ux-design (Brand-Tokens, „Mein heute"-Konzept, Voice & Tone). + bmad-create-epics-and-stories danach. +- **2026-05-25** – **Architektur-Phase abgeschlossen.** Dokument liegt unter + `_bmad-output/planning-artifacts/architecture.md` (8 von 8 Schritten erledigt, + Status complete). Wichtigste Pivots: + - **Stack ist Vite + React (SPA), nicht Next.js** — auf Team-Wunsch + (T3-Stack-Vorschlag aus Step 3 verworfen). + - Monorepo (pnpm workspaces): `apps/web`, `apps/api`, `packages/db`, `packages/shared`. + - Backend: Fastify 5+ mit tRPC v11, `openid-client` v6+ für Authentik OIDC, + Drizzle ORM auf Postgres 16+, Session-Cookies (HttpOnly+SameSite=Lax) statt JWT. + - Background-Jobs: System-Cron → authentifizierter Fastify-Endpoint + (`/internal/cron/*` mit Bearer-Token), 4 Jobs definiert. + - Hosting: bestehende ToolCTRL-Infrastruktur, Caddy 2.x als Reverse-Proxy. + - 4 tRPC-Procedure-Typen: `public`, `protected`, `dayEntryVisible`, `projectOwner`. + - OQ-5 (Archivierungs-Policy) in der Architektur fixiert: Soft-Delete via + `archiviert`-Flag, kein automatisches Löschen. + - Verdict der Validierung: **READY FOR IMPLEMENTATION** (16/16 Checklist, + 0 Critical, 3 Important nicht-blockierende Gaps). + - Folgeskill: `bmad-create-ux-design` (OQ-2 Brand-Tokens, „Mein heute"-Layout, + Voice & Tone), danach `bmad-create-epics-and-stories`. +- **2026-05-25** – **UX-Design-Phase abgeschlossen.** Dokument liegt unter + `_bmad-output/planning-artifacts/ux-design-specification.md` (14 von 14 Schritten + erledigt, status: complete). Hauptentscheidungen: + - **Brand-Tokens fixiert (OQ-2 geschlossen):** Weiß-Schwarz-Monochrom mit drei + Ampel-Akzentfarben für Status; konkrete Hex-Werte und Typo-Scale dokumentiert. + - **Typografie:** Inter (UI) + JetBrains Mono (Werte), selbst-gehostet via + `@fontsource`. Konsistent mit Brand-Marketing-Style. + - **Component-Library:** Tailwind + Radix UI Primitives + 15 hauseigene Brand- + konforme Komponenten in `components/ui/`. Kein shadcn-Bulk, kein Material UI. + - **Design-Richtung:** „Office Minimal" — ruhiges, typografie-getragenes Layout, + Brand-Reduktion als Feature. Drei Sichtebenen: Desktop (Volllayout), Tablet + (2-Spalten), Mobile (lesbar, schreiben auf Desktop empfohlen). + - **Voice & Tone konkretisiert:** „Stark.", „Wie läuft es?", „Du hast heute noch + nicht gepingt." als Mikrocopy-Anker; keine Emojis, kein Corporate-Sprech, + deutsch durchgängig. + - **Brand-Element `^`:** Das macOS-Ctrl-Caret als wiederkehrendes Element + (Logo-Lockup `cockpit ^`, Streak-Badges, Toast-Bestätigungen). + - **Mermaid-Diagramme** für UJ-1 bis UJ-5 in der Spec. + - **A11y-Strategie:** WCAG 2.1 AA, Keyboard-First, axe-core-Tests, Lighthouse- + Ziel ≥ 95. + - Folgeskill: **`bmad-create-epics-and-stories`** — übersetzt PRD + Architektur + + UX-Spec in epische User-Stories mit Akzeptanzkriterien. +- **2026-05-25** – **Epics & Stories-Phase abgeschlossen.** Dokument liegt unter + `_bmad-output/planning-artifacts/epics.md` (4 von 4 Schritten erledigt, + status: complete). User-Direktive: „verkünstel dich nicht zu sehr in der + Planung — wir müssen mit dem Bauen anfangen." Daher schlanke Struktur: + **6 Epics** entlang der Architektur-Implementation-Sequence, **32 Stories** + insgesamt mit Given/When/Then-AKs. Schema wird inkrementell pro Story-Bedarf + migriert (kein Big-Bang). Coverage: 48/49 FRs, alle NFRs/AR/UX-DRs abgedeckt; + FR-49 (Volltextsuche) explizit Phase 2. **Verdict: READY FOR DEVELOPMENT — + bauen startet mit Story 1.1 (Repo-Bootstrap).** diff --git a/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/addendum.md b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/addendum.md new file mode 100644 index 0000000..20110ad --- /dev/null +++ b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/addendum.md @@ -0,0 +1,273 @@ +--- +title: Addendum – ToolCTRL Unternehmens-Cockpit +status: final +created: 2026-05-25 +updated: 2026-05-25 +binding_frame: ToolCTRL Projektmanagement-Leitfaden v3.0 (Mai 2026) +--- + +# Addendum zum PRD – ToolCTRL Unternehmens-Cockpit + +> Inhalte, die im Hauptdokument den Lesefluss stören würden, aber für Architektur, UX-Design und das spätere Verständnis von Entscheidungen wichtig sind. + +--- + +## 1. Technische Orientierung (vorläufig) + +### 1.1 Stack-Default +- **Frontend:** React. +- **Datenhaltung:** Postgres. +- **Backend:** offen — Node/Express oder Fastify (passt zur React-Welt), Python/FastAPI, oder .NET. Entscheidung in Architektur-Phase. +- **Auth:** Authentik (auth.tool-ctrl.de) via OIDC. Cockpit als weitere App im Authentik-Provider; Token-Validierung im Backend. +- **Hosting:** wahrscheinlich gleiche interne Infrastruktur wie Plane/Outline/Gitea. + +### 1.2 Datenmodell-Skizze (nicht normativ) + +``` +User + id, authentik_sub, name, email, default_area_id, active, abwesend_bis + +Area + id, name, kind (dev|sales|marketing|allgemein), order + +Project + id, area_id, name, + kind (kundenprojekt|internes_projekt) # Leitfaden §1 + verantwortliche_user_id # Leitfaden §7 (P1) + arbeitsmodus (cycle|kanban) # Leitfaden §5 + outline_steckbrief_url + steckbrief_freigegeben (bool, manuell) + plane_project_url + risikoliste_url (nullable) + dod_text (nullable, Freitext) # Leitfaden §4 + start, deadline, + status_cache (green|amber|red|null) + trend_cache (besser|gleich|schlechter|null) + pflicht_minimum_state (jsonb mit 6 booleans) # Leitfaden §9 + archiviert (bool), archiviert_am, lessons_learned_url (nullable) + created_by, created_at + +Milestone + id, project_id, title, due, status (open|done|overdue), description + +Deal + id, area_id (=sales), name, owner_id, value_eur, + probability (0-100), # für gewichtete Pipeline + stage (lead|erstgespraech|demo|angebot|abschluss), + entered_stage_at, + won_at (nullable), won_value_eur (nullable) + +GtmStep + id, product, title, owner_id, status (open|in_progress|done), order + +Campaign + id, area_id (=marketing), name, status (active|paused|done), + reach, owner_id + +LeadHandover # Crosslink Marketing → Sales + id, campaign_id, dealkette_deal_ids (array), + übergeben_am, übergeben_von + +DayEntry # ersetzt das alte "Update" + id, user_id, date, + status (begun|full|absent), # begun = nur Einstempeln; full = Einstempeln + Reflexion + checked_in_at, # Zeitstempel Morgen-Einstempeln + reflected_at (nullable), # Zeitstempel Abend-Reflexion + reflection_text (nullable, max 1000 Zeichen), + ampel (green|amber|red|null), + trend (besser|gleich|schlechter|null), + url (nullable), + blocker_id (nullable), + edit_locked_at (nullable), # 30 Min nach Reflexion + published_at # nächster Werktag 00:00 — ab dann für Kollegen sichtbar + +Todo + id, day_entry_id, user_id, date, + title (max 200), order, + bezug_kind (project|deal|campaign|allgemein|null), + bezug_id (nullable), + status (open|done|dropped), + drop_reason (nullable, Freitext), + created_at, completed_at, dropped_at + +# Hinweis: ein Tagesupdate (DayEntry) hat 0..N Todos plus einen einzigen +# Reflexionsblock. Vor `published_at` sehen nur die Person selbst und +# Projektverantwortliche (für Todos mit Projekt-Bezug) die Inhalte. + +Blocker + id, title, description, area_id, project_id (nullable), + owner_id, opened_by, opened_at, + resolved_by, resolved_at, resolution_text + +AbsenceDay + id, user_id, date, reason (urlaub|krank|sonstiges) + +AuditEvent + id, actor_user_id, action, entity_kind, entity_id, payload_jsonb, created_at +``` + +### 1.3 Auth-Flow +1. User öffnet `cockpit.tool-ctrl.de`. +2. Backend prüft Session-Cookie; bei Fehlen Redirect auf Authentik-Authorize-Endpoint. +3. Authentik liefert OIDC-Code, Cockpit-Backend tauscht ihn gegen Token (Authorization Code Flow + PKCE). +4. Backend legt User in eigener Tabelle an, falls erstmaliger Login (Mapping per `sub`). +5. Default-Area wird aus Authentik-Group-Claim gemappt. + +### 1.4 Phase 2 – Plane/Outline-Integration (Skizze) +- **Plane:** API-Token; bei Eingabe einer Plane-URL holt das Backend Titel, Status und Owner und cached sie. Pflicht-Minimum-Punkt 3 (Plane-Board) wird damit automatisch validiert. +- **Outline:** bei Eingabe einer Steckbrief-URL prüft das Backend per Outline-API, ob das Dokument existiert und welchen Status es hat (Draft/Published). Pflicht-Minimum-Punkt 1 wird damit automatisch validiert. +- **Lessons-Learned-Vorlage:** beim Projekt-Abschluss erzeugt das Cockpit eine vorbefüllte Outline-Seite gemäß Leitfaden-Vorlage. + +--- + +## 2. Verworfene Alternativen + +### 2.1 „Bleiben wir bei Plane allein, sparen uns das Cockpit" +- **Verworfen, weil:** Plane ist ein Tickets-Tool. Es zwingt nicht zu täglichen Updates und aggregiert nicht über Bereiche hinweg. Der Schmerz „wir sehen nicht, was läuft" bleibt ungelöst. Das Cockpit ist im Leitfaden §5 zudem explizit vorgesehen. + +### 2.2 „Zurück zu Jira+Confluence" +- **Verworfen, weil:** schon einmal aufgegeben. Plane/Outline sind die strategisch gewollten Nachfolger. + +### 2.3 „Slack/Element-Channel mit Daily-Standup-Bot" +- **Verworfen, weil:** Chat skaliert nicht zu Übersicht. Visualisierung (Puls, Funnel, Kanban) und Aggregation fehlen; Suche und Filter sind schwach. + +### 2.4 „SaaS-Lösung kaufen (Range, Geekbot, Standuply, etc.)" +- **Verworfen, weil:** externe SaaS bedeutet weiteren Anbieter, Datenabfluss und Auth-Insel. ToolCTRL bevorzugt Self-Hosting hinter Authentik. Keine der untersuchten Lösungen trifft die ToolCTRL-Spezifika (Pflicht-Minimum-Check aus Leitfaden, Crosslinks, GTM-Schritte). + +### 2.5 „localStorage / Single-Page, kein Backend" +- **Verworfen, weil:** Multi-User braucht Server-Persistenz. + +### 2.6 „Alle 5 Bereiche mit Detail-Views im MVP" +- **Verworfen, weil:** Buchhaltung und Design haben keine etablierten KPI-Strukturen. Detail-Views wären leere Skelette. Phase 2 nach Definition. + +### 2.7 „Steckbrief-Editor ins Cockpit holen — One-Stop-Shop" +- **Verworfen, weil:** Leitfaden §8 ist explizit: „Wir führen kein zusätzliches Projektmanagement-Tool ein." Outline ist das Gedächtnis. Doppel-Storage erodiert die Tool-Aufteilung. + +### 2.8 „Plane-Issues spiegeln, damit man im Cockpit Tickets sieht" +- **Verworfen, weil:** Plane ist „jederzeit die Wahrheit" (Leitfaden §5). Ein Spiegel driftet auseinander. Wir verlinken auf Plane. + +### 2.9 „CRUD für Projekte komplett aus dem Cockpit raus, nur API-Sync" +- **Verworfen, weil:** im MVP gibt es noch keinen API-Sync. Pragmatik vor Reinheit: das Cockpit pflegt eine reduzierte Sicht (Name, Verantwortliche/r, URLs, grobe Meilensteine), bis Phase 2 kommt. Die Doppelpflege wird offen kommuniziert und gemessen (CM4). + +--- + +## 3. In-Depth-Personas (für UX-Phase) + +### 3.1 Lukas, Entwickler, 28 +- Macht morgens Code, will das Cockpit in unter 30 s pro Tag bedienen. +- Wechselt häufig zwischen 2–3 Projekten am Tag. +- Lästert intern, wenn das Tool ihn „checken" lässt. Sichtbar zu machen, dass Kollege X seit 5 Tagen blockt, empfindet er als hilfreich. + +### 3.2 Dejan, Owner / Auftraggeber (P4), oft auch P1, 35 +- Will im Jourfixe nicht mehr fragen „wie sieht's bei X aus" — sondern direkt klicken. +- Liest den Puls 2× täglich, schreibt selbst 1× Tagesupdate mit Bezug „Allgemein" oder zu einem Projekt. +- Größte Sorge: dass das Cockpit-Projekt das gleiche Schicksal erleidet wie Jira/Confluence zuvor. + +### 3.3 Sales-Kollege (Q2), 30+ +- Lebt im Kanban-Tab; bewegt 5–10 Karten pro Woche. +- Schreibt Tagesupdates ungern, wenn das Schreiben träge ist — daher CM1-Schwellwert (≤ 60 s Median). +- Will den eigenen Funnel-Snapshot stolz zeigen können — Marketing-Crosslink (FR-19) ist UI-relevant. + +### 3.4 Marketing-Kollege (Q3) +- Aktualisiert wöchentlich Kampagnen, weniger täglich. +- Die tägliche Update-Pflicht ist die ungewohnteste Anforderung — UX muss kurze „heute kein Fortschritt, aber dran"-Updates erlauben. + +### 3.5 Designerin / Buchhaltung (Q4, Q5) +- Schreiben Tagesupdates mit Bezug „Allgemein". Die UI darf sie nicht zwingen, ein Projekt zu wählen. +- Bereich = ihr eigener (Authentik-Group), Bezug-Default = „Allgemein". + +--- + +## 4. Mechanik-Detail: Wie wir „Versandung" wirklich messen + +Vier Indikatoren zusammen: + +1. **Update-Quote pro Person/Tag** — direkter Disziplin-Indikator. Sinkt sie unter 70 %, ist die Cockpit-Kultur erodiert. +2. **Median-Update-Alter pro Projekt** — steigt der Median über 7 Werktage, hat das Projekt aufgehört zu leben. +3. **Anteil Meilensteine mit Status `overdue`** — steigt er über 30 %, sind Planung und Realität auseinandergedriftet. +4. **Pflicht-Minimum-Erfüllungsquote** (M6) — die Leitfaden-Brücke. Sinkt sie, geht es nicht nur am Status, sondern an der Methodik selbst. + +Diese vier werden im Puls aggregiert und in der 4-Wochen-Retro besprochen. + +--- + +## 5. Off-Brand-Risiken des Prototyps + +Der HTML-Prototyp (`inputs/unternehmens-cockpit.html`) zeigt das gewünschte Konzept gut, trifft aber visuell nicht die Markenrichtung: + +- Hintergrund: Hellgrau (`#eceef3`) — soll weiß sein. +- Header: dunkles Navy — passt nicht zu „weiß primär, schwarz sekundär". +- Akzentfarbe: Indigo (`#4f46e5`) — durch ToolCTRL-Brand-Akzent ersetzen. +- Ampelfarben (Grün/Amber/Rot): inhaltlich nötig, an die Brand-Palette adaptieren, ohne Funktion zu verlieren. + +Die UX-Phase muss das Re-Skinning explizit durchführen — der Prototyp ist Logik-Referenz, keine Visual-Referenz. Die Voice-Anker aus dem Prototyp („Stark.", „Wie läuft es?", „Welt der Übersicht", „Willkommen, [Name]") sind zu bewahren — siehe NFR-12/13 im PRD. + +--- + +## 6. Konzept-Vokabular + +| Begriff | Bedeutung im Produkt | Quelle | +|---|---|---| +| **Puls** | Startseite — Unternehmens-Übersichtspuls. | Prototyp | +| **Area / Bereich** | Fachliche Spielwiese: Entwicklung, Vertrieb, Marketing. | Prototyp + Leitfaden | +| **Tagesupdate / DayEntry** | Kombinierter Tageseintrag einer Person: Einstempeln + ToDos + Reflexion + Ampel + Trend. | Leitfaden §5 + ToolCTRL-Workflow | +| **Einstempeln** | Morgendliche Anwesenheits-Aktion plus Anlegen der ToDo-Liste für den Tag. | ToolCTRL-Workflow | +| **ToDo** | Selbstgesetzter Tagesplan-Punkt mit optionalem Bezug, abends abhakbar. | ToolCTRL-Workflow | +| **Reflexion** | Abendlicher zweiter Touchpoint: ToDos abhaken, Freitext, Ampel, Trend. | ToolCTRL-Workflow | +| **„Mein heute"** | Persönliche Tagessicht (eigene ToDos, eigener Status) — privat während des Tages. | Cockpit-Konzept | +| **Konsolidierter Vortagseintrag** | Die für Kollegen sichtbare Version eines DayEntry, publiziert am nächsten Werktagsmorgen. | Cockpit-Konzept | +| **Bezug** | Worauf sich ein Tagesupdate bezieht: Projekt / Deal / Kampagne / Allgemein. | Prototyp | +| **Ampel** | Grün (im Plan) / Amber (Abweichung, Plan greift) / Rot (Abweichung, kein Plan). | Leitfaden §5 | +| **Trend** | besser / gleich / schlechter. | Leitfaden §5 | +| **Blocker** | Strukturiertes Hindernis, bereichsübergreifend sichtbar. | Prototyp | +| **Update-Serie / Streak** | Aufeinanderfolgende Werktage mit ≥ 1 Tagesupdate. | Prototyp | +| **Jourfixe** | Wöchentliches Treffen, vom Puls aus moderiert (Default Di 11:00). | ToolCTRL | +| **Crosslink** | Lead-Übergabe-Banner Marketing → Vertrieb. | Prototyp | +| **Bereichsübergreifend offen** | Label für die aggregierte Blocker-Liste im Puls. | Prototyp | +| **Pflicht-Minimum** | Die 6 Pflichtpunkte pro Projekt. | Leitfaden §9 | +| **Projektsteckbrief** | Eckdaten-Dokument in Outline. Das Cockpit verlinkt darauf. | Leitfaden §3 | +| **Zieldreieck** | Leistung & Umfang · Zeit · Aufwand. | Leitfaden §1 | +| **Meilenstein** | Überprüfbares Zwischenergebnis am Phasen-Ende. | Leitfaden Anhang | +| **Baseline** | Freigegebener Bezugsstand (Steckbrief nach Freigabe). | Leitfaden Anhang | +| **Scope Creep** | Schleichende, unkontrollierte Umfangserweiterung. | Leitfaden Anhang | +| **Definition of Done (DoD)** | Festlegung, wann etwas wirklich „fertig" ist. | Leitfaden Anhang | +| **Module / Issue / Sub-Issue** | Plane-Hierarchie der Arbeitszerlegung. | Leitfaden §4 | +| **Cycles / Kanban** | Zwei Arbeitsmodi der Umsetzungsphase. | Leitfaden §5 | +| **Retrospektive / Lessons Learned** | Strukturierter Rückblick am Projektende. | Leitfaden §6 | +| **Deal-Stage** | Lead → Erstgespräch → Demo → Angebot → Abschluss. | Prototyp | +| **GTM-Schritt** | Go-to-Market-Aufgabe pro Produkt. | Prototyp | +| **Lead-Funnel** | Reichweite → Website → Leads → MQL → Übergabe. | Prototyp | +| **Projektart** | Kundenprojekt oder Internes Projekt. | Leitfaden §1 | +| **Projektverantwortliche/r (P1)** | Eine Person pro Projekt, führt das Zieldreieck. | Leitfaden §7 | + +--- + +## 7. Leitfaden-Bezüge (was setzt was um) + +Tabelle zur Nachvollziehbarkeit, welche PRD-Anforderung auf welchen Leitfaden-Paragraphen zurückgeht. Hilft Architektur- und Story-Phase, die Methodik-Anbindung zu prüfen. + +| Leitfaden-Abschnitt | Im PRD umgesetzt durch | +|---|---| +| §1 Was ein Projekt ist · Projektarten | FR-8 (Projektart-Gruppierung); Datenmodell `Project.kind`. | +| §1 Zieldreieck | Implizit in Ampel + Trend + Meilenstein-Status (kein eigenes UI). | +| §2 Vier-Phasen-Zyklus | FR-35-37 (Abschluss-Workflow); UJ-5 (Anlegen). | +| §3 Projektsteckbrief | A4 (kein Editor); FR-10 (Verlinkung); FR-30 Pflicht-Punkt 1. | +| §3 SMART-Ziele | nicht explizit im Cockpit (lebt in Outline); im Leitfaden §3 als Pflicht-Inhalt des Steckbriefs referenziert. | +| §3 Stakeholder | nicht im MVP; bleibt im Outline-Steckbrief. | +| §4 Arbeit zerlegen | A5 (kein Spiegel des Plane-Boards); FR-30 Pflicht-Punkt 3. | +| §4 Meilensteine | FR-11 Cockpit-Sicht; FR-30 Pflicht-Punkt 4. | +| §4 Aufwand schätzen | Out of Scope — Kimai + Plane decken ab. | +| §4 Risiken | FR-10 (Risikoliste-URL verlinken); Liste bleibt in Outline. | +| §5 Arbeitsmodus (Cycles/Kanban) | Datenmodell `Project.arbeitsmodus`; angezeigt in FR-10. | +| §5 Rhythmus → tägliche Updates im Cockpit | gesamte Feature-Gruppe E (Einstempeln · ToDos · Reflexion). | +| §5 Status sichtbar machen (Ampel + Trend + 2-3 Sätze) | FR-26 Reflexions-Formular (Ampel, Trend, Freitext ≤ 1000 Zeichen). | +| §5 Mit Änderungen umgehen | Out of Scope MVP; ändert nur den Outline-Steckbrief. | +| §5 Reviews an Meilensteinen | FR-11 Meilenstein-Status; UJ-3 Verfall-Erkennung. | +| §6 Abschluss / Lessons Learned | FR-35-37; Pflicht-Punkt 6. | +| §7 Rollen | §4.1 PRD (Projektverantwortliche/r, Team, PO, Auftraggeber). | +| §8 Tool-Aufteilung | A3-A5 PRD; gesamtes Strategie-Framing. | +| §9 Pflicht-Minimum | Feature-Gruppe G (FR-30); M6; Puls-Sichtbarkeit. | + +--- + +*Ende Addendum.* diff --git a/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/extract-prototype-html.md b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/extract-prototype-html.md new file mode 100644 index 0000000..9f16f78 --- /dev/null +++ b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/extract-prototype-html.md @@ -0,0 +1,332 @@ +# HTML-Prototyp Extract + +Quelle: `inputs/unternehmens-cockpit.html` (1.214 Zeilen, Single-File-Vanilla-JS-Prototyp, deutsche Sprache, `TODAY="2026-05-21"` hart kodiert, Persistenz via `localStorage` Key `toolctrl_unternehmens_cockpit_v0_2`). + +Titel der App: **tool-ctrl · Unternehmens-Cockpit — v0 Prototyp** +Marker im UI: `v0 PROTOTYP` Pill im Header. + +--- + +## Top-Level-Navigation / Hauptbereiche + +Im Header gibt es **zwei Top-Level-Views** (umschaltbar via `nav button`): + +1. **Cockpit** (Default) — zeigt Unternehmens-Puls + Detail eines ausgewählten Bereichs. +2. **Tagesupdate** — ein Formular zum Erfassen eines neuen Tagesupdates. + +Innerhalb der Cockpit-View gibt es **drei Bereiche** (Areas) als Sub-Navigation via "Area-Tiles": + +- **Entwicklung** +- **Vertrieb** +- **Marketing** + +Header enthält außerdem: +- Brand-Marker (Dot + Titel + v0-Pill). +- Live-Datum "Heute · Mo, 21. Mai 2026". +- Button **"Demodaten zurücksetzen"** (cleart `localStorage` + lädt `SEED_UPDATES`). + +Davor: **Intro / Splash-Screen** (`#intro`) — animierter HUD-Kreis, "Willkommen, Haris", User-Pill (HA · Vertrieb & Management · grüner Online-Dot), Enter-Button (oder Enter-Taste), dann fade-out. + +--- + +## Module / Features (gruppiert) + +### Unternehmens-Puls (oberster Karten-Block im Cockpit) +- **Zweck:** schneller Überblick über alle drei Bereiche auf einen Blick + offene Querschnitts-Themen. +- **Datenfelder pro Area-Tile:** Name, Status-Dot (green/amber/red), Status-Pill ("Im Plan" / "Gefährdet" / "Blockiert"), zwei Key-Metrics (variabel pro Bereich), Updates-Heute-Counter `sub/exp` + Progress-Bar. +- **Area-Tile-Metriken:** + - Entwicklung: `Projekte im Plan` (z. B. 4/6), `Meilensteine gesamt` (% erledigt). + - Vertrieb: `Aktive Deals` (#), `Pipeline-Wert` (EUR). + - Marketing: `Kampagnen live` (#), `Leads an Vertrieb` (# letzter Funnel-Step). +- **UI-Patterns:** 3-Spalten-Grid mit Tiles (klickbar, `on`-State markiert ausgewählte Area), darunter zweigeteilte Foot-Sektion. +- **Foot-Sektion:** + - Links: "X von Y Tagesupdates sind heute eingetragen" + Big-Progress-Bar + **Jourfixe-Hinweis** ("Nächstes Jourfixe — Di, 26. Mai, 11:00 · übergeordnete Themen aller Bereiche", berechnet via `nextTuesday()`). + - Rechts: **"Bereichsübergreifend offen"** — rote Chips für alle aktuellen Blocker (dedupliziert per area+blocker-Text, abgeschnitten >52 Zeichen). Bei keiner Blockade grüner Text "Aktuell keine offenen Blocker." +- **Interaktionen:** Klick auf Area-Tile setzt `state.selected` und scrollt nach oben; Klick auf roten Blocker-Chip navigiert zur betroffenen Area. +- **Status:** fertig und voll funktional. + +### Bereichs-Detail: Entwicklung +- **Zweck:** Konsolidierte Sicht aller Entwicklungsprojekte mit Meilensteinen und Tagesupdates. +- **Layout:** `dhead` (Bereichs-Kopf) + 2-Spalten-Grid: **Projekt-Rail links (262px)** + **Projekt-Detail rechts**. +- **Bereichs-Kopf (`dhead`):** Status-Dot, Bereichsname "Entwicklung", Pill, Leitung (Person), Updates-Heute, Blurb-Text ("Alle 6 Produkt- und Infrastrukturprojekte mit Status, Meilensteinen und Tagesupdates — Projekt links wählen."). +- **Bereichs-Stats (4 Tiles):** `Projekte` (6 · "3 Umsatz · 3 Intern"), `Im Plan` (X/6), `Meilensteine` (X/Y + Bar), `Updates heute` (X/Y + Bar). +- **Projekt-Rail:** gruppiert in **"Umsatz"** und **"Intern"**; jedes `pitem` zeigt: Status-Dot, Projektname, nächster Meilenstein-Name, Due-Text (`in N Tagen` / `heute fällig` / `N Tage überfällig` mit warn/over-Färbung), `done/total` Meilensteine, mini-Progress-Bar. `on`-State für selektiertes Projekt. +- **Projekt-Detail (rechte Spalte):** eigener `dhead` mit Projektname (h1), Status-Pill, Gruppe-Tag ("Umsatz"/"Intern"), Leitung. Dann 4 Stats: `Status` (Pill, "aus dem letzten Update"), `Nächster Meilenstein` (Name + Datum + due-Text), `Fortschritt` (X/Y + Bar), `Updates heute` (X/Y + "Update-Serie"-Streak-Counter). +- **Darunter 2-Spalten-Grid (1fr 290px):** + - **Tagesupdates-Feed** (links): chronologisch nach Datum gruppiert, "Heute"-Marker; jedes Update zeigt Avatar mit Initialen + Personen-Farbe, Name, Rolle, Status-Pill, Text, optional Blocker-Box (rot). "+ Update hinzufügen" Button (oben rechts) prefillt Formular mit Area+Ref. + - **Meilensteine-Liste** (rechts): Checkliste mit Box, Name, Fälligkeitsdatum; Zustände `done` (grün gefüllt, durchgestrichen), `next` (lila Outline), normal. +- **Status:** fertig. + +### Bereichs-Detail: Vertrieb +- **Zweck:** Deal-Pipeline + Go-to-Market-Fortschritt je Produkt. +- **Bereichs-Stats (4):** `Aktive Deals`, `Pipeline-Wert` (EUR Summe), `Gewichtet` (Σ value × Stage-Wahrscheinlichkeit), `Gewonnen` (# + EUR). +- **Crosslink-Banner:** "X Leads hat Marketing diesen Monat in die Pipeline übergeben — Marketing-Status ansehen" (Pfeil-Icon, klickt zu Marketing). +- **Deal-Pipeline-Board (Kanban-Style):** 5 Spalten = Stages `Lead`, `Erstgespräch`, `Demo`, `Angebot`, `Abschluss`. Spaltenkopf: Stage-Label + "N Deals · €Summe". Deal-Karten: Status-Dot, Kundenname, EUR-Wert, Produkt-Tag (Caps, z. B. "HIVEGRID"), Next-Step-Text. +- **Vertriebsfortschritt je Produkt (GTM):** pro Produkt eine Karte mit Name, "X/Y Schritte", Progress-Bar, Schritt-Liste mit Box (Zustände `done`/`next`/`blocked`/`open`) + Tags ("als Nächstes" / "blockiert"). +- **Tagesupdates-Feed** (gleicher Feed-Style wie Entwicklung, aber mit `upd-ref`-Badge = Referenz zu Deal/Kampagne/"(allgemein)"). +- **Status:** fertig. + +### Bereichs-Detail: Marketing +- **Zweck:** Lead-Funnel + Kampagnen-Übersicht. +- **Bereichs-Stats (4):** `Reichweite` (#), `Leads gesamt`, `An Vertrieb` (Übergabe-Counter), `Kampagnen live` (X von Y). +- **Crosslink-Banner:** "Die N übergebenen Leads landen im Vertrieb — aktuelle Pipeline: €X" (klickt zu Vertrieb). +- **Lead-Funnel-Visualisierung:** 5 Stufen (Reichweite → Website-Besucher → Leads → Qualifiziert (MQL) → An Vertrieb übergeben). Pro Zeile: Label + Sub-Label, horizontale gefüllte Bar (Breite relativ zum Maximum, Wert eingebrannt), Conversion-% zur Vorstufe ("X% Übergang"). Letzte Zeile = "handover" mit grünem Gradient. +- **Kampagnen-Liste:** flache Liste, pro Zeile: Name + Channel (Sub-Text), Verantwortlicher, Leads-Count, Status-Pill (`läuft`, `geplant`, `ausgewertet`, `blockiert`, `pausiert` — je eigene Farbe). +- **Tagesupdates-Feed** (gleicher Feed-Style). +- **Status:** fertig. + +### Tagesupdate-Formular (View 2) +- **Zweck:** strukturierte Eingabe eines täglichen Status-Updates. +- **Layout:** 2-Spalten (Formular-Karte 1fr + Side-Nudge 300px). +- **Formular-Felder:** + - `Wann` — statisch "Heute — Mo, 21. Mai 2026" (read-only Box). + - `Wer bist du?` — `` Entwicklung/Vertrieb/Marketing. + - `Bezug` (Projekt, Deal oder Kampagne) — dynamische ` +
+
+ ${sopt("green","green","Im Plan","läuft nach Plan")} + ${sopt("amber","amber","Gefährdet","gerät in Verzug")} + ${sopt("red","red","Blockiert","brauche Hilfe")} +
+
+
+
+ + +
+

Heute noch offen

+ ${nudge} +
+

Der Rhythmus

+

Tägliche Details kommen ins Cockpit, damit jeder Bereich aktuell ist. + Dienstags um 11:00 trifft sich das Team zum Jourfixe für die übergeordneten Themen.

+
+ `; +} + +/* ===================== Render + Events ===================== */ +const app=document.getElementById("app"); +function render(){ + app.innerHTML=header()+(state.view==="cockpit"?cockpit():form()); + wire(); +} +function toast(msg){ + const t=document.getElementById("toast"); + document.getElementById("toast-msg").textContent=msg; + t.classList.add("show"); + clearTimeout(toast._t); + toast._t=setTimeout(()=>t.classList.remove("show"),3200); +} +function wire(){ + app.querySelectorAll("[data-nav]").forEach(b=>b.onclick=()=>{ state.view=b.dataset.nav; render(); }); + app.querySelectorAll("[data-area]").forEach(b=>b.onclick=()=>{ + if(b.dataset.action) return; + state.selected=b.dataset.area; state.view="cockpit"; render(); + window.scrollTo({top:0,behavior:"smooth"}); + }); + app.querySelectorAll("[data-devproject]").forEach(b=>b.onclick=()=>{ + state.devProject=b.dataset.devproject; render(); + }); + app.querySelectorAll('[data-action="prefill"]').forEach(b=>b.onclick=()=>{ + if(b.dataset.person) state.formPerson=b.dataset.person; + if(b.dataset.area){ state.formArea=b.dataset.area; state.formRef=""; } + if(b.dataset.ref) state.formRef=b.dataset.ref; + state.view="form"; render(); + const el=document.getElementById("f-text"); if(el) el.focus(); + }); + app.querySelectorAll("[data-status]").forEach(b=>b.onclick=()=>{ + syncForm(); state.formStatus=b.dataset.status; render(); afterForm(); + }); + const reset=app.querySelector('[data-action="reset"]'); + if(reset) reset.onclick=()=>{ + if(confirm("Den Prototyp auf die Beispieldaten zurücksetzen? Selbst hinzugefügte Updates gehen verloren.")){ + updates=SEED_UPDATES.map(u=>Object.assign({},u)); saveUpdates(); render(); toast("Demodaten zurückgesetzt"); + } + }; + const submit=app.querySelector('[data-action="submit"]'); + if(submit) submit.onclick=doSubmit; + if(state.view==="form") afterForm(); +} +function syncForm(){ + const p=document.getElementById("f-person"), a=document.getElementById("f-area"), + r=document.getElementById("f-ref"), tx=document.getElementById("f-text"), + bl=document.getElementById("f-blockers"); + if(p) state.formPerson=p.value; + if(a) state.formArea=a.value; + if(r) state.formRef=r.value; + if(tx) state._text=tx.value; + if(bl) state._blockers=bl.value; +} +function afterForm(){ + const tx=document.getElementById("f-text"), bl=document.getElementById("f-blockers"); + if(tx) tx.value=state._text||""; + if(bl) bl.value=state._blockers||""; + const p=document.getElementById("f-person"); if(p) p.onchange=syncForm; + const r=document.getElementById("f-ref"); if(r) r.onchange=syncForm; + const a=document.getElementById("f-area"); + if(a) a.onchange=()=>{ syncForm(); state.formRef=""; render(); afterForm(); }; +} +function doSubmit(){ + syncForm(); + const err=document.getElementById("f-err"); + const p=state.formPerson, ar=state.formArea||state.selected, ref=state.formRef||"(allgemein)", + text=(state._text||"").trim(), st=state.formStatus, bl=(state._blockers||"").trim(); + if(!p){ err.textContent="Wähl aus, wer du bist."; return; } + if(!ar){ err.textContent="Wähl einen Bereich."; return; } + if(!text){ err.textContent="Schreib, was du heute geschafft hast."; return; } + if(!st){ err.textContent="Wähl aus, wie es läuft."; return; } + updates.push({ area:ar, ref:ref, person:p, date:TODAY, status:st, text:text, blockers:bl }); + saveUpdates(); + state.selected=ar; state.view="cockpit"; + state.formStatus=""; state._text=""; state._blockers=""; state.formArea=""; state.formRef=""; + render(); + window.scrollTo({top:0,behavior:"smooth"}); + toast("Update von "+person(p).name+" erfasst · "+area(ar).name); +} + +render(); + +/* ---- Titelseite ---- */ +(function(){ + const intro=document.getElementById("intro"); + function enter(){ + if(intro.classList.contains("gone")) return; + intro.classList.add("gone"); + setTimeout(()=>{ intro.style.display="none"; },650); + } + document.getElementById("enter-btn").addEventListener("click",enter); + document.addEventListener("keydown",e=>{ if(e.key==="Enter") enter(); }); +})(); + + + diff --git a/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/prd.md b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/prd.md new file mode 100644 index 0000000..acf3409 --- /dev/null +++ b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/prd.md @@ -0,0 +1,404 @@ +--- +title: PRD – ToolCTRL Unternehmens-Cockpit +status: final +created: 2026-05-25 +updated: 2026-05-25 +owner: Dejancosic +audience: ToolCTRL (intern, alle Mitarbeiter) +binding_frame: ToolCTRL Projektmanagement-Leitfaden v3.0 (Mai 2026) +--- + +# PRD – ToolCTRL Unternehmens-Cockpit + +> **Was das ist.** Eine interne Web-Anwendung als tägliches Übersichts-Cockpit über alle laufenden Projekte und Bereiche von ToolCTRL. Sie operationalisiert den **ToolCTRL Projektmanagement-Leitfaden v3.0** als drittes Werkzeug neben **Plane** (Arbeit) und **Outline** (Gedächtnis) — im Leitfaden §5 (Rhythmus) so vorgesehen. +> +> **Wofür dieses Dokument da ist.** Es legt fest, was das Cockpit im MVP können muss. Capability-orientiert; technische Tiefe lebt im `addendum.md`. + +--- + +## 1. Vision & Problemkontext + +### 1.1 Das echte Problem +ToolCTRL hat **kein Tool-Problem, sondern ein Durchziehen-Problem.** Dank KI-gestützter Entwicklung starten wir 10+ Projekte parallel. Die Anfangsphase ist schnell und motivierend. Sobald Reibung entsteht, versandet das Projekt: Updates bleiben aus, Meilensteine schleifen, Verantwortlichkeit löst sich auf, Markt-Releases bleiben aus. + +Wir hatten Jira und Confluence im Einsatz und haben mittendrin damit aufgehört, weil wir „schneller ohne PM" sein wollten. Ergebnis: Wir bekommen nichts fertig. Wir holen die strukturierte Projektarbeit jetzt zurück — mit der **dreigeteilten Werkzeug-Logik des Leitfadens**: + +| Ebene | Werkzeug | Funktion (Leitfaden §5, §8) | +|---|---|---| +| **Arbeit** | **Plane** | Module → Issues → Sub-Issues, Boards, Cycles. *„Plane ist jederzeit die Wahrheit über den aktuellen Stand."* | +| **Gedächtnis** | **Outline** | Projektsteckbrief, Risikoliste, Status-Notizen, Entscheidungen, Lessons Learned. | +| **Übersicht / Disziplin** | **Cockpit** (dieses Produkt) | Grober Überblick über alle Projekte, tägliche Updates und Check-ins, illustrierte Unternehmensbereiche. | + +Das Cockpit ist die Antwort auf: *„Warum versanden unsere Projekte? Weil niemand täglich gezwungen ist, sichtbar zu machen, wo er steht — und weil das Leitfaden-Pflicht-Minimum schleichend erodiert."* Das Cockpit erzwingt Sichtbarkeit und macht Erosion früh erkennbar. + +### 1.2 Vision in einem Satz +**Das Cockpit ist der Ort, an dem jeder Mitarbeiter in unter 60 Sekunden sieht, woran heute gearbeitet wird, was klemmt, welche Projekte aus dem Plan laufen und welche das Pflicht-Minimum des Leitfadens nicht (mehr) erfüllen — und an dem niemand sich tagelang wegducken kann.** + +### 1.3 Warum jetzt +- „Kein PM" als Strategie ist gescheitert. +- Der Leitfaden v3.0 setzt die verbindliche Rahmen-Methodik — das Cockpit ist sein operatives Tagesgesicht. +- Plane (Tickets) und Outline (Doku) decken den *aggregierten Status* und das *tägliche Sichtbarmachen* nicht ab. +- Das Cockpit muss vor der nächsten Welle neuer Projekte stehen, damit wir nicht in dasselbe Muster fallen. + +--- + +## 2. Ziele & Anti-Ziele + +### 2.1 Ziele (was wir erreichen wollen) +- **G1** – Jeder aktive Mitarbeiter schreibt mindestens 1× pro Werktag ein Tagesupdate. Die Quote ist öffentlich sichtbar. +- **G2** – Jedes laufende Projekt hat jederzeit einen klar erkennbaren Ampelstatus (Leitfaden §5: Grün / Gelb / Rot) inkl. Trend und einen visuellen Deadline-/Meilenstein-Verlauf. +- **G3** – Bereichsübergreifende Blocker sind innerhalb von 24h für alle Beteiligten sichtbar. +- **G4** – Das Cockpit ist feste erste Anlaufstation des Jourfixe (Di 11:00) und der täglichen Routine. +- **G5** – Mehr Projekte erreichen die „Marktreife / Release"-Phase, weil Stagnation früh sichtbar wird. +- **G6** – Erfüllung des **Pflicht-Minimums** (Leitfaden §9) ist pro Projekt sofort sichtbar; nicht erfüllte Punkte sind im Puls aufgelistet. + +### 2.2 Anti-Ziele (was wir explizit NICHT bauen) +- **A1** – Kein **Ticketsystem**. Tickets bleiben in Plane. +- **A2** – Keine **Doku**. Wissens-Seiten, Steckbriefe, Risikolisten, Lessons Learned bleiben in Outline. +- **A3** – **Kein zusätzliches Projektmanagement-Tool** (Leitfaden §8: „Wir führen kein zusätzliches Projektmanagement-Tool ein."). Cockpit hält sich strikt an die Tool-Aufteilung. +- **A4** – **Kein Steckbrief-Editor.** Steckbrief lebt in Outline (Leitfaden §3). Das Cockpit verlinkt und referenziert. +- **A5** – **Kein gespiegeltes Plane-Board.** Die Issue-Liste bleibt in Plane. +- **A6** – **Keine externe Kundenkommunikation** (kein CRM, kein Kundenportal). +- **A7** – **Keine Zeiterfassung** — Kimai existiert. +- **A8** – **Kein Werkzeug zum Verkauf.** Das Cockpit ist und bleibt rein intern. +- **A9** – **Kein konfigurierbares Reporting-/BI-Dashboard** im MVP. Nur die im PRD spezifizierten Aggregate. + +--- + +## 3. Erfolgskriterien & Gegenmetriken + +### 3.1 Erfolgsmetriken +| ID | Metrik | Zielwert nach 8 Wochen Live-Betrieb | +|---|---|---| +| M1 | Anteil Werktage mit ≥1 Tagesupdate pro aktiver Person | ≥ 80% | +| M2 | Mittlere Zeit von „Blocker eingetragen" bis „im Puls sichtbar" | < 5 Minuten | +| M3 | Anteil Projekte mit aktuellem Ampelstatus (Update ≤ 3 Werktage alt) | ≥ 90% | +| M4 | Anteil Jourfixes, die das Cockpit als Hauptview nutzen | 100% | +| M5 | Aktiv genutzte Bereiche (Dev / Sales / Marketing) bekommen wöchentlich Updates | 3/3 | +| M6 | Anteil aktiver Projekte, die alle 6 Leitfaden-§9-Pflicht-Punkte erfüllen | ≥ 90% | + +### 3.2 Gegenmetriken (was wir nicht kaputt machen dürfen) +| ID | Gegenmetrik | Grenze | +|---|---|---| +| CM1 | Gesamt-Tageszeit im Cockpit pro Person (Median, Werktag) | ≤ 3 Minuten | +| CM1a | Zeit pro Morgen-Einstempeln + ToDos (Median) | ≤ 60 Sekunden | +| CM1b | Zeit pro Abend-Reflexion (Median) | ≤ 90 Sekunden | +| CM2 | Anteil inhaltsleerer Pflicht-Updates | qualitativ — Jourfixe-Diskussion | +| CM3 | Spannungs-/Lästergefühl durch „fehlt heute"-Anzeige | qualitativ — 4-Wochen-Retro | +| CM4 | Doppelpflege Cockpit ↔ Outline-Steckbrief | qualitativ — wenn Steckbrief-Felder im Cockpit dupliziert werden, justieren wir | + +> [NOTE FOR PM] CM2/CM3/CM4 sind qualitativ und werden 4 Wochen nach Launch im Jourfixe reflektiert. + +--- + +## 4. Nutzer & Rollen + +### 4.1 Leitfaden-Rollen (§7) – pro Projekt +| Rolle | Aufgabe im Cockpit-Kontext | +|---|---| +| **Projektverantwortliche/r** (P1) | Pflegt Ampelstatus + Trend, achtet auf Pflicht-Minimum, moderiert Cockpit-Sicht im Jourfixe für ihr/sein Projekt. | +| **Team** (P2) | Schreibt Tagesupdates mit Projektbezug; setzt Blocker; aktualisiert Meilenstein-Status. | +| **Product Owner** (P3, bei Produktprojekten) | Lese-Schwerpunkt; nutzt das Cockpit zur Priorisierungs-Diskussion. | +| **Auftraggeber** (P4, intern = Geschäftsführung) | Lesender Hauptnutzer für Pflicht-Minimum und Puls. | + +### 4.2 Querschnittsrollen – alle Mitarbeiter +Alle Mitarbeiter sind aktive Cockpit-Nutzer, unabhängig vom Leitfaden-Rolle-Hut: + +| Querschnitt | Hauptnutzen am Cockpit | +|---|---| +| **Entwickler** (Q1) | Tägliche Updates mit Projektbezug, Meilenstein-Status, eigene Streak. | +| **Sales / Vertrieb** (Q2) | Pflegt Deal-Pipeline + GTM-Schritte, postet Vertriebs-Updates. | +| **Marketing** (Q3) | Pflegt Lead-Funnel + Kampagnen-Status, postet Marketing-Updates. | +| **Design** (Q4) | Postet allgemeine Tagesupdates, kann Projekte als Bezug wählen. | +| **Buchhaltung** (Q5) | Postet allgemeine Tagesupdates. | + +Alle Rollen sehen alles (flaches internes Vertrauensmodell im MVP). + +### 4.3 Identität & Authentifizierung +Login erfolgt ausschließlich über **Authentik** (auth.tool-ctrl.de) per OIDC. Default-Bereich wird aus Authentik-Group-Claim abgeleitet (Mapping in OQ-1). Projektverantwortliche/r wird pro Projekt im Cockpit als Attribut gesetzt. + +--- + +## 5. User Journeys + +### UJ-1 — Tagesrhythmus: Einstempeln, Arbeiten, Reflektieren +*Primär: Q1-Q5* + +**Morgens — Einstempeln + ToDos** +1. Person öffnet das Cockpit, klickt „Einstempeln". +2. Trägt eine **ToDo-Liste für heute** ein: 1-N Items, jedes Item mit Titel und optionalem Bezug (Projekt/Deal/Kampagne/Allgemein). +3. Speichert → Person verschwindet aus „heute fehlt eingestempelt", gilt für die Streak als „heute begonnen". + +**Abends — Reflexion** +4. Person ruft im Lauf des Tages oder spätestens abends ihre ToDo-Liste auf. +5. Hakt ab, was erledigt wurde. +6. Trägt im Freitext nach, was sonst noch passiert ist (max. 1.000 Zeichen). +7. Setzt **Ampel** und — falls Bezug=Projekt vorhanden — **Trend**. +8. Optional: Blocker → bereichsübergreifende Blocker-Liste im Puls. +9. Speichert → Tag gilt als „voll abgeschlossen", Streak+1. + +**Nächster Morgen — Kollegen-Sichtbarkeit** +10. Ab dem nächsten Werktagsmorgen ist der konsolidierte Vortags-Eintrag (ToDos + Häkchen + Reflexion + Ampel + Trend) für alle Kollegen sichtbar. +11. Innerhalb des laufenden Tages sehen Kollegen nur die binäre Anwesenheit („eingestempelt: ja/nein"), nicht die ToDo-Inhalte. Reduziert In-Day-Surveillance. + +### UJ-2 — Jourfixe-Moderation +*Primär: P1, P4 (dienstags 11:00)* +1. PM öffnet Cockpit-Puls. +2. Geht die 3 Bereiche durch — Ampel pro Bereich, Top-Projekt-Status, offene Blocker, **Pflicht-Minimum-Score**. +3. Klickt auf rote/gelbe Projekte → Detail-View mit letzten Updates, Ampel + Trend, Meilenstein-Status, Deadline-Bar. +4. Diskutiert Blocker mit Verantwortlichen. + +### UJ-3 — Projekt-Verfall früh erkennen +*Primär: P1, P4* +1. Person scrollt durch den Puls. +2. Sieht in einem Bereich ein Projekt mit roter Ampel, ablaufender Deadline-Bar **oder** unerfülltem Pflicht-Minimum. +3. Klickt → Detail-View zeigt: letztes Update vor X Tagen, X von Y Meilensteinen fällig, welche Pflicht-Punkte offen sind. +4. Schreibt direkt einen Kommentar/eine Nachfrage als allgemeines Update mit Bezug=Projekt. + +### UJ-4 — Onboarding eines neuen Mitarbeiters +*Primär: Q1-Q5* +1. Mitarbeiter wird in Authentik angelegt → Cockpit-Zugriff automatisch via OIDC. +2. Erster Login: leerer „Mein Status"-Bereich + Hinweis „Hier schreibst du dein erstes Tagesupdate". +3. Default-Bereich aus Authentik-Group. +4. Schreibt das erste Update, erscheint ab sofort in Anwesenheits-/Streak-Liste. + +### UJ-5 — Projekt anlegen (Cockpit-Sicht) +*Primär: P1* +1. Person legt in Outline den Steckbrief an (Leitfaden §3). +2. Wechselt ins Cockpit → „Neues Projekt anlegen" — füllt Cockpit-Sichtdaten: Name, Verantwortliche/r, Projektart (Kundenprojekt/Internes Projekt), Arbeitsmodus (Cycle/Kanban), **Steckbrief-URL** (Outline), **Plane-Board-URL**, grobe Meilensteine. +3. Cockpit zeigt das neue Projekt in seinem Bereich und im Puls. Die Pflicht-Minimum-Häkchen werden initial bewertet (Steckbrief vorhanden ✓, Verantwortliche/r ✓ usw.). + +> [ASSUMPTION] Authentik-Groups werden für die initiale Bereichszuweisung genutzt. Mapping legen wir in der Architektur-Phase fest. + +--- + +## 6. Funktionale Anforderungen (Features) + +> Feature-Gruppen mit global durchnummerierten FR-IDs. Stabil für Architektur- und Story-Phase. + +### 6.1 Feature-Gruppe A: Unternehmens-Puls (Startseite) +**Zweck:** Eine Seite, auf der jeder in unter 60s sieht, was los ist. + +- **FR-1** Übersichtsleiste aller Bereiche (MVP: Entwicklung, Vertrieb, Marketing) inkl. aggregiertem Ampelstatus und Anzahl heute-fehlender Updates pro Bereich. +- **FR-1a** **Ampel-Aggregation pro Bereich:** *Rot*, wenn ≥1 Projekt rot ist; *Gelb*, wenn ≥1 Projekt gelb und kein rotes; *Grün*, wenn alle Projekte grün; *Grau*, wenn keine Projekte aktiv. *Pflicht-Minimum-Verstöße eines Projekts heben die Ampel mindestens auf Gelb.* +- **FR-2** Bereichsübergreifende Liste aller offenen Blocker (sortiert nach Alter). +- **FR-3** Heute-Quote: „X von Y Mitarbeitern haben heute gepingt" mit Klick → Liste der fehlenden Personen. +- **FR-4** Anzeige des nächsten Jourfixe-Termins (statisch konfigurierbar; Default Di 11:00). +- **FR-5** Pro Bereich werden folgende Key-Metrics angezeigt (verbindlicher Katalog für MVP): + - **Entwicklung:** Anzahl Projekte gesamt | im Plan (grün) | gefährdet (gelb) | blockiert (rot) | überzogene Meilensteine + - **Vertrieb:** Pipeline-Wert gewichtet (Σ value × probability) | offene Deals | gewonnene Deals (Quartal, # + EUR) | offene GTM-Schritte + - **Marketing:** Reichweite (laufende Periode) | aktive Kampagnen | Leads (Periode) | MQLs (Periode) | Übergaben an Vertrieb (Periode) +- **FR-6** Klick auf Bereichs-Kachel öffnet die Bereichs-Detail-View. +- **FR-7** **Pflicht-Minimum-Übersicht** im Puls: Liste aller aktiven Projekte mit roten/gelben Pflicht-Minimum-Punkten (siehe FR-38). + +### 6.2 Feature-Gruppe B: Bereichs-Detail Entwicklung +- **FR-8** Liste aller aktiven Projekte der Entwicklung, gruppiert nach **Projektart** (Kundenprojekt / Internes Projekt — Leitfaden §1). +- **FR-9** Pro Projekt-Kachel: Name, Verantwortliche/r (P1), Ampelstatus (Grün/Gelb/Rot) + Trend (↗︎ besser / → gleich / ↘︎ schlechter), Deadline-Progress-Bar (verstrichene Zeit / Gesamtzeit), Anzahl offener Meilensteine, Pflicht-Minimum-Score (z. B. „5/6"). +- **FR-10** Klick → Projekt-Detail mit: + - Kopfzeile: Name, Verantwortliche/r, Projektart, Arbeitsmodus (Cycle/Kanban), Ampel + Trend + - Pflicht-Minimum-Anzeige (FR-38) + - **Verlinkungen (Pflicht-Felder):** Outline-Steckbrief-URL, Plane-Board-URL, optional Risikoliste-URL + - Optional Definition of Done (Freitext, Cockpit-Sichtfeld) + - Meilenstein-Checkliste (Cockpit-Sicht — siehe FR-11) + - Update-Feed des Projekts +- **FR-11** Meilensteine werden im Cockpit als grobe Sicht gepflegt (Titel, Fälligkeit, Status: offen/erledigt/überzogen). *Wahrheit ist der Steckbrief in Outline;* Cockpit-Meilensteine sind eine reduzierte Sichtkopie. Doppelpflege wird akzeptiert, bis Phase-2-API-Sync eingeführt wird. + +### 6.3 Feature-Gruppe C: Bereichs-Detail Vertrieb +- **FR-12** 5-Spalten-Kanban: Lead → Erstgespräch → Demo → Angebot → Abschluss. Drag-and-Drop zwischen Spalten. +- **FR-13** Deal-Karten zeigen Name, Verantwortlicher, Wert (€), **Stage-Wahrscheinlichkeit (probability, 0-100%)**, Tage in aktueller Stage. +- **FR-14** **Vertriebs-Header-KPIs:** Pipeline-Wert gewichtet (Σ value × probability), gewonnene Deals (# + EUR, laufendes Quartal), aktiver Deal-Count, durchschnittliche Stage-Verweildauer. +- **FR-15** Pro Produkt eine sortierbare GTM-Schritt-Liste (Schritt, Verantwortlicher, Status). +- **FR-16** Vertrieb-Update-Feed (Filter auf Bezug=Deal oder Bezug=Vertrieb-allgemein). + +### 6.4 Feature-Gruppe D: Bereichs-Detail Marketing +- **FR-17** Horizontaler Lead-Funnel mit 5 Stufen (Reichweite → Website → Leads → MQL → Übergabe) und Conversion-Prozenten zwischen den Stufen. +- **FR-18** Liste aktiver Kampagnen mit Status (Aktiv / Pausiert / Beendet), Reichweite, Verantwortlicher. +- **FR-19** **Crosslink-Banner Marketing↔Vertrieb:** Sichtbarer Banner in Marketing & Vertrieb: „Marketing hat X Leads übergeben in [Zeitraum], Vertrieb hat daraus Y Deals erzeugt (Z €)." +- **FR-20** Marketing-Update-Feed (Filter auf Bezug=Kampagne oder Bezug=Marketing-allgemein). + +### 6.5 Feature-Gruppe E: Tagesrhythmus (Einstempeln · ToDos · Reflexion) + +> Ein „Tagesupdate" besteht aus zwei Touchpoints am gleichen Tag: **Morgen-Einstempeln mit ToDo-Liste** und **Abend-Reflexion**. Beides zusammen wird am nächsten Werktagsmorgen als konsolidierter Vortags-Eintrag für Kollegen sichtbar. + +#### 6.5.1 Morgen — Einstempeln & ToDos +- **FR-21** „Einstempeln"-Aktion auf der Puls-Seite (großer Button bei nicht eingestempelten Personen): öffnet die ToDo-Eingabe für heute. +- **FR-22** **ToDo-Liste** für den aktuellen Werktag: + - 1-N ToDo-Items + - pro Item: Titel (max. 200 Zeichen), optionaler Bezug (Projekt/Deal/Kampagne/Allgemein), optionale Bezugs-ID + - keine Ampel/Trend pro ToDo +- **FR-23** Beim Einstempeln gilt der Tag als „begonnen" — Person verschwindet aus der „heute fehlt"-Liste auf dem Puls. +- **FR-24** **Sichtbarkeit innerhalb des laufenden Tages:** + - Kollegen sehen nur „Person hat sich eingestempelt (ja/nein)" — keine ToDo-Inhalte. + - Die Person selbst sieht ihre ToDo-Liste in „Mein heute". + - Projektverantwortliche/r eines bezogenen Projekts darf die ToDos sehen, falls Bezug auf das Projekt verweist (operative Notwendigkeit). +- **FR-25** ToDos sind während des Tages bis zur Reflexion editierbar (hinzufügen / löschen / ändern). + +#### 6.5.2 Abend — Reflexion +- **FR-26** **Reflexions-Formular**: Person ruft ihre ToDo-Liste auf und füllt aus: + - Pro ToDo: ☑� erledigt / � offen / ✖� verworfen (mit optionalem Grund) + - **Reflexions-Freitext** (max. 1.000 Zeichen) — was sonst noch passiert ist + - **Status-Ampel** (Leitfaden §5 Wortlaut): + - 🟢 Grün – im Plan + - 🟡 Gelb – spürbare Abweichung, **aber** Maßnahmenplan greift + - 🔴 Rot – Abweichung **ohne** gesicherten Plan, braucht jetzt eine Entscheidung + - **Trend** (Pflicht bei Bezug=Projekt; sonst optional): ↗︎ besser / → gleich / ↘︎ schlechter + - **Blocker** (optional Freitext, erzeugt Blocker-Eintrag — siehe FR-41ff.) + - **URL** (optional, z. B. Plane-Ticket oder Outline-Seite) +- **FR-27** Nach Speichern der Reflexion ist der Tag „voll abgeschlossen". Person zählt für die Streak als voller Tag. +- **FR-28** Reflexion ist nach 30 Minuten nicht mehr editierbar. Erinnerung optional („Du hast heute noch nicht reflektiert"-Hinweis ab z. B. 17:00 Uhr — keine externe Push-Notification im MVP). + +#### 6.5.3 Konsolidierung & Sichtbarkeit +- **FR-29** Am nächsten Werktagsmorgen wird der konsolidierte Vortags-Eintrag publiziert: ToDos + Häkchen + Reflexions-Freitext + Ampel + Trend werden für Kollegen sichtbar im Bereichs-Feed und im jeweiligen Bezugs-Feed. +- **FR-30** Default-Sortierung in Feeds: neueste oben. Pagination: 50 Items pro Seite oder „Mehr laden". +- **FR-31** Eine Reflexion setzt Ampelfarbe und Trend des verlinkten Projekts/Deals/Kampagne — zuletzt eingetragener Wert gewinnt (mehrere Bezüge möglich → jeder Bezug erhält die Werte). +- **FR-32** **Rückwirkende Erfassung:** Bis zu **einem Werktag zurück** dürfen Einstempeln + Reflexion nachgetragen werden. Ältere Tage sind nicht mehr erfassbar (außer als „Abwesend" markiert, siehe FR-37). +- **FR-33** **Tages-Status pro Person** (sichtbar im eigenen Profil und im Puls für andere): + - 🟢 voll (Einstempeln + Reflexion) — zählt für Streak + - 🟡 halb (nur Einstempeln, keine Reflexion) — zählt nicht für Streak, aber Person ist „nicht durchgefallen" + - ⚪ nicht angefangen (kein Einstempeln) — fällt aus Streak + - 🟣 abwesend + +### 6.6 Feature-Gruppe F: Anwesenheit / Update-Serie (Streak) / Disziplin +- **FR-34** Pro Person wird die aktuelle **Update-Serie** (Streak) — aufeinanderfolgende Werktage mit Tages-Status „voll" — angezeigt. „Halbe" Tage brechen die Streak nicht, zählen aber nicht zur Serie. +- **FR-35** „Heute fehlt"-Liste auf der Puls-Seite (siehe FR-3); klickbar, zeigt Personen ohne Einstempeln am aktuellen Werktag nach 11:00 und Personen mit „halbem" Tag-Status nach 17:00. +- **FR-36** Werktage-Definition: Mo-Fr (Feiertage Phase 2). +- **FR-37** Eine Person kann sich für einen Tag (oder Zeitraum) als „Abwesend" markieren (Urlaub / Krankheit). Diese Tage zählen nicht gegen die Streak. + +### 6.7 Feature-Gruppe G: **Pflicht-Minimum-Check (Leitfaden §9)** +- **FR-38** Pro aktivem Projekt zeigt das Cockpit die 6 Pflicht-Punkte des Leitfadens als sichtbare Checkliste: + 1. Projektsteckbrief in Outline freigegeben → erfüllt, wenn `outline_steckbrief_url` gepflegt **und** als „freigegeben" markiert ist + 2. Projektverantwortliche/r gesetzt → erfüllt, wenn P1 zugewiesen + 3. Plane-Board mit zerlegter Arbeit → erfüllt, wenn `plane_project_url` gepflegt + 4. Meilensteine mit groben Terminen → erfüllt, wenn ≥1 Meilenstein definiert + 5. Status-Ampel aktuell → erfüllt, wenn letzte Reflexion mit Bezug=Projekt ≤ 7 Werktage alt + 6. Abschluss-Retrospektive — *nur bei beendeten Projekten relevant; aktiv = n/a* +- **FR-39** Im MVP werden Pflicht-Punkte 1, 2, 3, 4 als manuelle Bestätigungs-Felder pro Projekt geführt (Cockpit-Sicht, Doppelpflege akzeptiert). Punkt 5 wird automatisch aus dem Reflexions-Zeitstempel abgeleitet. Punkt 6 wird beim Abschluss-Workflow gesetzt. +- **FR-40** Projekte mit ≥1 unerfülltem Pflicht-Punkt erscheinen prominent auf der Puls-Seite (siehe FR-7). + +### 6.8 Feature-Gruppe H: Blocker +- **FR-41** Ein Blocker entsteht entweder als Feld in einer Reflexion oder separat über „Neuer Blocker"-Button. +- **FR-42** Blocker tragen: Titel, Beschreibung, Verantwortlicher (zur Lösung), Ersteller, Bereich(e), Datum offen, Status (offen / gelöst). +- **FR-43** Blocker erscheinen auf der Puls-Seite, in den betroffenen Bereichs-Detail-Views und in der Projekt-Detail-View (falls projektgebunden). +- **FR-44** Auflösung: Auflöser, Auflöse-Datum und Auflösungs-Kommentar werden persistiert. **Nur die/der Projektverantwortliche (P1) des bezogenen Projekts darf einen Blocker als gelöst markieren.** Bei Blockern ohne Projektbezug darf die/der Ersteller/in oder Verantwortliche/r des Blockers selbst auflösen. + +### 6.9 Feature-Gruppe I: Projekt-Abschluss +- **FR-45** Projektverantwortliche/r kann ein Projekt im Cockpit auf „abgeschlossen" setzen. +- **FR-46** Beim Abschluss prüft das Cockpit Pflicht-Minimum-Punkt 6 (Lessons-Learned-URL) — wenn fehlend, fordert es die URL an. +- **FR-47** Abgeschlossene Projekte verschwinden aus dem Puls, bleiben aber in einer „Archiv"-Sicht pro Bereich lesbar. + +### 6.10 Feature-Gruppe J: Navigation, Suche & Filter +- **FR-48** Globale Top-Navigation: Puls / Entwicklung / Vertrieb / Marketing / „Mein heute" / Eigenes Profil / Archiv. +- **FR-49** Volltextsuche über Updates → Phase 2. Im MVP genügt Filterung im Feed nach Bereich/Bezug. + +--- + +## 7. Nicht-funktionale Anforderungen (NFRs) + +### 7.1 Performance +- **NFR-1** Puls-Seite lädt vollständig in < 1,5 s bei 6-15 aktiven Nutzern und 90 Tagen Historie. +- **NFR-2** Tagesupdate-Speichern dauert < 500 ms (Server-Roundtrip). + +### 7.2 Verfügbarkeit +- **NFR-3** Zielverfügbarkeit Mo-Fr 8-19 Uhr: 99% (kein 24/7-SLA). +- **NFR-4** Geplante Wartungsfenster außerhalb der Kernzeit kommunizierbar. + +### 7.3 Sicherheit & Privacy +- **NFR-5** Alle Routen sind hinter Authentik-OIDC abgesichert; kein anonymer Zugriff. +- **NFR-6** Keine PII von Externen (Deal-Karten enthalten nur intern bekannte Firmennamen, keine Klar-Personendaten). +- **NFR-7** Audit-Spur: jedes mutierende Event (Update, Blocker, Meilenstein-Statuswechsel, Pflicht-Minimum-Änderung) hat `actor`, `timestamp`, `payload`. + +### 7.4 Bedienbarkeit & Visual +- **NFR-8** Brand-konform: primär weiß, sekundär schwarz, gemäß ToolCTRL Brand Identity. Konkrete Design-Tokens in der UX-Phase. +- **NFR-9** Tagesupdate-Schreib-Workflow ist in ≤ 3 Klicks ab Puls erreichbar. +- **NFR-10** Desktop-first; mobile-Responsiveness ist Phase 2 (mobil-lesbar ist Pflicht, mobil-schreibbar nicht). +- **NFR-11** Sprache: Deutsch durchgängig im UI. Keine i18n im MVP. + +### 7.5 Voice & Tone (Mikrocopy) +- **NFR-12** Tonalität ist **locker, direkt, deutsch, kurze Imperative.** Beispiele aus dem Prototyp als Voice-Anker: + - „Stark." + - „Wie läuft es?" + - „Du hast heute noch nicht gepingt." + - „Willkommen, [Name]" +- **NFR-13** Keine Corporate-Sprech-Phrasen („wir möchten Sie darauf hinweisen", „im Zuge der Optimierung"). Keine englischen Floskeln im Fließtext der UI (Fachbegriffe wie „Backlog", „Kanban" sind okay — sie kommen aus dem Leitfaden). + +### 7.6 Datenhaltung +- **NFR-14** Persistenz in **Postgres**. Kein localStorage-only. +- **NFR-15** Tägliches Backup (Mechanik in Architektur-Phase). + +### 7.7 Erweiterbarkeit +- **NFR-16** Datenmodell muss Phase-2-Erweiterungen ohne mehr als eine zweistündige Migration tragen: zusätzliche Bereiche (Design, Buchhaltung), Plane/Outline-API-Sync, Kommentare auf Updates, Mobile, Feiertags-Kalender. + +--- + +## 8. Out of Scope (MVP) / Phase-2-Kandidaten + +| Item | Begründung | +|---|---| +| Plane-API-Sync (Ticket-Status, Owner, Titel ziehen; Meilensteine auto-erkennen) | Komplexität verzögert MVP; URL-Link reicht zunächst. | +| Outline-API-Sync (Steckbrief-Status „freigegeben" auto-erkennen) | Selbe Begründung; manuelle Bestätigung reicht. | +| Outline-Embed | Verlinkung genügt. | +| Design-Bereich, Buchhaltung-Bereich (eigene Detail-Views) | Beide nutzen „Allgemein" im MVP. | +| Mobile-App / Responsive Mobile-UI (schreibend) | Desktop deckt 95% der täglichen Nutzung ab; mobil bleibt lesbar. | +| Volltextsuche über Updates | Filter im Feed reicht im MVP. | +| Reaktionen / Kommentare auf Updates | Nice-to-have, kein Kernproblem. | +| Reminders per Element/Matrix/Mail bei fehlendem Update | Cockpit-Visualisierung zuerst beweisen, dann pushen. | +| Konfigurierbare Reporting-/BI-Dashboards | Andere Werkzeuge geeigneter. | +| Zeiterfassung | Kimai existiert. | +| Mehrsprachigkeit | Nicht nötig. | +| Feiertags-Kalender (für Streak-Berechnung) | MVP: Mo-Fr ohne Feiertags-Sonderfall. | + +--- + +## 9. Offene Fragen + +| ID | Frage | Wer entscheidet | Bis wann | Phase-Blocker? | +|---|---|---|---|---| +| OQ-1 | Genaues Mapping Authentik-Groups → Cockpit-Bereiche? | Architektur-Phase | Vor MVP-Bau | ja | +| OQ-2 | Welche konkreten Brand-Tokens (Hex, Typografie, Logo-Verwendung) gelten 1:1? | UX-Phase | Vor UI-Bau | ja | +| OQ-5 | Archivierte Projekte: Historie für immer behalten oder nach X Monaten löschen? | Architektur + Product | Vor MVP-Bau | nein | +| OQ-8 | Erinnerungszeit für „du hast noch nicht reflektiert"-Hinweis (Default 17:00)? | Product (Dejan) | Vor Story-Bau | nein | +| OQ-9 | Was passiert, wenn eine Person die ToDo-Liste morgens leer lässt — ist „eingestempelt mit 0 ToDos" gültig oder muss ≥1 ToDo gesetzt sein? PRD-Default: ≥1 ToDo Pflicht. | Product (Dejan) | Vor MVP-Bau | nein | +| OQ-10 | Darf eine Person ToDos *nach* dem Abend-Reflektieren noch nachtragen (z. B. „bin um 10 noch zur Apotheke gefahren")? PRD-Default: nein, nach Reflexion ist der Tag geschlossen. | Product (Dejan) | Vor Story-Bau | nein | + +**Beschlossene OQs (vorher offen, jetzt im PRD verankert):** +- ~~OQ-3~~ → Rückwirkende Erfassung **max 1 Werktag zurück** (FR-32). +- ~~OQ-4~~ → Nur P1 (oder Blocker-Ersteller/Verantwortlicher bei projektlosen Blockern) darf auflösen (FR-44). +- ~~OQ-6~~ → Pflicht-Minimum-Punkt 1 als manuelle Cockpit-Checkbox im MVP (FR-39). +- ~~OQ-7~~ → Trend **Pflicht bei Bezug=Projekt**, sonst optional (FR-26). + +--- + +## 10. Annahmen-Index + +Sammlung aller im PRD getroffenen Annahmen — zur expliziten Bestätigung im Architektur- bzw. UX-Lauf: + +- **A-1** Authentik-OIDC-Integration ist technisch in < 1 Tag erledigt (Erfahrung aus Plane/Outline-Setup). +- **A-2** Authentik-Groups werden für die initiale Bereichszuweisung genutzt; Mapping wird in der Architektur-Phase festgelegt. +- **A-3** Team wächst in den nächsten 12 Monaten nicht über 30 Personen. +- **A-4** Postgres + ein leichter Backend-Layer reichen aus; konkrete Backend-Wahl in Architektur-Phase. +- **A-5** Pflicht-Minimum-Punkte 1-4 werden im MVP manuell im Cockpit bestätigt (kein Outline-/Plane-API-Sync). +- **A-6** Doppelpflege Cockpit-Meilenstein ↔ Outline-Steckbrief ist im MVP akzeptiert; wird beobachtet (CM4). + +--- + +## 11. Risiken + +| Risiko | Mitigation | +|---|---| +| Das Cockpit wird selbst zu einem „starten und abbrechen"-Projekt. | MVP-Scope hart klein halten. Erstes Release in 4 Wochen anpeilen. Selbst-Dogfooding ab Tag 1. **Das Cockpit selbst muss das Pflicht-Minimum erfüllen.** | +| Daily-Update-Pflicht erzeugt Lästergefühl. | CM2/CM3 messen; 4-Wochen-Retro; Mechanik justierbar. | +| Cockpit erodiert die Tool-Aufteilung des Leitfadens (wird Schatten-Plane oder Schatten-Outline). | A1-A5 + CM4 wachen darüber. Architektur-Phase prüft den Schnitt explizit. | +| Sales-Daten sind sensibel und sollen evtl. nicht jeder sehen. | MVP: flaches Vertrauen, alle sehen alles. Bei Bedarf in Phase 2 Rollen-Sichtbarkeit. | +| Pflicht-Minimum-Häkchen werden „durchgewinkt" und verlieren Bedeutung. | Jourfixe diskutiert Stichproben. Phase 2: Outline-/Plane-API-Validierung. | + +--- + +## 12. Erfolgskontrolle & Review-Punkte + +- **Woche 2 nach Launch** – kurzer Puls-Check im Jourfixe: M1 erreicht? +- **Woche 4 nach Launch** – Retro: CM2/CM3/CM4. Adjust falls nötig. +- **Woche 8 nach Launch** – Vollauswertung gegen M1-M6. Entscheidung: Phase 2 starten oder MVP weiter härten? + +--- + +*Ende PRD. Begleitend: `addendum.md` (technische Tiefe, verworfene Alternativen, Leitfaden-Bezüge, In-Depth-Personas) und `.decision-log.md` (Audit-Trail).* diff --git a/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/reconcile-prototype.md b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/reconcile-prototype.md new file mode 100644 index 0000000..dbb5fa4 --- /dev/null +++ b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/reconcile-prototype.md @@ -0,0 +1,248 @@ +# Reconciliation: HTML-Prototyp ↔ PRD + Addendum + +Quelle (Prototyp): `inputs/unternehmens-cockpit.html` (verdichtet in `extract-prototype-html.md`) +Geprüft gegen: `prd.md` + `addendum.md` +Datum: 2026-05-25 + +--- + +## Vollständig abgedeckt + +### Hauptstruktur & Navigation +- **Cockpit als Default-View** → PRD §1.1 (Tabelle), §6.1 (Feature-Gruppe A: Puls als Startseite), FR-32 (Top-Nav inkl. "Puls") +- **Tagesupdate als zweite Top-Level-View** → FR-32 ("Neues Tagesupdate" in Top-Nav), FR-19 (Formular), UJ-1 +- **Drei Areas: Entwicklung / Vertrieb / Marketing** → PRD §6.1 (FR-1), §6.2/§6.3/§6.4 (eigene Feature-Gruppen), Addendum §6 (Vokabular) +- **Live-Datum im Header** → implizit via "Heute"-Logik (FR-3, FR-25) + +### Unternehmens-Puls +- **Aggregierter Ampelstatus pro Area** → FR-1 +- **Bereichsübergreifend offene Blocker** → FR-2, FR-30, Risiko-Modell §10 +- **Heute-Quote ("X von Y haben gepingt")** → FR-3 +- **Jourfixe-Hinweis (Di 11:00)** → FR-4, Vision G4, Addendum §6 +- **Key-Metrics pro Area-Tile (3-5 Metrics)** → FR-5 +- **Klick auf Area-Tile öffnet Detail-View** → FR-6 + +### Entwicklung +- **Projekt-Liste mit Gruppierung Umsatz/Intern** → FR-7 +- **Status-Pill je Projekt (abgeleitet aus letztem Update)** → FR-8, FR-23 +- **Deadline/Meilenstein-Progress** → FR-8, FR-10 +- **Projekt-Detail mit Meilenstein-Checkliste + Update-Feed** → FR-9 +- **Meilensteine mit Titel/Fälligkeit/Status** → FR-10 +- **CRUD für Projekte/Meilensteine** → FR-11 (Prototyp hatte nur Read; PRD erweitert bewusst) + +### Vertrieb +- **5-Spalten Kanban (Lead → Erstgespräch → Demo → Angebot → Abschluss)** → FR-12, Addendum §6 (Deal-Stage) +- **Deal-Karten mit Name/Owner/Wert** → FR-13 +- **GTM-Schritt-Liste pro Produkt** → FR-14, Addendum §6 +- **Vertrieb-Update-Feed** → FR-15 +- **Crosslink zu Marketing (Lead-Übergabe)** → implizit über FR-30 (Blocker bereichsübergreifend) + UJ-2, allerdings nicht prominent (siehe Gaps) + +### Marketing +- **Lead-Funnel mit 5 Stufen + Conversion-Raten** → FR-16, Addendum §6 +- **Kampagnen-Liste mit Status** → FR-17 +- **Marketing-Update-Feed** → FR-18 + +### Tagesupdates +- **Formular mit Person/Bereich/Bezug/Freitext/Ampel/Blocker** → FR-19 +- **Bezug = Projekt/Deal/Kampagne/Allgemein** → FR-19, Addendum §6 +- **Chronologische Anzeige in Area- und Bezug-Feed** → FR-21 +- **Update setzt Ampelfarbe des Referenzobjekts** → FR-23 +- **Optionale URL als Zusatz-Bezug** → FR-22 (Erweiterung gegenüber Prototyp) +- **Edit-Fenster (Prototyp: kein Edit)** → FR-20 (PRD lockert bewusst auf 30min) +- **Validierung (Pflichtfelder)** → implizit in FR-19 + +### Disziplin / Streak +- **Streak-Counter pro Person/Projekt** → FR-24, Vokabular im Addendum +- **"Heute fehlt"-Liste / Side-Nudge** → FR-3, FR-25 +- **Werktage-Definition** → FR-26 +- **Abwesenheit / Urlaub-Marker** → FR-27 (Prototyp hatte das nicht — PRD erweitert) + +### Blocker +- **Blocker als Feld in Update** → FR-28 +- **Bereichsübergreifend sichtbar** → FR-2, FR-30 +- **Standalone-Blocker-Erstellung** → FR-28 (PRD erweitert gegenüber Prototyp) +- **Blocker mit Owner/Status/Auflösung** → FR-29, FR-31 + +### Status-System (Ampel) +- **3 Status: Im Plan / Gefährdet / Blockiert** → FR-19, Addendum §6 +- **green/amber/red Mapping** → Datenmodell-Skizze in Addendum §1.2 +- **Status-Rollup auf Area** → FR-1 (aggregierter Status) + +### Identität +- **Personen mit Name/Rolle** → PRD §4.1 (Rollen-Tabelle), Datenmodell User in Addendum §1.2 +- **OIDC-Auth (Prototyp hatte keine Auth — bewusste Erweiterung)** → PRD §4.2, NFR-5, Addendum §1.3 + +### Datenmodell (impliziert) +- **TEAM/AREA/PROJECT/DEAL/CAMPAIGN/UPDATE/MILESTONE/STAGE/FUNNEL/GTM** → Addendum §1.2 mit allen wesentlichen Entitäten + +--- + +## Bewusst weggelassen (mit Begründung im PRD) + +- **localStorage-only Persistenz** → Addendum §2.5 (explizit verworfen, Postgres stattdessen, NFR-12) +- **Single-User-Modell ohne Auth** → PRD §4.2 (Authentik-OIDC), Addendum §2.5 +- **Intro/Splash-Screen ("Willkommen, Haris")** → nicht in MVP-Scope; nicht explizit als Anti-Ziel formuliert, aber als kosmetische Prototypen-Animation klar nicht produktiv (vgl. Addendum §5: Prototyp = Logik-Referenz, kein Visual-Referenz) +- **"Demodaten zurücksetzen"-Button** → reines Prototypen-Feature; im MVP gegen echte Daten irrelevant +- **TODAY hartcodiert** → trivial weggelassen, MVP nutzt echtes Datum +- **Hardcoded Produkte (HiveGrid/Asset Control/etc.)** → werden zu echten DB-Datensätzen (Datenmodell Project/GtmStep) +- **Off-Brand Visuals (Indigo, Navy, Hellgrau)** → Addendum §5 explizit als Off-Brand-Risiko markiert; NFR-8 verlangt Brand-Konformität +- **Detail-Views für Design/Buchhaltung** → Addendum §2.6 (verworfen, weil keine KPIs definiert); PRD §8 Phase 2 +- **Volltextsuche** → PRD §8, FR-33 als Phase 2 +- **Mobile/Responsive** → PRD §8, NFR-10 (desktop-first) +- **Reaktionen/Kommentare auf Updates** → PRD §8 +- **Plane-API-Sync / Outline-Embed** → PRD §8, Addendum §1.4 als Phase 2 +- **Reminders per Mail/Element** → PRD §8 +- **BI/Reporting-Dashboards** → PRD Anti-Ziel A6, §8 +- **Zeiterfassung** → PRD Anti-Ziel A4 + +--- + +## Gaps (potenziell vergessen — Empfehlung: aufnehmen oder bewusst rejizieren) + +### Mittlere Priorität + +1. **Won-Deals als KPI im Vertriebs-Header** + - Prototyp: KPI-Tile "Gewonnen (# + EUR)" in Vertriebs-Bereichsstats. + - PRD: nicht erwähnt. FR-13 zeigt nur Deal-Karten, kein Won-Aggregat. + - Empfehlung: als KPI aufnehmen oder explizit rejizieren (z. B. "Won-Deals werden nicht separat geführt; abgeschlossene Deals verschwinden aus Kanban"). + +2. **Gewichteter Pipeline-Wert (Σ value × Stage-Wahrscheinlichkeit)** + - Prototyp: explizite KPI mit Wahrscheinlichkeiten pro Stage (0.10/0.25/0.50/0.75/0.90). + - PRD: nicht erwähnt; Stage hat keine Wahrscheinlichkeit im Datenmodell. + - Empfehlung: in FR-12/13 ergänzen oder als Phase 2 vermerken. Konsequenz für Datenmodell: Stage braucht `probability`-Feld. + +3. **Crosslink-Banner Marketing↔Vertrieb (Lead-Übergabe)** + - Prototyp: prominenter Crosslink "X Leads hat Marketing diesen Monat in die Pipeline übergeben" auf Vertriebs- und Marketings-Detail-View. + - PRD: implizit via Funnel-letzte-Stufe (FR-16), aber kein expliziter Cross-Area-Link als FR. + - Empfehlung: als FR aufnehmen — der Crosslink ist eine sichtbare Verbindung, die "Areas reden miteinander" konkret macht. Vibe-relevant. + +4. **Streak nicht nur pro Person, sondern pro Projekt ("Update-Serie")** + - Prototyp: Projekt-Detail zeigt eigenen Streak-Counter "Tage am Stück mit Update". + - PRD: FR-24 nennt Streak nur pro Person. + - Empfehlung: in FR-24 erweitern oder als separates FR ergänzen — Projekt-Streak ist ein gutes Versandungs-Frühwarnsystem (deckt sich mit Addendum §4 Indikator 2: "Median-Update-Alter pro Projekt"). + +5. **Bereichs-Stats-Kacheln in Detail-Views (4er-Grid mit "Projekte/Im Plan/Meilensteine/Updates heute")** + - Prototyp: jede Area-Detail-View hat 4 Stats-Tiles als sekundärer Übersichtskopf. + - PRD: nicht spezifiziert, nur einzeln in FR-5 (Puls). Detail-Views haben keine Stat-Tile-Anforderung. + - Empfehlung: in FR-7/12/16 als "Bereichskopf mit 4 Kennzahlen" ergänzen — sonst gehen Detail-Views in Wireframes verloren. + +### Niedrigere Priorität (qualitativ / UX-Feinheiten) + +6. **Due-Date-Färbung (warn = amber ≤4 Tage, over = rot überfällig)** + - Prototyp: konkretes UI-Pattern in Projekt-Rail und Meilensteinen. + - PRD: NFR-/FR-Ebene zu generisch — keine explizite Färblogik. + - Empfehlung: in UX-Phase festhalten; nicht zwingend im PRD, aber sollte als "Pattern" im Addendum §6 oder UX-Brief landen. + +7. **Avatar mit Initialen + Personen-Farbe als wiederkehrendes UI-Atom** + - Prototyp: starkes Identitäts-Element, durchgängig in Feed-Items. + - PRD: nicht erwähnt — nur "Person" als Feld. + - Empfehlung: UX-Phase-Detail; jeder User braucht `color` im Datenmodell. + +8. **"Bereichsübergreifend offen"-Chip-Liste als Puls-Element** + - Prototyp: explizit rechte Foot-Sektion des Puls. + - PRD: FR-2 nennt nur "Liste"; das Pattern als rote Chips mit Klick→Area ist nicht spezifiziert. + - Empfehlung: in FR-2 spezifizieren oder UX-Phase. + +9. **Kampagnen-Status "pausiert"** + - Prototyp: 5 Status (läuft / geplant / ausgewertet / blockiert / pausiert). + - PRD/Addendum: FR-17 nennt nur 3 (Aktiv / Pausiert / Beendet); Addendum Datenmodell hat (active|paused|done). + - Empfehlung: Status-Set vereinheitlichen. Entweder Prototyp-Set übernehmen (granularer) oder explizit auf 3 Status reduzieren. + +10. **Toast-Komponente für Bestätigungs-Feedback** + - Prototyp: explizit nach Update-Submit. + - PRD: nicht erwähnt. + - Empfehlung: UX-Phase; trivial, sollte aber bewusst entschieden sein. + +11. **Side-Nudge "Der Rhythmus"-Erklärtext im Tagesupdate-Formular** + - Prototyp: rechte Spalte im Formular mit Erklärtext zum Rhythmus + Jourfixe. + - PRD: nicht spezifiziert; PRD-Tonalität ist eher knapp. + - Empfehlung: bewusst entscheiden — passt zur "Disziplin"-Vision und macht den Sinn des Updates explizit (gut für Onboarding, UJ-4). + +### Konzeptionelle Lücken + +12. **Vibes/Onboarding-Splash "Welt der Übersicht"** + - Prototyp: Splash-Screen mit personalisierter Begrüßung ("Willkommen, Haris"). + - PRD: UJ-4 nennt nur "leerer Mein-Status-Bereich + Hinweis". + - Empfehlung: Tonalität (locker, persönlich, motivierend) bewusst entscheiden — wenn das Cockpit "Disziplin ohne Druck" sein soll, könnte ein personalisierter Empfang Wirkung haben. Im PRD nicht erwähnt. + +13. **Kein expliziter "Tonalität / Voice"-Abschnitt im PRD** + - Prototyp-Texte sind eher locker-geschäftlich ("Alle Tagesupdates sind da. Stark.", "läuft nach Plan", "brauche Hilfe"). + - PRD spricht nüchtern in Capability-Sprache; keine Voice-/Tonalitäts-Vorgabe. + - Empfehlung: kurzen "Voice & Tone"-Abschnitt im UX-Brief — siehe eigener Abschnitt unten. + +--- + +## Vokabular-Konsistenz + +| Begriff | Prototyp | PRD | Addendum | Status | +|---|---|---|---|---| +| **Puls / Unternehmens-Puls** | ✓ Hauptbegriff | ✓ FR-1..5 | ✓ §6 | konsistent | +| **Areas / Bereich** | ✓ beide | ✓ "Area" und "Bereich" gemischt | ✓ "Area / Bereich" | konsistent (Doppelbegriff) | +| **Ampel** | ✓ | ✓ ("Ampelstatus") | ✓ | konsistent | +| **Im Plan / Gefährdet / Blockiert** | ✓ | ✓ FR-19 | ✓ §6 | konsistent | +| **Bezug** | ✓ | ✓ FR-19 | ✓ §6 | konsistent | +| **Streak / Update-Serie** | ✓ beide nebeneinander | ✓ "Streak" (FR-24) | ✓ "Streak" | **Inkonsistenz:** "Update-Serie" als Synonym fehlt im PRD/Addendum | +| **Jourfixe** | ✓ | ✓ FR-4, G4 | ✓ §6 | konsistent | +| **Meilenstein** | ✓ | ✓ FR-8, FR-10 | ✓ §6 | konsistent | +| **Deal-Stage / Stages** | ✓ | ✓ FR-12 | ✓ "Deal-Stage" §6 | konsistent | +| **GTM / GTM-Schritt** | ✓ "GTM" und "Vertriebsfortschritt je Produkt" | ✓ FR-14 ("GTM-Schritt-Liste") | ✓ §6 | konsistent (PRD nutzt "GTM-Schritt" sauber) | +| **Lead-Funnel** | ✓ | ✓ FR-16 | ✓ §6 | konsistent | +| **MQL** | ✓ | ✓ FR-16 | ✓ §6 | konsistent | +| **Blocker** | ✓ | ✓ Feature-Gruppe G | ✓ §6 | konsistent | +| **Bereichsübergreifend offen** | ✓ | (sinngemäß FR-2 "bereichsübergreifende Liste") | – | **Begriff fehlt wörtlich** — sollte im PRD/Addendum als Label übernommen werden | +| **Crosslink** | ✓ | – | – | **Begriff fehlt** (Konzept ist nur implizit) | +| **Umsatz / Intern (Projekt-Gruppen)** | ✓ | ✓ FR-7 | ✓ §1.2 (`group umsatz|intern`) | konsistent | +| **Pipeline-Wert / Gewichteter Wert** | ✓ beide | – | – | **fehlt** (siehe Gap #2) | +| **Reichweite** | ✓ | ✓ FR-16 | – | konsistent | +| **Übergabe / An Vertrieb übergeben** | ✓ | ✓ FR-16 ("Übergabe") | ✓ "Übergabe" §6 | konsistent | +| **Kampagne / Kampagnen-Status** | ✓ 5 Status | ✓ 3 Status (FR-17) | ✓ 3 Status | **Inkonsistenz im Status-Set** (siehe Gap #9) | +| **Pflichtenheft** | ✓ Prototyp-Kontext | – | – | nicht übernommen; vermutlich projektspezifisch, kein Produkt-Vokabular | +| **Demodaten** | ✓ | – | – | bewusst weggelassen (Prototyp-Spezifikum) | + +### Empfehlungen Vokabular +- "Bereichsübergreifend offen" als Label in FR-2 verankern. +- "Update-Serie" als deutscher Synonym-Begriff zu "Streak" im Addendum §6 ergänzen — der Prototyp nutzt beide nebeneinander. +- "Crosslink" als Konzept im Addendum §6 aufnehmen, falls Gap #3 angenommen wird. +- Kampagnen-Status-Set zwischen Prototyp (5) und Addendum (3) auflösen. + +--- + +## Tonalität / Vibe + +### Beobachtungen Prototyp +Der Prototyp hat einen sehr spezifischen Voice-Charakter: +- **Locker-geschäftlich:** "Alle Tagesupdates sind da. Stark." | "Aktuell keine offenen Blocker." | "Stark." als Erfolgs-Quittung. +- **Persönlich-direkt:** "Willkommen, Haris" | "Was hast du heute geschafft?" | "Wie läuft es?" | "Etwas, das dich blockiert oder wobei du Hilfe brauchst?" +- **Konkret statt abstrakt:** "in 3 Tagen", "heute fällig", "2 Tage überfällig" — Mensch-lesbare Datumsangaben statt ISO-Strings. +- **Bilder/Metaphern:** "Puls", "Welt der Übersicht" (im Header-Konzept), "Rhythmus" (Side-Nudge im Formular), "Areas" (englisch eingedeutscht). +- **Kein Corporate-Sprech:** keine "KPIs", keine "Stakeholder", keine "Initiativen" im UI-Copy. + +### Was PRD/Addendum vom Vibe einfangen +- PRD hat in §1 selbst einen klaren "Voice": *"kein Tool-Problem, sondern ein Durchziehen-Problem"* | *"niemand sich tagelang wegducken kann"* | Vision §1.2 ist sehr direkt. **Das ist gut.** +- Anti-Ziele §2.2 sind in derselben direkten Tonalität gehalten. +- Persona-Beschreibungen in Addendum §3 fangen Tonalität ("Lästert intern", "will stolz zeigen") und Persönlichkeit gut ein. + +### Was verloren geht +- **"Welt der Übersicht"** als emotionaler Header-Claim ist nicht im PRD prominent. Wenn dieser Anspruch ("Übersicht als Welt, in der man sich aufhält") tragend sein soll, gehört er in §1.2 (Vision) oder als eigener Abschnitt "Voice & Tone". +- **Personalisierter Empfang ("Willkommen, [Name]")** ist im Onboarding (UJ-4) zu nüchtern beschrieben — verliert den "Mensch wird begrüßt"-Vibe. +- **Konkrete Mensch-Sprache in UI-Copy** (z. B. "Wie läuft es?", "brauche Hilfe", "Stark.") ist nirgends als Voice-Anweisung dokumentiert. Risiko: ein Implementations-Team baut "Status auswählen" statt "Wie läuft es?" — funktional richtig, vibe-mäßig tot. +- **Rhythmus-Erklärtext** (Side-Nudge "Der Rhythmus") als pädagogisches Element ist nicht spezifiziert — das erzählt User aber, warum tägliches Update überhaupt Sinn macht. +- **Lockere Quittungen** ("Stark.", "Alle Tagesupdates sind da.") sind FR-frei und drohen, im Bau durch "Update erfolgreich gespeichert." ersetzt zu werden. + +### Empfehlung +Ein expliziter Abschnitt **"Voice & Tone"** in PRD oder Addendum mit: +- Beispiel-Copy aus Prototyp als Referenz ("Stark.", "Wie läuft es?", "Willkommen, [Name]"). +- Verbot von Corporate-Sprech in UI (keine "KPIs", keine "Submit"-Buttons — stattdessen "Tagesupdate absenden"). +- Mensch-lesbare Datumsangaben Pflicht ("in 3 Tagen", nicht "2026-05-28"). +- Personalisierung mit Vornamen wo sinnvoll. +- Header-Claim "Welt der Übersicht" oder vergleichbar als emotionale Klammer entscheiden (übernehmen oder bewusst rejizieren). + +--- + +## Zusammenfassung + +- **Vollständig abgedeckt:** ~50 Items (alle Haupt-Features, Datenmodell-Entitäten, Hauptbegriffe). +- **Bewusst weggelassen mit Begründung:** ~13 Items (Prototyp-Spezifika, Phase-2-Kandidaten, Off-Brand-Visuals, Anti-Ziele). +- **Gaps zur Diskussion:** 13 Items — davon 5 mittlere Priorität (Won-Deals, gewichteter Pipeline-Wert, Crosslinks, Projekt-Streak, Detail-View-Stats) und 8 UX-/Vibe-Lücken. +- **Vokabular:** überwiegend konsistent; vier kleinere Lücken (Update-Serie, Bereichsübergreifend offen, Crosslink, Kampagnen-Status-Set). +- **Tonalität:** PRD hat eigenen guten Voice für Strategie/Anti-Ziele, aber UI-Copy-Tonalität des Prototyps ("Stark.", "Wie läuft es?", personalisierter Empfang) ist nirgends als Voice-Anweisung dokumentiert. Empfehlung: "Voice & Tone"-Abschnitt nachziehen. diff --git a/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/review-rubric.md b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/review-rubric.md new file mode 100644 index 0000000..4dc0d4d --- /dev/null +++ b/_bmad-output/planning-artifacts/prds/prd-projectmanagement-2026-05-25/review-rubric.md @@ -0,0 +1,128 @@ +# PRD Quality Review — ToolCTRL Unternehmens-Cockpit + +## Overall verdict + +**Good.** Das PRD ist ein angemessen kalibriertes Internal-Tool-Dokument mit klarer These ("Durchziehen-Problem, nicht Tool-Problem"), ehrlichem Scope (3 Areas statt 5, harte Anti-Ziele, sauber gepflegte Out-of-Scope-Tabelle) und einem zur Stake-Höhe passenden Detaillierungsgrad. Es trägt die Entscheidungen sichtbar (Daily-Update-Pflicht, flaches Vertrauensmodell, Authentik-OIDC) und benennt die wirklichen Risiken — insbesondere das Meta-Risiko, dass das Cockpit selbst versanden könnte. Was am ehesten wackelt, ist die Done-ness-Schärfe einzelner FRs (mehrere Adjektive ohne Bound) und ein paar leise Vagheiten in den NFRs; downstream lassen sich die Stories dennoch sauber ableiten. + +## Decision-readiness — strong + +Das PRD legt seine Entscheidungen offen statt sie zu verstecken. § 1.1 nennt das Kernproblem in einem Satz ohne Hedge ("kein Tool-Problem, sondern ein Durchziehen-Problem"). § 4.1 deklariert das flache Vertrauensmodell ("Alle Rollen sehen alles") und § 10 bestätigt, dass diese Entscheidung bewusst gegen Sales-Sensibilität gewählt wurde — das ist gelebter Trade-off, nicht Smoothing. § 2.2 listet sechs Anti-Ziele, jedes mit Reibung zu einer plausiblen Alternative (Ticketsystem, Zeiterfassung, BI). § 3.2 führt drei Gegenmetriken, davon zwei (CM2/CM3) explizit qualitativ — mit `[NOTE FOR PM]`-Verankerung der 4-Wochen-Retro. § 9 enthält fünf echte Open Questions mit benannten Entscheidern und Fristen ("Vor MVP-Bau"), nicht rhetorisch. + +Was die Decision-readiness vom Status `strong` hält statt sie zu trüben: Die Top-Entscheidung "Daily-Update-Pflicht mit öffentlicher fehlt-heute-Liste" wird zwar in § 1.1 und FR-3 begründet, aber der explizite Trade-off — Disziplin-Mechanik gegen das Risiko Lästergefühl — taucht erst verteilt in CM3 und § 10 Risiko 2 auf. Ein einziger Decision-Block "Wir haben uns für Pflicht-mit-Öffentlichkeitsdruck entschieden, obwohl …" würde das Bild schärfer machen, ist aber für ein Internal-Tool dieses Stake-Levels Kosmetik. + +### Findings +- **low** Trade-off zur Pflicht-Mechanik liegt verstreut (§ 1.1, § 3.2 CM3, § 10) — *Fix:* Optional einen 3-Zeilen-Decision-Block "Pflicht vs. freiwillig" in § 1.1 oder § 2 mit explizitem Was-wir-aufgeben. + +## Substance over theater — strong + +Das PRD verzichtet konsequent auf Theater-Bausteine. Es gibt **keine** Differenzierungs-/Innovations-Sektion (richtig, weil intern). Die Personas in § 4.1 sind funktional (eine Tabelle, eine Spalte "Hauptnutzen") und werden in FRs und UJs auch tatsächlich gebraucht — UJ-1 lebt vom Entwickler, UJ-2 vom PM, UJ-4 vom Onboarding. Die In-Depth-Personas (Addendum § 3) sind in den Addendum verschoben, wo sie für die UX-Phase Sinn ergeben, ohne das Hauptdokument zu verstopfen. + +Die Vision in § 1.2 ist produktspezifisch ("in unter 60 Sekunden", "niemand sich tagelang wegducken kann") — sie ließe sich nicht ohne Edit in ein anderes Cockpit-PRD übertragen. § 1.3 "Warum jetzt" liefert konkrete Vergangenheit (Jira/Confluence aufgegeben, "schneller ohne PM" gescheitert), keine generischen Plattitüden. + +Die NFRs in § 7 sind teilweise hart (NFR-1 ≤ 1,5 s bei 6-15 Nutzern und 90 Tagen Historie; NFR-2 ≤ 500 ms; NFR-9 ≤ 3 Klicks; CM1 ≤ 60 s Median), teilweise weicher (NFR-3 99% in Mo-Fr 8-19 — passend für Internal; NFR-13 "tägliches Backup, Mechanik in Architektur-Phase"). NFR-8 "Brand-konform … konkrete Tokens in UX-Phase" ist ehrlich vertagt und nicht Theater. + +### Findings +*(Keine. Substance-Bar wird für Internal-Stake-Level vollständig getragen.)* + +## Strategic coherence — strong + +Das PRD hat eine sichtbare These: **Versandung ist ein Sichtbarkeits-/Disziplin-Problem, kein Werkzeug-Problem.** Diese These zieht sich durch: + +- § 1.1 framt das Problem +- § 2 G1 ("≥1 Tagesupdate pro Werktag") und G2 ("Ampelstatus jederzeit erkennbar") operationalisieren die These +- FR-1/FR-3 (Puls + Heute-Quote), FR-19 (Update-Formular), FR-24/FR-25 (Streak + Fehlt-Liste), FR-2 (Blocker-Liste) sind allesamt direkte Hebel auf die These +- M1-M3 messen genau die These, nicht Vanity (M1 Update-Quote, M2 Blocker-Time-to-Visible, M3 Ampel-Frische); M4-M5 messen Verankerung im Arbeitsalltag +- Die drei Mechanik-Indikatoren im Addendum § 4 (Update-Quote, Median-Update-Alter, Overdue-Meilensteine) sind die operative Übersetzung der These — selten so sauber. + +Die Feature-Priorisierung folgt der These: 3 Detail-Areas statt 5, weil Buchhaltung/Design keine etablierten KPIs haben (Addendum § 2.6); Plane/Outline-API-Sync bewusst Phase 2 (§ 8) — weil API-Komfort die These nicht stützt, Sichtbarkeit aber sofort braucht. Gegenmetriken (§ 3.2) existieren und benennen explizit, dass die Pflicht-Mechanik kippen kann. + +MVP-Scope-Kind ist "problem-solving" (nicht Plattform, nicht Revenue), und die Scope-Logik passt: Drei Bereiche, drei Mechaniken, eine Startseite. Kein Backlog mit Section-Headern. + +### Findings +*(Keine substanziellen.)* + +## Done-ness clarity — adequate + +Mehrheit der FRs trägt mindestens eine testbare Konsequenz. Gute Beispiele: + +- FR-3: "X von Y Mitarbeitern haben heute gepingt" mit klickbarer Fehlt-Liste — eindeutig testbar +- FR-12: "5-Spalten Kanban: Lead → Erstgespräch → Demo → Angebot → Abschluss. Drag-and-Drop" — Stages explizit +- FR-16: 5 Funnel-Stufen explizit benannt +- FR-20: "unveränderbar nach 30 Minuten" — präzise Schwelle +- FR-25: "Werktag, nach 11:00" — Bedingung scharf + +Wo die Schärfe wackelt: + +- **FR-1** "aggregiertem Ampelstatus" — die Aggregations-Regel ist undefiniert. Wird die Area-Ampel aus dem schlechtesten Projekt abgeleitet, aus der Mehrheit, gewichtet? Engineering muss raten oder rückfragen. +- **FR-8** "Ampelstatus (abgeleitet aus letztem Update)" + **FR-23** "zuletzt gewinnt" — die Regel ist hier definiert (zuletzt gewinnt), aber FR-1's Area-Aggregation bleibt offen. +- **FR-5** "3-5 Key-Metrics pro Area (z. B. offene Deals, aktive Kampagnen, Projekte im Plan vs. gefährdet)" — die "z. B."-Beispiele sind keine Spezifikation. Welche Metriken konkret pro Area? Das verlässt das PRD ohne Festlegung. +- **FR-11** "schreibbar durch jede Person, Audit via `created_by` / `updated_by`" — keine Acceptance dafür, was bei Lösch-/Archiv-Operationen passiert (OQ-5 deckt nur den Archiv-Sub-Fall ab). +- **FR-21** "chronologisch angezeigt" — neueste oben oder älteste oben? Pagination? Limit? +- **NFR-1** Bound präzise; **NFR-3** "99% in Mo-Fr 8-19" angemessen für Internal; **NFR-9** "≤ 3 Klicks" scharf — aber **NFR-8** "Brand-konform" ohne Tokens und **NFR-14** "ohne Migrations-Drama" ist Adjektiv, kein Bound. Für NFR-14 wäre eine Schema-Migration-Strategie-Erwartung ("additive Migrationen, keine Daten-Drops in Phase 2") konkreter. +- **OQ-4** (Wer darf Blocker als gelöst markieren?) sollte vor MVP-Bau beantwortet sein — sonst fehlt FR-31 die Berechtigungsregel. + +Insgesamt ist Done-ness ausreichend für ein Internal-Tool dieser Größe, aber die Story-Phase wird an FR-1 (Area-Aggregation), FR-5 (Key-Metrics-Katalog) und FR-21 (Sort-/Pagination) rückfragen. + +### Findings +- **medium** FR-1 Area-Ampel-Aggregationsregel fehlt (§ 6.1) — *Fix:* "Worst-of aller Projekt-Ampeln in der Area" oder Alternative festlegen; einen Satz hinzufügen. +- **medium** FR-5 Key-Metrics nur als "z. B."-Beispiele (§ 6.1) — *Fix:* Pro Area die 3-5 Metriken namentlich aufzählen, oder explizit als `[ASSUMPTION]` markieren und in OQ aufnehmen. +- **low** FR-21 Sortierung/Pagination der Feeds undefiniert (§ 6.5) — *Fix:* "Neueste zuerst, Pagination ≥ N Einträge" oder Äquivalent. +- **low** NFR-14 "ohne Migrations-Drama" ist Adjektiv (§ 7.6) — *Fix:* Konkretisieren als "Phase-2-Felder additiv migrierbar; kein Daten-Drop nötig". +- **low** OQ-4 (Blocker-Auflöser-Berechtigung) muss vor Story-Bau zu FR-31 gespiegelt werden (§ 9, § 6.7) — *Fix:* Antwort früh holen; danach FR-31 ergänzen. + +## Scope honesty — strong + +Sehr saubere Scope-Disziplin. § 2.2 nennt sechs Anti-Ziele jeweils mit der Alternative, gegen die abgegrenzt wird. § 8 ist eine zehnzeilige Out-of-Scope-Tabelle mit Begründung pro Item — kein "TBD", jeder Eintrag trägt eine Erklärung ("URL-Link reicht zunächst", "Visualisierung zuerst beweisen, dann pushen"). FR-26 (Feiertage), FR-33 (Volltextsuche) und NFR-10 (Mobile) sind explizit als Phase 2 markiert, nicht stillschweigend ausgelassen. + +`[ASSUMPTION]`-Tags sind sparsam, aber an den richtigen Stellen: UJ-4 (Authentik-Group-Mapping), FR-33 (Phase 2-Charakter), § 10 (drei Top-Annahmen zu OIDC-Aufwand, Team-Größe, Backend-Stack). `[NOTE FOR PM]` in § 3.2 verankert die 4-Wochen-Retro für CM2/CM3. + +§ 9 hat fünf Open Questions mit Entscheider und Frist. Open-Items-Density: ca. 5 OQs + 4 `[ASSUMPTION]` + 1 `[NOTE FOR PM]` = 10 Open-Items bei einem MVP-PRD mit 33 FRs und 14 NFRs — angemessen für ein 4-Wochen-MVP, das in die Architektur-Phase übergeht. Keines der Items blockiert den Green-Light-Beschluss zum Bau; OQ-3 und OQ-4 sollten allerdings vor Story-Erstellung beantwortet sein. + +### Findings +- **low** Assumptions-Index fehlt am Ende des PRD (§ 11 endet ohne Annex) — *Fix:* Optional einen "Annahmen-Index"-Block am Ende, der die vier `[ASSUMPTION]`-Inlines auflistet. Für Internal-Stake nicht zwingend. + +## Downstream usability — adequate + +Glossar ist im Addendum § 6 vorhanden und sauber gepflegt (12 Begriffe: Puls, Area, Tagesupdate, Bezug, Ampel, Blocker, Streak, Jourfixe, Meilenstein, Deal-Stage, GTM-Schritt, Lead-Funnel). Die Begriffe werden im PRD konsistent verwendet — "Puls" durchgängig in § 6.1, FR-2, FR-25; "Area" durchgängig statt Mischung mit "Bereich" (Achtung: "Bereich" wird parallel benutzt, siehe Mechanical Notes). + +FR-IDs sind kontinuierlich (FR-1 bis FR-33), keine Lücken, keine Duplikate. NFRs analog (NFR-1 bis NFR-14). UJs (UJ-1 bis UJ-4), Metriken (M1-M5), Gegenmetriken (CM1-CM3), Anti-Ziele (A1-A6), Open Questions (OQ-1 bis OQ-5) — alle ID-Räume sauber, lokal benannt, eindeutig. + +UJs nennen Personas implizit ("PM", "Owner", "Mitarbeiter", "Person") — nicht via exakter Label aus § 4.1. UJ-2 sagt "PM" (§ 4.1 hat "Product / PM"), UJ-3 sagt "PM oder Owner" (§ 4.1 hat "Geschäftsführung / Owner"). Das ist keine Katastrophe, aber Downstream-Story-Erstellung wird einmal nachschauen müssen. Für ein Internal-Tool akzeptabel; bei chain-top-Status mit UX-Phase davor wäre eine Persona-Label-Säuberung nützlich. + +Cross-References funktionieren: FR-3 referenziert klar FR-25, FR-2 spiegelt zu FR-30. Sektionen sind extrahierbar — z. B. § 6.5 (Tagesupdates) kann allein gelesen werden, ohne "siehe oben"-Verweise. + +Das PRD ist explizit Chain-Top — § 0 sagt "Tech-Entscheidungen leben im begleitenden `addendum.md` und in der späteren Architektur-Phase", § 11 plant Phase-2-Review. Downstream-Usability ist damit eine relevante Dimension. + +### Findings +- **medium** UJ-Persona-Linkage nicht via exakter § 4.1-Label (§ 5) — *Fix:* "PM" → "Product / PM"; "PM oder Owner" → "Product / PM oder Geschäftsführung / Owner". Klein, aber spart Reibung. +- **low** "Bereich" und "Area" werden parallel genutzt (z. B. FR-19 "Bereich", Addendum § 6 nennt beide als Synonym) — *Fix:* Glossar-Eintrag explizit als gleichwertig erklären (ist faktisch schon dort), oder konsequent eine Form wählen. Aktuell tolerierbar. + +## Shape fit — strong + +Das PRD ist als **Internal-Tool / Multi-Operator-Kapazitäts-Spec** geformt, was zum Produkt passt (6-15 Nutzer, kein Verkauf, mehrere Rollen mit unterschiedlichen Workflows). Personas und UJs sind vorhanden und tragen — sie sind nicht overhead, denn das Cockpit hat echte Rollendifferenz (Sales braucht Kanban, Dev braucht Meilensteine, GF liest). SMs sind operational/Quoten-basiert (M1 Update-Quote, M3 Ampel-Frische), nicht User-Acquisition — passt zum Internal-Stake. + +Was richtig dosiert ist: + +- Keine Differentiator-Sektion (brauchts nicht — kein Verkauf) +- Keine Investor-Pitch-Schicht +- Keine GTM-Strategie +- Personas knapp tabellarisch + In-Depth ins Addendum verschoben +- NFR-Tiefe auf Internal-Verfügbarkeit (Mo-Fr 8-19, 99%) statt 24/7-SLA +- Brand/UX explizit auf UX-Phase verschoben + +Was nicht over-formalized ist: Keine UJ-Inflation (vier UJs decken die Hauptpfade — Update schreiben, Jourfixe, Verfall-Detect, Onboarding — und das genügt für ein Cockpit). Keine 10-Persona-Parade. + +Was nicht under-formalized ist: Das PRD hat eine Vision, eine These, Erfolgsmetriken, Gegenmetriken, Open Questions, Risiken — alles vorhanden, ohne aufgebläht zu wirken. + +### Findings +*(Keine.)* + +## Mechanical notes + +- **Glossar-Drift:** "Bereich" und "Area" werden synonym verwendet; das Glossar (Addendum § 6) führt sie als gleichwertig ("Area / Bereich"). Konsistent, aber nicht ganz sauber — z. B. FR-19 nutzt "Bereich", FR-1 nutzt "Areas" im Plural. Tolerierbar. +- **ID-Kontinuität:** FR-1 bis FR-33 lückenlos. NFR-1 bis NFR-14 lückenlos. UJ-1 bis UJ-4, M1-M5, CM1-CM3, A1-A6, G1-G5, OQ-1 bis OQ-5 — alle sauber. Keine Duplikate. +- **Cross-References:** FR-3 ↔ FR-25 konsistent. FR-2 ↔ FR-30 konsistent. § 9 OQ-3/OQ-4 referenzieren implizit FR-19/FR-31 — sollte vor Story-Bau gespiegelt werden. +- **Assumptions-Index-Roundtrip:** Vier inline `[ASSUMPTION]`-Tags (UJ-4, FR-33, § 10 dreifach). Kein konsolidierter Index am Ende. Für Internal-Stake nicht zwingend, aber bei Chain-Top-Lesung praktisch. +- **UJ-Persona-Linkage:** UJs nennen Rollen nicht via exakter § 4.1-Label — siehe Downstream-Findings. +- **Erforderliche Sektionen:** Vision, Ziele, Anti-Ziele, SMs, CMs, Personas, UJs, FRs, NFRs, Out-of-Scope, Open Questions, Risks/Assumptions, Review-Punkte — alle vorhanden. Glossar ins Addendum verlagert (akzeptabel; man könnte einen einzeiligen Verweis aus dem PRD ins Addendum-Glossar setzen). +- **Addendum-Aufgabentrennung:** Tech-Skizze, verworfene Alternativen, In-Depth-Personas, Mechanik-Detail, Off-Brand-Risiken, Glossar — sauber im Addendum, hält das PRD lesbar. Das ist eine starke Trennung. diff --git a/_bmad-output/planning-artifacts/ux-design-specification.md b/_bmad-output/planning-artifacts/ux-design-specification.md new file mode 100644 index 0000000..d25fa5a --- /dev/null +++ b/_bmad-output/planning-artifacts/ux-design-specification.md @@ -0,0 +1,789 @@ +--- +stepsCompleted: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] +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/unternehmens-cockpit.html + - prds/prd-projectmanagement-2026-05-25/inputs/ToolCTRL_Projektmanagement-Leitfaden_v3.0.pdf + - prds/prd-projectmanagement-2026-05-25/extract-prototype-html.md + - architecture.md +workflowType: 'ux-design' +project_name: 'ToolCTRL Unternehmens-Cockpit' +user_name: 'Dejancosic' +date: '2026-05-25' +binding_frame: 'ToolCTRL Projektmanagement-Leitfaden v3.0 (Mai 2026)' +prd_status: 'final' +architecture_status: 'complete' +--- + +# UX Design Specification — ToolCTRL Unternehmens-Cockpit + +**Author:** Dejancosic +**Date:** 2026-05-25 + +> Dieses Dokument legt das Visual Design, die Komponenten-Bibliothek, die Layouts und die Mikrocopy fest. Es operationalisiert die UX-Anforderungen aus PRD (NFR-8, NFR-9, NFR-12/13) und liefert die Brand-Token-Werte (OQ-2), die in `tailwind.config.ts` einfließen. + +--- + + + +--- + +## Executive Summary + +### Project Vision + +Das **ToolCTRL Unternehmens-Cockpit** ist ein internes Web-Werkzeug, das jeden Werktag in unter 60 Sekunden sichtbar macht, woran das Team arbeitet, was klemmt und welche Projekte aus dem Plan laufen. Es operationalisiert den Projektmanagement-Leitfaden v3.0 als drittes Werkzeug neben Plane (Arbeit) und Outline (Gedächtnis) und ist die Antwort auf das wiederkehrende „Wir starten viel, wir bekommen nichts fertig"-Muster. + +UX-Aufgabe: Eine Anwendung schaffen, die sich **unaufgeregt, lesbar und schnell** anfühlt — kein „Tool, das einen checkt", sondern ein gemeinsamer Spiegel des Tages. Die Anwendung muss Disziplin erzeugen, ohne Lästergefühl zu produzieren (PRD CM3). + +### Target Users + +Alle ToolCTRL-Mitarbeiter (6-15 Personen), aufgeteilt in zwei Sichtebenen: + +**Leitfaden-Rollen (pro Projekt):** + +- **P1 Projektverantwortliche/r** — pflegt Ampelstatus und Trend, moderiert Jourfixe, achtet auf Pflicht-Minimum. +- **P2 Team** — schreibt Tagesupdates, setzt Blocker, pflegt Meilenstein-Status. +- **P3 Product Owner** — Lese-Schwerpunkt, Priorisierungs-Diskussionen. +- **P4 Auftraggeber** (intern = Geschäftsführung) — Lese-Hauptnutzer für Puls und Pflicht-Minimum. + +**Querschnittsrollen (alle Mitarbeiter):** + +- **Q1 Entwickler** — Updates mit Projektbezug, viele Bezugswechsel pro Tag, möchte unter 30 s pro Touchpoint. +- **Q2 Sales** — lebt im Kanban, verschiebt 5–10 Deals pro Woche, schreibt Updates ungern wenn das System träge ist. +- **Q3 Marketing** — pflegt Lead-Funnel + Kampagnen, weniger täglicher Fortschritt → das System darf „heute kein Fortschritt, aber dran"-Updates nicht verteufeln. +- **Q4 Design / Q5 Buchhaltung** — schreiben „Allgemein"-Updates, brauchen keine Projektpflicht. + +Stand der Nutzer-Reife: hoch tech-affin (alle haben ein Authentik-SSO-Login bereits gewohnheitsmäßig), Desktop-First-Nutzer, Deutsch als Arbeitssprache. + +### Key Design Challenges + +1. **Zwei Touchpoints pro Tag, beide unter 60 Sekunden.** Morgen-Einstempeln + ToDos und Abend-Reflexion müssen *sehr* schnell zu erledigen sein — sonst erodiert die Disziplinmechanik (CM1a/CM1b). +2. **Sichtbarkeitsmodell pre/post Publikation.** Innerhalb des Tages: ToDos sind privat, nur „eingestempelt: ja/nein" für Kollegen sichtbar. Ab nächstem Werktagsmorgen: konsolidierter Eintrag wird öffentlich. Die UI muss diese Privatheit ohne Erklärungstext verständlich machen. +3. **Pflicht-Minimum als Erstklass-Information.** Die 6 Leitfaden-Pflicht-Punkte pro Projekt müssen prominent und auf einen Blick lesbar sein — sie sind die Brücke zwischen Cockpit und Leitfaden, nicht eine Detailseite. +4. **Drei strukturell unterschiedliche Bereichs-Details.** Entwicklung (Projekte+Meilensteine), Vertrieb (Kanban+gewichtete Pipeline), Marketing (Lead-Funnel+Kampagnen) sehen visuell anders aus, gehören aber in dasselbe Layout-Skelett — das verlangt eine konsistente Schale mit feature-spezifischem Inhalt. +5. **Drei Status-Ampelfarben in einer monochrom-geprägten Brand.** Brand ist schwarz/weiß; Ampel braucht Grün/Gelb/Rot semantisch. Sie müssen funktional erkennbar bleiben, ohne die Brand-Sprache zu sprengen. +6. **Voice & Tone als Disziplin-Anker.** Locker und direkt (`„Stark."`, `„Wie läuft es?"`) — UI-Mikrocopy ist Pflicht-Vehikel, kein Beiwerk. + +### Design Opportunities + +1. **Ein einzelner „Puls"-Bildschirm als tägliche Heimat.** Wenn der Puls in einem Blick die Antwort auf „Was muss ich heute wissen?" liefert, gewinnt das Cockpit täglich. Konsequente Information-Density ohne Overload. +2. **„Mein heute" als persönliche Tagesoase.** Eine ruhige, private Sicht, die der einzige Ort ist, wo ToDos heute bearbeitbar sind — visuell von der öffentlichen Puls-Sicht trennen. +3. **Reflexion als geführter, kurzer Flow.** Statt eines Formulars: eine schrittweise Reflexion (ToDos abhaken → Freitext → Ampel + Trend → fertig), die sich wie ein Tagesabschluss anfühlt, nicht wie ein Pflicht-Formular. +4. **Markenmäßige Reduktion als Feature.** Brand ist monochrom — das passt zu „weniger ablenken, mehr Übersicht". Statt das als Limit zu sehen, ist es ein Wettbewerbsvorteil gegenüber bunten SaaS-Tools. +5. **Der `^`-Caret als wiederkehrender Mikro-Anker.** Das macOS-Ctrl-Symbol als kleines Brand-Element (im Logo, in Tab-Indikatoren, in Bestätigungs-Toasts) zieht eine subtile Identitätslinie durch die App. +6. **Sichtbar gemachte Streaks als sozialer Motivator.** Wer 14 Werktage in Folge eingestempelt ist, sieht das prominent im Profil und im „Heute fehlt"-Modul. Sozialer Druck ohne Public Shaming. + +--- + +## Core User Experience + +### Defining Experience + +Die Kern-Erfahrung des Cockpits ist der **Tagesrhythmus**: Eine Person stempelt morgens ein und definiert ihre ToDos für den Tag; am Abend reflektiert sie, hakt ab, setzt Ampel und Trend; am nächsten Werktagsmorgen sehen Kollegen den konsolidierten Vortags-Eintrag. Diese Schleife wiederholt sich jeden Werktag und ist der zentrale Wertbeitrag des Produkts. Alles andere — Puls, Bereichs-Details, Pflicht-Minimum, Blocker — ist die Auswertungs- und Übersichts-Schicht über dieser täglichen Routine. + +Der zweite zentrale Moment ist der **Jourfixe-Puls am Dienstag 11:00**: Die Übersichtsseite wird vom PM (P1/P4) als Hauptbildschirm aufgerufen, alle drei Bereiche werden in einer scroll-bereiten Sicht durchgegangen, rote/gelbe Projekte und offene Blocker werden direkt angesprochen. + +### Platform Strategy + +- **Plattform:** Web-Anwendung (SPA), self-hosted unter `cockpit.tool-ctrl.de`. +- **Primärer Input:** Maus + Tastatur. Touch-Geräte sind nicht primär, müssen aber das Tagesupdate-Schreiben nicht blockieren. +- **Bildschirmgröße:** Desktop-first (≥ 1280 px breit). Mobile/Tablet: lesbar (responsive Layout-Grid), nicht primär schreibbar (PRD NFR-10). +- **Offline:** keine Offline-Fähigkeit im MVP. Cockpit ist immer online (interne Infrastruktur). +- **Geräte-Capabilities:** kein Spezial-Hardware, kein Push, keine Kamera. Der Browser ist die einzige Bühne. +- **Browser-Support:** moderne Evergreen-Browser (Chrome, Firefox, Safari der letzten 2 Major-Versionen). Kein IE11. + +### Effortless Interactions + +Diese Dinge müssen sich **mühelos** anfühlen — wenn hier Reibung entsteht, scheitert das ganze Produkt: + +- **Einstempeln** — Person klickt einen großen CTA auf der Puls-Seite, Modal öffnet sich mit Bereichs-Default und leerer ToDo-Liste. Tippen → Enter → nächster ToDo → Enter. Speichern mit Cmd/Ctrl+S oder Enter auf leerer Zeile. Ziel: ≤ 30 Sekunden für 3-5 ToDos. +- **ToDo abhaken in der Reflexion** — Single-Click auf die Checkbox neben jedem ToDo. Optionaler Verwerf-Modus (Rechtsklick oder Hover-Button) für „verworfen mit Grund". +- **Ampel + Trend setzen** — drei große farbige Buttons (Grün/Gelb/Rot), nicht ein winziges Dropdown. Trend als drei Pfeil-Buttons (↗︎ → ↘︎). +- **Blocker erfassen** — entweder inline beim Reflektieren (Toggle „Blocker?") oder über ein „+ Neuer Blocker"-Button im Header, der von jeder Seite aus erreichbar ist. +- **Projekt wechseln** — Bereichs-Detail hat eine Projekt-Rail oben mit Klick-zum-Wechsel; Projekt-Detail hat „� Bereich" als Breadcrumb. +- **Login** — automatischer Authentik-Redirect; nach erfolgreichem Login landet die Person auf der Puls-Seite. Kein Login-Formular im Cockpit selbst. + +### Critical Success Moments + +1. **Morgen-Eintritt:** Person öffnet das Cockpit (oder folgt einem Reminder-Link). Die Puls-Seite zeigt prominent oben „Du hast heute noch nicht gepingt." mit einem großen primären Button. Die Person ist in 3 Klicks im Einstempeln-Flow. **Failure-Mode:** Diesen Hinweis übersehen oder das Formular träge bedienen — die Streak wird gefährdet. +2. **Reflexions-Moment am Abend:** Ab 17:00 Uhr erscheint im Header ein dezenter Hinweis „Du hast heute noch nicht reflektiert." Die Person klickt, sieht die eigene ToDo-Liste vom Morgen wieder, hakt ab, schreibt 2–3 Sätze, setzt Ampel. **Failure-Mode:** Die Reflexion fühlt sich wie ein Bürokratie-Akt an — dann wird sie ausgelassen und der Tag bleibt „halb". +3. **Jourfixe-Aufruf am Dienstag:** PM öffnet Puls, scrollt einmal von oben nach unten, hat in 60 Sekunden Übersicht über alle Bereiche, Blocker und Pflicht-Minimum-Verstöße. **Failure-Mode:** Wenn Informationen verstreut auf mehreren Tabs liegen, wird die Jourfixe ineffizient. +4. **Nächster-Morgen-Reveal:** Person öffnet das Cockpit am Tag X+1, sieht im Area-Feed die konsolidierten Vortags-Einträge der Kollegen — ToDos, abgehakte Status, Reflexion, Ampel. Das schafft das Gefühl gemeinsamer Sichtbarkeit. **Failure-Mode:** Wenn die Publikation um 00:05 fehlschlägt (Cron-Job), erscheint der Tag X+1 leer — Vertrauen ins Cockpit erodiert. +5. **Pflicht-Minimum-Erstmal-Sichtbar-Werden:** Bei Projekt-Neuanlage wird in der Projekt-Detail-Sicht die 6-Punkte-Checkliste angezeigt; offene Punkte sind sofort visuell als „nicht erfüllt" markiert. **Failure-Mode:** Wenn die Checkliste versteckt in einem Tab liegt, vergessen Verantwortliche, sie zu pflegen — der zentrale Leitfaden-Hebel verschwindet. + +### Experience Principles + +1. **Disziplin durch Sichtbarkeit, nicht durch Reibung.** Das Cockpit erzwingt nichts mit roten Bannern oder Pflicht-Pop-ups. Es macht nur sichtbar, wer und welches Projekt fehlt. Sichtbarkeit ist der Hebel, nicht Druck. +2. **Privat tagsüber, öffentlich rückblickend.** „Mein heute" gehört der Person; der konsolidierte Vortagseintrag gehört dem Team. Diese Zeit-Asymmetrie ist UI-konstitutiv. +3. **Monochrome Ruhe, semantische Klarheit.** Weiß und Schwarz dominieren. Farbe (Grün/Gelb/Rot) trägt ausschließlich Bedeutung — niemals Dekoration. +4. **Drei-Klick-Maximum für Schreib-Touchpoints.** Einstempeln, Reflexion, Neuer Blocker sind von der Puls-Seite in ≤ 3 Klicks erreichbar (PRD NFR-9). +5. **Voice statt Decoration.** „Stark.", „Wie läuft es?", „Du hast heute noch nicht gepingt." als Mikrocopy-Anker. Kein Corporate-Sprech, keine Emojis im UI-Text, keine englischen Floskeln. +6. **Die Wahrheit lebt woanders.** Steckbrief in Outline, Issues in Plane, Tickets nicht im Cockpit. Verlinkungen sind eine UI-Erstklass-Komponente, nicht ein technischer Hinweis. +7. **Geschwindigkeit ist ein Feature.** Puls < 1,5 s, Update-Speichern < 500 ms. Performance-Wahrnehmung ist Teil des UX-Versprechens — träge wirkt wie respektlos gegenüber der Zeit der Mitarbeiter. + +--- + +## Desired Emotional Response + +### Primary Emotional Goals + +Das Cockpit soll vier Gefühle gleichzeitig erzeugen: + +- **Ruhe.** Beim Öffnen kein Lärm, keine bunten Banner, keine Notifications-Flut. Der erste visuelle Eindruck ist *aufgeräumt*. +- **Selbstwirksamkeit.** Nach dem Einstempeln das Gefühl: „Ich weiß, was ich heute will." Nach der Reflexion: „Ich weiß, was ich geschafft habe." Kein Pflichtgefühl, sondern stiller Stolz. +- **Mitsehen-Können.** Beim Puls-Scrollen: „Ah, so läuft's bei den anderen — gut, dass ich das ohne Fragen sehe." Geteilte Sichtbarkeit ohne Überwachungsgefühl. +- **Frühwarn-Vertrauen.** Wenn ein Projekt rot wird, vertraut man dem Cockpit, dass es das schon Tage vorher angedeutet hätte (gelb → rot Trend). Das Cockpit ist ein verlässlicher Frühwarn-Spiegel, kein Schock-Geber. + +Was das Cockpit **nicht** erzeugen darf: + +- � **Lästergefühl** — das „fehlt heute"-Modul darf sich nicht wie ein Pranger anfühlen (PRD CM3). +- � **Hektik** — keine roten Banner, keine Pulsing-Animationen, keine Pop-ups. +- � **Bürokratie** — Reflexion soll wie ein Tagesabschluss-Ritual wirken, nicht wie ein Stundenzettel. +- � **Spielerei** — keine Konfetti-Animationen für Streaks, keine Gamification-Badges. Subtil bleiben. + +### Emotional Journey Mapping + +Eine typische Werktag-Emotion einer Person (Q1 Entwickler oder Q2 Sales): + +| Moment | Erwartete Emotion | UI-Antwort | +|---|---|---| +| **Login morgens** | Neugierig, leicht angespannt | Klarer Begrüßungs-Header, „Wie läuft es?" sanft, kein Druck | +| **„Du hast heute noch nicht gepingt"** | Erinnerung statt Anklage | Großer freundlicher CTA, keine roten Akzente | +| **ToDos schreiben** | Fokussierung | Minimalistisches Formular, eine Spalte, monospaced-Optionen | +| **Tagsüber im Cockpit reinschauen** | Schnelles Vergewissern | Puls in unter 60 s gelesen, Streak-Badge sichtbar, kein Drill-Down nötig | +| **Reflexion abends** | Ritualisches Abschließen | Geführter Flow: Checkboxen → Freitext → Ampel → fertig. Bestätigung mit „Stark." | +| **Morgens X+1: Vortags-Reveal** | Soziale Verbundenheit | Konsolidierte Vortageseinträge im Feed, ruhig präsentiert | +| **Wenn ein Projekt gelb wird** | Vorsichtige Wachsamkeit | Subtile Gelb-Akzente, klare Erläuterung („Maßnahmenplan greift") | +| **Wenn ein Projekt rot wird** | Handlungsbereitschaft | Klarer roter Akzent, Hinweis „braucht jetzt eine Entscheidung", direkter Link zum Projekt-Detail | +| **Wenn man die Streak verliert** | Verständnis, keine Strafe | Streak-Counter geht auf 0 ohne Drama; vorheriger Höchstwert wird im Profil als „bester Lauf" gespeichert | + +### Micro-Emotions + +Diese kleinen Momente machen den Unterschied zwischen „funktional" und „liebenswert": + +- **„Stark."-Bestätigungs-Toast** nach erfolgreicher Reflexion. Drei Buchstaben, schwarzer Pill, 2 Sekunden sichtbar. +- **Tagesplan-Echo:** Beim Reflektieren werden die ToDos vom Morgen mit einer leichten Animation eingeblendet — die Person sieht das eigene morgendliche Ich wieder. +- **Subtile Ladekontrolle:** Skeleton-Boxen statt Spinner. Wartezeit fühlt sich wie „die App ist schon fast da" an, nicht wie „die App hängt". +- **Streak-Tag im Profil:** Eine schlichte Zahl mit dem `^`-Caret als Suffix. „14 ^". Keine Trophäen. +- **Reflexions-Erinnerung:** Ab 17:00 erscheint im Header ein dezenter Hinweis-Pill „Reflexion offen". Klick → direkt im Flow. Kein Notification-Lärm. +- **Vortags-Reveal:** Beim Laden des Feeds am Morgen X+1 erscheinen die neuen konsolidierten Einträge mit einer kurzen Fade-In-Animation (≤ 200 ms). Markiert visuell „diese sind neu seit gestern". + +### Design Implications + +Die emotionalen Ziele übersetzen sich in folgende UX-Pflichten: + +- **Weißraum großzügig.** Cards mit viel Padding, klare vertikale Rhythmen — keine Information-Density um jeden Preis. +- **Typografie als Hauptkommunikator.** Hierarchie über Schriftgrade und Gewichte, nicht über Farben. +- **Animationen sparsam und schnell.** Höchstens 200 ms, ease-out, niemals länger oder bouncy. Animation als Bestätigung, nicht als Show. +- **Status-Farben nur funktional.** Grün/Gelb/Rot ausschließlich bei Ampel/Trend/Blocker. Keine farbigen Decorative-Akzente sonst. +- **Mikrocopy konsequent deutsch und kurz.** Keine englischen Floskeln, keine Höflichkeitsfloskeln („Bitte beachten Sie…"). +- **Kein Drama bei Fehlern.** Fehler-Toasts dezent, in der unteren rechten Ecke, schwarz mit weißem Text. + +### Emotional Design Principles + +1. **Subtil schlägt laut.** Animationen, Farbakzente und Mikrocopy sind unaufdringlich; nichts ringt um Aufmerksamkeit. +2. **Ritual statt Pflicht.** Reflexion fühlt sich wie ein Abschluss-Ritual an, nicht wie ein Formular. +3. **Sichtbarkeit ist freundlich, nicht überwachend.** „Heute fehlt"-Anzeige als Information, nicht als Anklage. +4. **Geschwindigkeit ist Respekt.** Schnell laden, schnell reagieren — das vermittelt: Wir respektieren deine Zeit. +5. **Stille Anerkennung.** Streaks, „Stark."-Toasts, „bester Lauf"-Memory: kleine Anerkennungen, keine Trophäen-Show. + +--- + +## UX Pattern Analysis & Inspiration + +### Inspiring Products Analysis + +Vier Produkte dienen als Pattern-Referenzen für das Cockpit. Wir kopieren nicht ihren Look, sondern lernen *welche UX-Muster sich für unsere Probleme bewährt haben*. + +**Linear (linear.app)** — Issue-Tracker für Tech-Teams +- **Was übernehmen:** Aufgeräumte, monochrome Listen-Ansichten; Tastatur-Shortcuts als Erstklass-Interaktion; sehr schnelle Mutations-Feedback-Schleifen; Status-Icons als kleine Quadrate statt Text. +- **Was nicht:** Linear ist Ticket-Tool — wir sind explizit *kein* Ticket-Tool (PRD Anti-Ziel A1). + +**Linear's Sub-Issue-System** — Mini-Hierarchie für Tagesplan +- Übertragbar als Inspiration für die ToDo-Liste: minimalistisches Inline-Editing, Tippen → Enter → nächste Zeile. + +**Notion** (notion.so) — Doku & Wissens-Tool +- **Was übernehmen:** Klare Typografie-Hierarchie; reduzierte Farbpalette; Embed-/Verlinkungs-Patterns für externe Tools. +- **Was nicht:** Wir sind kein Doku-Tool (Outline-Job). Keine Notion-typischen Floating-Elements oder Slash-Commands. + +**GitHub Projects (Roadmap-Sicht)** — Multi-Projekt-Übersicht +- **Was übernehmen:** Tabellen-Sicht über viele Items mit klar erkennbarem Status, Filter-Persistenz, Spalten-Sortierung. +- **Was nicht:** GitHub-typische Density (für uns zu viel auf einen Blick) und die Bunt-Tags-Kultur. + +**Apple HIG (Human Interface Guidelines)** — System-Design-Sprache +- **Was übernehmen:** Klare Modal-Patterns, Toast-Position, Form-Stil, San-Serif Lesbarkeit, generöser Weißraum. + +**Plane (plane.so)** — das parallel laufende Schwester-Tool +- **Was übernehmen:** UX-Konsistenz bei Iconografie und Statusfarben, damit der Wechsel Plane ↔ Cockpit nicht mental zerreisst. +- **Was nicht:** Plane's volle Issue-Listen — die spiegeln wir nicht (Anti-Ziel A5). + +### Transferable UX Patterns + +| Pattern | Quelle | Anwendung im Cockpit | +|---|---|---| +| **Inline-Add-on-Enter** | Linear, Notion | ToDo-Liste: Enter erzeugt neue ToDo-Zeile; Cmd+Enter speichert die ganze Liste. | +| **Tastatur-Shortcuts mit `?`-Help** | Linear, GitHub | `g p` → Puls, `g h` → Mein heute, `g a` → Archiv. `?` öffnet Cheatsheet. | +| **Optimistic Updates mit zarter Bestätigung** | Linear | Beim Abhaken eines ToDos: sofortige UI-Reaktion + leichtes Häkchen-Fade-In; Rollback bei API-Fehler. | +| **Skeleton-Cards** | Linear, Vercel | Statt Spinner: graue Card-Skelette mit identischem Layout wie die Echtdaten. | +| **Hover-Reveal-Actions** | Linear | Sekundäre Aktionen (Edit, Verwerfen, Löschen) erst beim Hover/Focus sichtbar. Reduziert visuelle Last. | +| **Linkurations-Cards** | Notion, Slack-Unfurls | Beim Verlinken einer Plane-URL erscheint im Update eine kleine Card mit Titel und Status (Phase 2 mit API; im MVP: nur URL). | +| **Sticky-Sub-Header** | GitHub Projects | Bei langer Bereichs-Detail-View bleibt der Bereichs-Header oben sticky, damit man immer weiß wo man ist. | +| **Status-Pill mit Trend-Pfeil** | GitHub PR-Status | Ampel-Pill mit anhängendem Trend-Pfeil als kompaktes Statussignal an Projekt-Karten. | +| **Profil-Streak-Stat** | Duolingo (abstrahiert) | Streak als schlichte Zahl im Profil, ohne Streak-Recovery-Käufe oder Drama. | + +### Anti-Patterns to Avoid + +Diese Patterns sind im Cockpit **verboten**, weil sie den Experience Principles widersprechen: + +- � **Roter Mahn-Banner oben.** Statt „Du hast heute noch nicht eingestempelt"-Banner: ein freundlicher CTA mit `^`-Caret und schwarzem Pill-Button. Niemals Alarmrot. +- � **Notification-Badges mit Zahlen.** Wir zeigen Status, nicht Posteingänge. Keine roten Zähler. +- � **Gamification-Trophäen.** Keine Badges, keine Levels, keine Konfetti-Animationen. Streak ist genug. +- � **Pflicht-Pop-up beim Login** („Bitte stempel zuerst ein"). Stattdessen ist der Einstempel-CTA Teil der Puls-Seite, nicht ein Modal. +- � **Bunte Status-Tags** in Marketing-/Vertrieb-Stil. Status-Farben bleiben monochrom + 3 Ampel-Töne. +- � **Komplexe Filter-Sidebar**. Im MVP keine Multi-Facet-Filter. Wenn der Feed lang wird: Bereich-Filter + „nur meine" reicht. +- � **Dichte Tabellen mit 10+ Spalten.** Karten/Lists, keine Excel-Sheets. +- � **Pulsing-/Spin-Animationen** auf der Puls-Seite. Animation nur bei expliziter User-Aktion. +- � **Auto-Refresh**. Daten werden bei Page-Navigation und manuellem Pull-to-Refresh-Equivalent neu geholt. Kein Background-Polling außer am User-aktiven Tab. +- � **Drag-and-Drop überall**. Drag-and-Drop nur im Sales-Kanban (FR-12), nicht für ToDos oder Meilensteine. + +### Design Inspiration Strategy + +- **Atomare Inspiration, nicht Holistik.** Wir kopieren keine ganzen App-Looks; wir übernehmen einzelne Patterns (Inline-Add, Skeleton, Sticky-Sub-Header) und kombinieren sie mit der ToolCTRL-Brand. +- **Brand schlägt Pattern.** Wo ein Pattern (z. B. Notion-Slash-Commands) gegen die Brand-Reduktion arbeiten würde, lassen wir es weg. +- **Linear ist die nächste Konstante.** Im Zweifel folgen wir Linear-Patterns für Listenwesen und Mutations-Feedback, da Linear das am saubersten löst für Tech-Teams. +- **Eigene Patterns nur wo nötig.** Sichtbarkeitsmodell pre/post Publikation und Pflicht-Minimum-Panel sind eigene Erfindungen — die müssen wir selbst gut machen. + +--- + +## Design System Foundation + +### Design System Choice + +**Tailwind CSS** (vom Architektur-Starter gesetzt) als Utility-First-Foundation, ergänzt um **Radix UI Primitives** für interaktive Komponenten (Dialog, Dropdown, Tabs, Tooltip) und eine **schlanke Custom-Component-Library** in `apps/web/src/components/ui/`. + +**Keine vollständige Component-Library** (kein shadcn-Bulk-Install, kein Material-UI, kein Ant Design): Die Brand ist zu spezifisch und das Tooling-Volumen zu klein, um eine externe Library zu rechtfertigen. + +### Rationale for Selection + +- **Tailwind passt zur Brand:** Monochrome Palette, klare Spacing-Skala, kein Default-Theme das mit der Brand kämpft. +- **Radix UI Primitives liefern Accessibility-Korrektheit** ohne Visual-Design — wir styleen sie mit Tailwind nach unserer Sprache. Spart Bug-Klassen wie kaputte Focus-Traps in Modalen, falsche ARIA-Roles, etc. +- **Eigene Components statt shadcn:** shadcn copies kleine Library-Snippets ins Repo — bei unserer Größe ist es kontrollierbarer, ~15 Komponenten direkt im Brand-Stil zu schreiben, als 50 Snippets zu kopieren und Brand-anzupassen. +- **Keine Dark-Mode-Pflicht im MVP:** Das Cockpit ist Single-Theme (weiß primär). Phase 2 kann Dark Mode hinzufügen. + +### Implementation Approach + +``` +apps/web/src/components/ui/ # Eigene Brand-konforme Komponenten +├── Button.tsx # 3 Varianten: primary, secondary, ghost +├── Card.tsx # Standard-Container mit großen Radii +├── Input.tsx # Text + Number + URL +├── Textarea.tsx +├── Select.tsx # nutzt Radix Select +├── Checkbox.tsx # nutzt Radix Checkbox +├── Toggle.tsx # 3-Wege-Toggle (Ampel) +├── Dialog.tsx # nutzt Radix Dialog +├── Tabs.tsx # nutzt Radix Tabs +├── Tooltip.tsx # nutzt Radix Tooltip +├── Toast.tsx # nutzt Sonner (toast lib) +├── SkeletonCard.tsx +├── AmpelBadge.tsx # Status-Pill mit Trend +├── PflichtMinimumIndicator.tsx # 6-Punkt-Block-Indikator +└── KeyCaret.tsx # Das `^`-Brand-Element +``` + +### Customization Strategy + +1. **Brand-Tokens als CSS-Variablen im `:root`**, von Tailwind via Theme-Erweiterung konsumiert. Das erlaubt späteres Dark-Mode-Switching ohne Class-Renaming. +2. **Eine `tailwind.config.ts` als Source of Truth** für Spacing, Radii, Schriftgrade, Farben. Custom-Components verwenden `@apply`-Composition oder direkte Utility-Klassen. +3. **Radix-Komponenten werden gestyled, nicht überschrieben.** Tailwind-Classes auf Radix-Primitives (``). +4. **Icon-Set:** **Lucide-React** als Default (offen, kostenlos, konsistent). Wo Brand-Icons aus `ToolCTRL Brand Identity/Brand EcoSystem/Ui icons Pattern` vorhanden sind (Home, Settings, Search, Trash, Zoom-in, etc.) — bevorzugt verwenden; Rest aus Lucide. Beide Sets sind monochrom-filled. + +--- + +## Defining Experience (Detail) + +### Defining Experience + +Im Zentrum steht der **Tagesrhythmus-Doppeltakt**: ein zeitlich getrennter Schreib-Workflow (morgens setzen, abends reflektieren), der bewusst nicht als ein einziges Formular vereint wird, sondern als zwei eigene Touchpoints existiert — getrennt durch den Tag. Die Trennung ist kein technischer Workaround, sondern das UX-Versprechen: „Plan und Realität dürfen sich unterscheiden, und wir machen genau diese Differenz sichtbar." + +Die zweite Erfahrungs-Säule ist der **Puls als gemeinsamer Spiegel**: ein einziger, dichter Bildschirm, der jeden Werktagsmorgen die Antwort liefert auf „Wie steht es heute um uns alle?". + +### User Mental Model + +Die Personen sollen das Cockpit so verstehen: + +- **„Mein heute" ist mein Privates** — wie eine Tageskarte, die nur ich sehe. +- **Der Puls ist unser Gemeinsames** — wie ein Schwarzes Brett, das jeden Werktag aktuell ist. +- **Bereich = Familie meines Projekts/Deals/Kampagne** — Entwicklung, Vertrieb, Marketing als drei eigene Häuser unter einem Dach. +- **Plane ist Werkzeug, Outline ist Wissen, Cockpit ist Spiegel.** Das Cockpit interpretiert oder editiert nichts — es zeigt. + +Was das Modell **vermeiden** muss: die Verwechslung „das Cockpit ist mein Ticket-Tool" (es ist nicht Plane) oder „das Cockpit ist mein Wiki" (es ist nicht Outline). + +### Success Criteria + +Die Kern-Erfahrung gilt als erfolgreich, wenn nach 8 Wochen Live-Betrieb gilt: + +- ≥ 80 % der Werktage haben pro aktiver Person mindestens ein abgeschlossenes Tagesupdate (PRD M1). +- Median Einstempel-Zeit ≤ 60 s, Median Reflexions-Zeit ≤ 90 s (CM1a/CM1b). +- 100 % der Jourfixes nutzen den Puls als Hauptbildschirm (M4). +- Kein erhöhter Druck-/Lästergefühl im 4-Wochen-Retro (CM3). + +### Novel UX Patterns + +Diese Patterns sind im Cockpit **neu erfunden** (haben kein direktes Vorbild in der Inspirations-Bibliothek): + +1. **Sichtbarkeits-Asymmetrie über Zeit:** ToDos sind tagsüber privat, am nächsten Werktagsmorgen publiziert — bewusst keine Realtime-Sichtbarkeit. Visuell muss klar sein: „Was du gerade tippst, sehen Kollegen ab morgen." +2. **Pflicht-Minimum-Indikator:** Sechs kleine Blöcke (•••••• statt einzelner Häkchen-Checkliste) zeigen pro Projekt den Erfüllungsstand auf einen Blick. Ein klick zum Detail. +3. **Tagesplan-Echo:** Bei der Abend-Reflexion erscheinen die morgens gesetzten ToDos zurück — Person konfrontiert sich mit dem eigenen morgendlichen Plan. +4. **Tagesstatus-Pill pro Person:** 🟢 voll, 🟡 halb, ⚪ nicht angefangen, 🟣 abwesend — eine vierwertige Statusanzeige, die sich klar von Projekt-Ampeln (3-wertig + grau) unterscheidet. + +### Experience Mechanics + +**Morgen-Einstempeln-Flow:** + +1. Person öffnet Cockpit → Puls-Seite. +2. CTA „Einstempeln" prominent oberhalb des Folds. Bei nicht-eingestempelten Personen großer schwarzer Pill-Button mit `^`-Caret. +3. Klick öffnet `/mein-heute` (kein Modal — eigene Route, das `^` der Brand wird Teil des Headers). +4. Eine zentrale Spalte: leere ToDo-Liste mit Cursor auf Zeile 1. +5. Bereich ist vorausgewählt (Default = eigene Area). Bezugs-Auswahl pro ToDo erscheint inline rechts neben der Zeile, wenn die ToDo fokussiert ist (Hover-Reveal-Pattern). +6. Enter erzeugt nächste Zeile. Cmd/Ctrl+S oder Klick auf „Eingestempelt"-Button speichert. +7. Bestätigung: Subtiler „Eingestempelt."-Toast (2 s) + Redirect auf Puls. „Heute fehlt"-Modul auf Puls zeigt eine Person weniger. + +**Abend-Reflexions-Flow:** + +1. Ab 17:00 erscheint im Header-Bereich ein dezenter „Reflexion offen"-Pill. +2. Klick → Route `/reflexion` (oder `/mein-heute` mit Reflexions-Tab). +3. Person sieht die morgens definierte ToDo-Liste, Klick auf Checkbox-Icon hakt ab, Klick auf X-Icon verwirft (mit optionalem Grund im Inline-Field). +4. Unter der ToDo-Liste: ein Freitext-Bereich „Sonst noch was?" (1.000 Zeichen, monospaced-Counter zeigt verbleibende Zeichen unauffällig). +5. Drei große farbige Ampel-Buttons (Grün/Gelb/Rot), darunter drei Trend-Pfeile (Pflicht, wenn Bezug=Projekt). +6. Optionaler „Blocker?"-Toggle, öffnet ein Inline-Feld. +7. „Reflektiert."-Toast (2 s) + Redirect auf Puls. + +**Vortags-Reveal:** + +- Beim ersten Laden des Puls am Werktag X+1 erscheinen die konsolidierten Vortageseinträge im Feed mit kurzem Fade-In. Header-Hinweis (sehr dezent): „Stand: Mo, 24. Mai". + +--- + +## Visual Design Foundation + +### Color System + +Konkrete Brand-Tokens, abgeleitet aus der ToolCTRL Brand Identity. Werden in `apps/web/src/styles/globals.css` als CSS-Variablen definiert und in `tailwind.config.ts` als Theme-Erweiterung gespiegelt. + +**Neutrals (Hauptpalette):** + +| Token | Hex | Verwendung | +|---|---|---| +| `--color-bg` | `#ffffff` | App-Hintergrund | +| `--color-surface` | `#fafafa` | Card-Hintergrund (sehr subtil von BG getrennt) | +| `--color-surface-elevated` | `#ffffff` | Modal-Hintergrund, schwebende Elemente (mit Schatten) | +| `--color-border` | `#e5e5e5` | Dünne Trennlinien, Card-Borders | +| `--color-border-strong` | `#d4d4d4` | Hover-Border, fokussierter Trennstrich | +| `--color-ink` | `#0a0a0a` | Haupttext (near-black, nicht reines #000 für weichere Lesbarkeit) | +| `--color-ink-muted` | `#525252` | Sekundärtext, Labels | +| `--color-ink-faint` | `#a3a3a3` | Tertiärtext, Platzhalter | +| `--color-brand` | `#0a0a0a` | Primärbutton-Hintergrund, Marken-Akzente (= ink) | +| `--color-brand-fg` | `#ffffff` | Text auf Brand-Flächen | + +**Semantische Ampel-Farben** (auch hell genug für die Brand-Welt): + +| Token | Hex (Foreground/Text) | Hex (Background/Surface) | Verwendung | +|---|---|---|---| +| `--color-ampel-green` | `#15803d` | `#dcfce7` | Status „Im Plan" | +| `--color-ampel-amber` | `#a16207` | `#fef3c7` | Status „Gefährdet" | +| `--color-ampel-red` | `#b91c1c` | `#fee2e2` | Status „Blockiert" | + +**Trend-Indikatoren:** + +| Token | Hex | +|---|---| +| `--color-trend-up` | `#15803d` (besser, ↗︎) | +| `--color-trend-flat` | `#525252` (gleich, →) | +| `--color-trend-down` | `#b91c1c` (schlechter, ↘︎) | + +**Focus / Interaction:** + +| Token | Hex | Verwendung | +|---|---|---| +| `--color-focus-ring` | `#0a0a0a` mit 20% opacity outline | Tastatur-Fokus-Indikator | +| `--color-hover-overlay` | `rgba(0,0,0,0.04)` | Hover-Layer auf Cards/Buttons | + +### Typography System + +**Schriftfamilie:** +- **Sans-Serif (UI):** `Inter` — selbst-gehostet via `@fontsource/inter`. Variable Font (300–900). Lehnt am Brand-Marketing-Sans an (geometrische Sans-Serif). +- **Monospace (Werte/Zeiten):** `JetBrains Mono` — selbst-gehostet via `@fontsource/jetbrains-mono`. Für Zeitstempel, EUR-Beträge, Datums-Anzeigen. + +**Type-Scale** (basierend auf 16 px Body): + +| Token | Größe / Line-Height / Weight | Verwendung | +|---|---|---| +| `text-xs` | 12 / 16 / 500 | Labels, Captions, Audit-Zeitstempel | +| `text-sm` | 14 / 20 / 400 | Sekundärtext, Meta-Information | +| `text-base` | 16 / 24 / 400 | Body, Form-Inputs | +| `text-lg` | 18 / 28 / 500 | Card-Titel | +| `text-xl` | 20 / 28 / 600 | Sektions-Header | +| `text-2xl` | 24 / 32 / 700 | Seitentitel | +| `text-3xl` | 32 / 40 / 700 | Hero im Puls („Stand: Di, 25. Mai") | + +**Voice-Anker-Typografie:** +- „**Stark.**" (Toast): `text-base font-semibold` schwarzer Pill. +- „**Wie läuft es?**" (Reflexions-Header): `text-xl font-medium ink-muted`. +- „**Du hast heute noch nicht gepingt.**" (Puls-CTA-Banner): `text-lg font-medium ink` + Button daneben. + +### Spacing & Layout Foundation + +**Spacing-Skala** (Tailwind-Standard `0.25rem` = `4px`): + +| Token | Wert | Anwendung | +|---|---|---| +| `space-1` | 4 px | Mikro-Padding | +| `space-2` | 8 px | Innen-Padding kleiner Pills | +| `space-3` | 12 px | Standard-Gap zwischen Inline-Elementen | +| `space-4` | 16 px | Card-Inner-Padding small | +| `space-6` | 24 px | Card-Inner-Padding default | +| `space-8` | 32 px | Sektions-Abstand | +| `space-12` | 48 px | Seitenrand-Padding | +| `space-16` | 64 px | Hero-Abstand | + +**Radii:** + +| Token | Wert | Verwendung | +|---|---|---| +| `radius-sm` | 6 px | kleine Pills, Inputs | +| `radius-md` | 10 px | Buttons, Tags | +| `radius-lg` | 16 px | Cards, Modal-Container | +| `radius-xl` | 24 px | Hero-Cards, Brand-konforme Big-Rounded-Boxes (vgl. Need-Help-PDF Brand-Stil) | +| `radius-full` | 9999 px | Pills, Avatars | + +**Schatten (sehr subtil, near-black mit niedriger Opacity):** + +| Token | Wert | Verwendung | +|---|---|---| +| `shadow-sm` | `0 1px 2px rgba(10,10,10,0.04)` | Cards in Default-State | +| `shadow-md` | `0 4px 12px rgba(10,10,10,0.06)` | Hover-State Cards | +| `shadow-lg` | `0 12px 32px rgba(10,10,10,0.10)` | Modale, Dropdowns | + +**Grid & Container:** + +- **Container-Max-Width:** 1.280 px, horizontal zentriert. +- **Container-Padding-X:** 24 px (mobile) → 48 px (≥ md). +- **Grid:** 12-Spalten-Grid mit 24 px Gap auf Desktop. +- **Puls-Layout:** 3-Spalten-Grid für die drei Bereiche; auf Tablet 2-Spalten; auf Mobile single-column-Stack. + +### Accessibility Considerations + +- **WCAG 2.1 AA** als Mindeststandard für Kontraste: + - `--color-ink` auf `--color-bg`: 19.5:1 ✅ + - `--color-ink-muted` auf `--color-bg`: 8.1:1 ✅ + - `--color-ampel-green` Text auf Green-BG: 5.5:1 ✅ + - `--color-ampel-amber` Text auf Amber-BG: 5.8:1 ✅ + - `--color-ampel-red` Text auf Red-BG: 6.1:1 ✅ +- **Farbe ist nie alleinige Information.** Ampel hat zusätzlich Icon + Text-Label („Im Plan", „Gefährdet", „Blockiert"). Trend hat Pfeil-Icon zusätzlich zur Farbe. +- **Fokus-Indikator** auf jedem interaktiven Element — `outline: 2px solid var(--color-ink)` mit 2 px Offset. +- **Tastatur-Navigation:** Tab-Order folgt visueller Reihenfolge; alle interaktiven Elemente sind ohne Maus erreichbar. +- **Screenreader:** Radix-Primitives liefern korrekte ARIA-Roles automatisch. Custom-Components ergänzen `aria-label` wo nötig (z. B. Icon-Only-Buttons). +- **Reduzierte Bewegung:** `prefers-reduced-motion: reduce` schaltet Fade-In-Animationen auf 0 ms. +- **Schriftgrad-Mindest:** keine Schriftgröße kleiner als 12 px im UI. + +--- + +## Design Direction Decision + +### Design Directions Explored + +Drei Visualrichtungen wurden konzeptionell erwogen: + +1. **"Office Minimal" (gewählt)** — fast vollständig weiß-grau-schwarz, Typografie-getragen, einzelne semantische Farbakzente, sehr ruhig. +2. **"Brand Dark Hero"** — schwarzer Header und Hero-Bereich wie in den LinkedIn-Posts; Body auf Weiß. Dramatischer, marken-näher. +3. **"Colorful Status Pop"** — Brand monochrom, aber Ampeln und Bereichs-Akzente sehr farbig und groß. Spielerischer Versuch. + +### Chosen Direction + +**Office Minimal** — die Anwendung präsentiert sich als ruhiges, fast nüchternes Werkzeug, das durch Klarheit überzeugt, nicht durch visuelle Effekte. + +### Design Rationale + +- **Passt zum User-Kontext:** Internes Tool, das mehrmals täglich geöffnet wird. „Brand Dark Hero" ermüdet auf Dauer, „Colorful Status Pop" wird laut. +- **Passt zur Brand-Reduktion:** ToolCTRL ist visuell minimal — Office Minimal überträgt diese Reduktion in die App. +- **Diensthandelnde Klarheit:** Wer schnell wissen will, „läuft das Projekt?", soll die Antwort in 1 s lesen können — das geht am besten mit hohem Kontrast und wenig Visual-Noise. +- **Brand-Akzente bleiben übrig:** Der `^`-Caret als wiederkehrendes Element, der schwarze Pill-Button-Stil, große Radii — das bringt die Marken-DNA in die App, ohne sie aufzudrängen. + +### Implementation Approach + +- Weiß als Default-Background überall. +- Cards mit `radius-lg` (16 px), `shadow-sm`, `border: 1px solid var(--color-border)`. +- Primärbuttons schwarz mit weißem Text + `radius-md` (10 px). +- Sekundärbuttons mit transparentem Hintergrund + 1 px Border in `--color-ink`. +- Ghost-Buttons (z. B. „Abbrechen") ganz ohne Border, nur Text-Color. +- Ampel-Indikatoren als kleine farbige Punkte/Pills mit kontrastreichem Text auf zarter Background-Farbe. +- Brand-Element `^` als kleines Detail im Logo-Lockup oben links: `cockpit ^`. +- Hero-Texte (z. B. „Stand: Di, 25. Mai") in `text-3xl font-bold` schwarz auf weiß. + +--- + +## User Journey Flows + +### UJ-1 — Tagesrhythmus (Morgen → Abend → Nächster Morgen) + +```mermaid +flowchart TD + A[Login via Authentik] --> B[Puls-Seite] + B --> C{Schon eingestempelt?} + C -- nein --> D[CTA Einstempeln klicken] + D --> E[/mein-heute öffnet sich] + E --> F[ToDos tippen, Enter erzeugt neue Zeile] + F --> G[Eingestempelt-Button] + G --> H[Stark.-Toast 2s] + H --> B + C -- ja --> I[Tagsüber arbeiten / weitere Cockpit-Lookups] + I --> J{Nach 17:00?} + J -- nein --> I + J -- ja --> K[Reflexion-offen-Pill im Header] + K --> L[/reflexion oder /mein-heute Tab Reflexion] + L --> M[ToDos abhaken / verwerfen] + M --> N[Freitext was sonst noch] + N --> O[Ampel + Trend setzen] + O --> P{Blocker?} + P -- ja --> Q[Blocker-Text inline] + P -- nein --> R[Reflektieren-Button] + Q --> R + R --> S[Reflektiert.-Toast] + S --> B + B --> T[Nächster Werktag 00:05: Cron publiziert] + T --> U[Vortrags-Reveal im Feed mit Fade-In] +``` + +**Touchpoints im Detail:** + +- **Morgen (CM1a ≤ 60 s):** Person + Bereich (Default) + 3-5 ToDos + Submit. UI muss Tab-Reihenfolge perfekt einhalten — Erste ToDo-Zeile bekommt sofort Fokus. +- **Tagsüber:** Cockpit darf aufgerufen werden, aber zeigt der Person nur die eigene Sicht + den Puls; die ToDos sind nur für sie selbst sichtbar. +- **Abend (CM1b ≤ 90 s):** Tagesplan-Echo (ToDos vom Morgen erscheinen mit kurzem Fade-In), schnelles Abhaken via Klick, ein bis drei Sätze Freitext, drei große Buttons für Ampel, drei Pfeile für Trend, fertig. +- **Nächster Morgen:** Vortrags-Reveal als zarte UI-Akzentuierung; sonst bleibt das Cockpit ruhig. + +### UJ-2 — Jourfixe-Moderation (Di 11:00, P1/P4) + +```mermaid +flowchart TD + A[PM öffnet Cockpit am Di 11:00] --> B[Puls-Seite] + B --> C[Bereichs-Strip lesen: 3 Ampelstatus] + C --> D[Blocker-Liste lesen] + D --> E[Pflicht-Minimum-Übersicht lesen] + E --> F{Rote oder gelbe Projekte?} + F -- ja --> G[Klick auf rotes/gelbes Projekt] + G --> H[Projekt-Detail mit Updates + Meilensteinen] + H --> I[Diskussion mit Verantwortlichem] + I --> J{Weitere Projekte?} + J -- ja --> G + J -- nein --> K[Optional: Jourfixe-Meta-Update als allgemeines Update] + F -- nein --> K + K --> L[Jourfixe beendet] +``` + +### UJ-3 — Verfall früh erkennen (P1, P4) + +```mermaid +flowchart TD + A[Person scrollt durch Puls] --> B[Projekt mit rotem Status auffällt] + B --> C[Klick auf Projekt-Card] + C --> D[Projekt-Detail zeigt: letzter Update vor X Tagen] + D --> E[Letzte Reflexionen mit Trend ↘︎] + E --> F[Meilenstein-Status: X von Y überzogen] + F --> G[Pflicht-Minimum-Panel zeigt unerfüllte Punkte] + G --> H[Person schreibt allgemeines Update mit Bezug auf Projekt] +``` + +### UJ-4 — Onboarding eines neuen Mitarbeiters + +```mermaid +flowchart TD + A[Admin legt User in Authentik an + Group] --> B[User loggt sich erstmals ein] + B --> C[OIDC-Flow läuft, Backend legt User-Zeile an] + C --> D[Default-Bereich aus Authentik-Group gemappt] + D --> E[Puls-Seite: Hero-Hinweis Erstes Tagesupdate] + E --> F[User klickt Einstempeln] + F --> G[Schreibt erste ToDos] + G --> H[Erscheint in Anwesenheits- + Streak-Liste, Streak=1] +``` + +### UJ-5 — Projekt im Cockpit-Sicht anlegen (P1) + +```mermaid +flowchart TD + A[P1 legt Steckbrief in Outline an] --> B[Wechsel ins Cockpit] + B --> C[Bereichs-Detail → Neues Projekt] + C --> D[Modal: Name, Verantwortliche/r, Projektart, Arbeitsmodus] + D --> E[Felder: Steckbrief-URL Pflicht, Plane-Board-URL Pflicht] + E --> F[Optional: DoD-Text, Risikoliste-URL] + F --> G[Optional: erste Meilensteine inline] + G --> H[Speichern → Projekt erscheint im Bereich + im Puls] + H --> I[Pflicht-Minimum-Indikator startet bei 4-6 erfüllten Punkten] +``` + +### Journey Patterns + +- **„Puls-zurück" als roter Faden:** Nach jedem Schreib-Touchpoint kehrt die Person auf den Puls zurück. Das verankert den Puls als Heimat. +- **Toast als kleinster Erfolg:** Jede Mutation → kurze Toast-Bestätigung mit Voice-Anker (`Stark.`, `Reflektiert.`, `Blocker offen.`). +- **Inline-Eingabe statt Modal-Stapel:** Wo möglich, werden Bezüge inline gesetzt, nicht in einem zweiten Modal. Modals nur für Projekt-Anlage und Blocker-Auflösung. +- **Breadcrumb über Tabs:** Statt Tabs nutzen wir Breadcrumbs (Puls → Bereich → Projekt) für sauberere Navigation und einfachere Tastatursteuerung. + +### Flow Optimization Principles + +1. **First-Field-Focus.** Jedes Modal/jeder Schreib-Flow setzt den Cursor automatisch auf das erste sinnvolle Feld. +2. **Enter ist Submit, wo es Sinn ergibt.** ToDo-Liste: Enter → neue Zeile. Reflexion: Cmd/Ctrl+Enter → Submit. +3. **Esc schließt Modale ohne Speichern** (Standard für alle Radix-Dialoge). +4. **Keine Multi-Step-Wizards.** Selbst die Reflexion ist ein single-screen Flow mit gestaffeltem visuellem Aufbau, nicht „Schritt 1 von 3". +5. **Konsequente Rückkehr.** Nach Erfolg: zurück zum Puls. Nach Abbruch: zurück zur Vor-Seite via Browser-History (Esc + Back). + +--- + +## Component Strategy + +### Design System Components + +Aus dem Tailwind + Radix-Stack stehen folgende Primitives ohne Eigenentwicklung zur Verfügung: + +- **Radix UI:** Dialog, Dropdown Menu, Select, Checkbox, Tabs, Toast (via Sonner), Tooltip, Popover, Hover Card, Collapsible, Accordion, Avatar, Form, Label, Toggle, Toggle Group. +- **Lucide React:** ~1.500 monochrome Icons (Suche, Settings, Plus, Check, X, ChevronDown, ArrowUpRight, ArrowRight, ArrowDownRight, Calendar, Clock, User, Filter, etc.) +- **Sonner:** Toast-Library. +- **React Hook Form + Zod:** Form-Management. +- **@fontsource/inter + @fontsource/jetbrains-mono:** Selbst-hostbare Brand-Fonts. + +### Custom Components + +15 hauseigene Komponenten in `apps/web/src/components/ui/`: + +| Komponente | Zweck | Verwendung in FRs | +|---|---|---| +| `Button` | 3 Varianten (primary/secondary/ghost), 3 Größen (sm/md/lg) | Überall | +| `Card` | Standard-Container mit `radius-lg`, `shadow-sm`, `border` | Puls, Bereiche, Projekte, Listen | +| `Input` | Text-Input mit Brand-Style | FR-21, FR-22, FR-26 | +| `Textarea` | Multi-Line-Eingabe, Counter unten rechts | FR-26 | +| `Select` | Auf Radix-Select, kompakter dropdown | FR-22 Bezug, Bereich | +| `Checkbox` | Auf Radix-Checkbox, animiertes Häkchen | FR-26 ToDo abhaken | +| `AmpelButton` | 3-Wege-Toggle Grün/Gelb/Rot, große Klick-Targets | FR-26 | +| `TrendButton` | 3-Wege-Toggle ↗︎/→/↘︎ | FR-26 | +| `Dialog` | Auf Radix-Dialog, mit Brand-Header und `radius-xl` | FR-31 Blocker, FR-45 Abschluss | +| `Tooltip` | Auf Radix-Tooltip, schwarzer Bubble mit weißem Text | Hover-Erläuterungen | +| `Toast` | via Sonner, schwarzer Pill mit weißem Text, Bottom-Right | Voice-Ankerbestätigungen | +| `SkeletonCard` | Graue Card-Skelette identischer Geometrie wie echte Cards | Loading-States überall | +| `AmpelBadge` | Status-Pill mit Farbpunkt + Label + optionalem Trend-Pfeil | FR-9, FR-21 | +| `PflichtMinimumIndicator` | Sechs kleine Quadrate horizontal, gefüllt/leer | FR-9 (Projekt-Card), FR-7 (Puls) | +| `KeyCaret` | Brand-`^`-Element als SVG, einbindbar in Header/Logo | TopNav, Toast-Bestätigung | + +### Component Implementation Strategy + +- **Compound-Components statt Prop-Explosion:** ``, ``, ``, `` für flexible Layouts ohne 20 Props. +- **`cva` (class-variance-authority)** für Variant-Verwaltung (`Button.variants.primary`, `Button.variants.secondary`). +- **Storybook entfällt im MVP** — bei 15 Komponenten und einem Frontend-Team können wir per `apps/web/src/routes/dev/components.tsx` eine eigene „Component Gallery"-Seite bauen, die nur in dev-Mode sichtbar ist. +- **Snapshot-Tests via Vitest + jsdom** für Komponenten-Stabilität. + +### Implementation Roadmap + +1. **Sprint 1 — UI-Kit-Bootstrap:** Button, Card, Input, Textarea, KeyCaret, Toast, SkeletonCard. Component-Gallery-Route. +2. **Sprint 2 — Status-Spezifika:** AmpelButton, TrendButton, AmpelBadge, PflichtMinimumIndicator. +3. **Sprint 3 — Interaktion:** Dialog, Tooltip, Select, Checkbox. +4. **Sprint 4 — Feature-Komponenten:** Puls-Module, Bereichs-Details, Tagesrhythmus-Flow. +5. **Sprint 5 — Spezialfälle:** Sales-Kanban (Drag-and-Drop via dnd-kit), Lead-Funnel. + +--- + +## UX Consistency Patterns + +### Button Hierarchy + +| Variant | Verwendung | Visual | +|---|---|---| +| **Primary** | Hauptaktion einer Seite (Einstempeln, Reflektieren, Speichern) | Schwarzer Hintergrund, weißer Text, `radius-md` | +| **Secondary** | Alternative Aktion (Abbrechen mit Aktion, Sekundäre Navigation) | Transparenter Hintergrund, 1 px schwarzer Border, schwarzer Text | +| **Ghost** | Tertiär (Abbrechen, Schließen, Edit-Hover-Actions) | Kein Border, kein Hintergrund, nur Text. Hover bekommt `--color-hover-overlay`. | +| **Destructive** | Löschen, Archivieren | Wie Primary, aber `--color-ampel-red`-Hintergrund (selten genutzt — Cockpit kennt fast keine Lösch-Aktionen) | +| **Icon-Only** | Inline-Aktionen (Edit, X) | Quadratisch 32×32, Ghost-Stil mit Lucide-Icon zentral. `aria-label` Pflicht. | + +**Größenstaffel:** `sm` (28 px Höhe), `md` (36 px Höhe, Default), `lg` (44 px Höhe, für Haupt-CTAs auf dem Puls). + +### Feedback Patterns + +- **Sofortige optimistic Reaktion** bei Mutations (Checkbox abhaken, Ampel setzen). Bei Server-Fehler: Rollback + roter Toast „Speichern fehlgeschlagen — bitte nochmal versuchen.". +- **Toast bei jeder Mutation** mit Voice-Anker. Toast erscheint bottom-right, 2 s sichtbar, ohne Dismiss-Button (außer Fehler-Toasts haben ein X). +- **Skeleton beim Initial-Load.** Niemals leerer Bildschirm, niemals Spinner als Alleinstellungsmerkmal. +- **Empty-States als Tropfen-Information:** Wenn ein Feed leer ist, nicht „Keine Daten" — sondern „Hier erscheinen die Updates des Tages, sobald jemand reflektiert hat.". Voice-konform. +- **Error-States immer mit Aktion.** Statt nur „Fehler beim Laden": „Konnte Projekt nicht laden. [Erneut versuchen]". + +### Form Patterns + +- **Labels über dem Feld**, nicht inline-floating. +- **Pflichtfelder mit `*`** im Label, deutlich aber dezent. +- **Validation onBlur**, nicht onChange — verhindert nervige Rotmeldungen während Tippen. +- **Submit-Button disabled** während Pending-Request mit Mini-Spinner statt komplettem Loading-Overlay. +- **Form-Errors unter dem Feld**, schwarzer Text, kein Rot (außer Border des Feldes wird rot). +- **Eine Spalte Form-Layout**, kein Side-by-Side außer Datum/Uhrzeit-Paare. + +### Navigation Patterns + +- **TopNav fest:** `cockpit ^` Logo links · Tabs (Puls / Entwicklung / Vertrieb / Marketing / Mein heute / Archiv) · rechts: Streak-Badge + Avatar. +- **Active-Indicator:** Aktiver Tab bekommt einen 2 px dicken schwarzen Underline. Kein farbiges Highlight. +- **Breadcrumbs in Detail-Views:** „Puls › Entwicklung › Projekt X" (Klickbare Pfad-Segmente). +- **Browser-Back funktioniert immer.** React-Router-History-Pflege. +- **Keyboard-Shortcuts:** `g p` Puls, `g h` Mein heute, `g e` Entwicklung, `g v` Vertrieb, `g m` Marketing, `g a` Archiv. `?` öffnet Cheatsheet. `n` öffnet „Neuer Blocker"-Dialog. Esc schließt. + +### Additional Patterns + +- **Status-Pill-Konvention:** Immer als `[� Label]` formatieren — kleiner farbiger Punkt + Label, in Pill mit `radius-full`. +- **Datum-Anzeige:** „Mo, 25. Mai" für Werktage; „vor 2 Tagen" für relative Zeitangaben; ISO-Format `2026-05-25` nur in technischen Audit-Anzeigen. +- **EUR-Anzeige:** Tausenderpunkt + Komma + Euro-Zeichen mit Leerzeichen, z. B. `12.500,00 €`. Niemals `12500€` oder `EUR 12500`. +- **Avatar-Konvention:** Initialen (max 2 Zeichen) in schwarzem Kreis mit weißer Schrift; Bilder optional aus Authentik-Profilbild Phase 2. + +--- + +## Responsive Design & Accessibility + +### Responsive Strategy + +- **Desktop-first** (`min-width` queries). +- **Drei Sicht-Ebenen:** + - **Desktop (≥ 1280 px):** Volles 3-Spalten-Layout im Puls, alle Tabs in TopNav sichtbar. + - **Tablet (768–1279 px):** Puls auf 2-Spalten-Grid; TopNav klappt überlange Tabs in „Mehr"-Dropdown. + - **Mobile (< 768 px):** Single-Column-Stack; TopNav wird zu Bottom-Tab-Bar mit 4 Items (Puls / Mein heute / Bereiche-Dropdown / Profil). Reflexion und Einstempeln sind **lesbar**, das Schreiben wird ausdrücklich auf Desktop empfohlen (Info-Hinweis im Mobile-Layout). + +### Breakpoint Strategy + +| Breakpoint | Größe | Verwendung | +|---|---|---| +| `sm` | ≥ 640 px | Vereinzelte Layout-Anpassungen | +| `md` | ≥ 768 px | Tablet-Layout aktiviert | +| `lg` | ≥ 1024 px | Mehr Padding, größere Cards | +| `xl` | ≥ 1280 px | Desktop-Volllayout, 3-Spalten | +| `2xl` | ≥ 1536 px | Container bleibt bei 1280 px gecappt, mehr Außen-Padding | + +### Accessibility Strategy + +- **Standard:** WCAG 2.1 AA durchgängig. +- **Keyboard-First-Pflicht.** Alle interaktiven Elemente erreichbar via Tab, Aktivierung mit Enter/Space. +- **Focus-Ring sichtbar:** `outline: 2px solid var(--color-ink)` + 2 px Offset auf jedem fokussierten Element. +- **Skip-Link** ganz oben („Zum Hauptinhalt springen") für Screenreader. +- **Heading-Hierarchie sauber:** `

` einmal pro Seite (Seitentitel), `

` für Sektionen, `

` für Sub-Sektionen. +- **Live-Regionen für Toast-Notifications:** `aria-live="polite"` für Erfolg, `aria-live="assertive"` für Fehler. +- **Alt-Texte** auf jedem nicht-dekorativen Bild und SVG. `