Internes Project-Management-Tool für ToolCTRL — ergänzend zu Plane (Tickets) und Outline (Doku). Umfasst Puls-Übersicht, Tagesrhythmus (Lock-in + Reflexion), Bereichs-Detail-Views (Entwicklung/Vertrieb/Marketing), Pflicht-Minimum-Check und Blocker-Lifecycle gemäß Projektmanagement-Leitfaden v3.0. Stack: pnpm-Monorepo · Vite + React 18 + Tailwind · Fastify + tRPC v11 + Drizzle ORM · Postgres 16 · Authentik OIDC (Phase 2, Dev-Auto-Login aktiv). Planungs-Artefakte unter _bmad-output/planning-artifacts/. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
967 lines
56 KiB
Markdown
967 lines
56 KiB
Markdown
---
|
||
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.
|