--- 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.