Gitea läuft seit 2026-11-11 auf hivegrid-control (git.hg-int.dev); git.tool-ctrl.de wird abgeschaltet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
464 lines
35 KiB
Markdown
464 lines
35 KiB
Markdown
# everythingbutwork
|
||
|
||
Internes Web-Tool von **ToolCTRL** für den täglichen Überblick über alle Projekte und Bereiche. Es ist das dritte Werkzeug neben **Plane** (Tickets) und **Outline** (Doku) und operationalisiert den ToolCTRL Projektmanagement-Leitfaden v3.0.
|
||
|
||
> Falls du in dieses Repo neu reinrutschst (neue Session, anderes Modell): lies **`CONVENTIONS.md`** vor jedem Edit, und schau in **`_bmad-output/planning-artifacts/`** für den vollen PRD/Architektur/UX-/Stories-Kontext.
|
||
|
||
---
|
||
|
||
## Was die App tut
|
||
|
||
| Bereich | Feature |
|
||
| ------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| **Dashboard** (`/dashboard`) | Unternehmens-Übersicht: Bereichs-Ampelstatus, **Showstopper** (offene Showstopper-Risiken), **Projekte in Eskalation/Taskforce**, „Heute fehlt"-Liste, bereichsübergreifende Blocker, Pflicht-Minimum-Übersicht, Jourfixe-Hinweis, „Heute im Team" (ToDos), „Zeit im Team heute" (Live-Tracking, Wall-Clock), Vortags-Feed |
|
||
| **Bereich** (`/bereich`, `/bereich/<slug>`) | Tab-Switcher Entwicklung / Vertrieb / Marketing — Projekt-Liste (Kundenprojekte / Interne Projekte) mit Pflicht-Minimum- & Modus-Indicator. **Vertrieb** zusätzlich: Lead-Teaser + „Projekte mit Vertriebsaktivität" (bereichsübergreifend) |
|
||
| **Projekt** (`/projekt/:id`) | Detail: Ziel/Maxime/Nicht-Ziele/Liefergegenstände + Zieldreieck, **Phasen-Lebenszyklus mit Gate-Reviews**, **Statusbericht** (Ampel/Trend + Verlauf), **Stakeholder-Matrix**, **Risiko- & Chancen-Register**, **verknüpfte Leads**, **Änderungs-Log**, Verlinkungen, Pflicht-Minimum, Meilensteine (mit Ergebnis/Kundennutzen + Datums-Warnung), DoD, Projekt-Abschluss (Checkliste + Vermerk, P1), Projekt-Modus |
|
||
| **Leads** (`/leads`, `/lead/:id`) | Vertriebs-CRM: Lead-Liste nach Status (Cold/Warm/Hot/Won/Lost), Dossier (Stammdaten, Pain & Bedarf, Soft Factors, Strategie), Kontaktpersonen, Austausch-Log, **CSV-Import**, Lead→Deal-Promotion, **Projekt-Verknüpfung** |
|
||
| **Heute** (`/heute`) | Tagesrhythmus: Lock-in mit ToDo-Liste, Auto-Carryover offener ToDos vom Vortag, **ToDos mitten am Tag hinzufügen**, Timer pro ToDo, Zeiterfassungs-Timeline mit Edit/Delete/Nachtrag |
|
||
| **Reflexion** (`/reflexion`) | Abend-Reflexion: ToDos abhaken + **Titel inline korrigieren**, Freitext, Ampel + Trend, optional Blocker. Editierbar bis 00:00 Berlin |
|
||
| **Archiv** (`/archiv`) | Abgeschlossene Projekte, gruppiert nach Bereich |
|
||
|
||
**Querschnittsfunktionen**
|
||
|
||
- **Leads / CRM**: Qualitative Lead-Dossiers im Vertrieb, **CSV-Import** (header-basiert, Dedup über Firmenname, Adresse → Standort, Notizen → Austausch-Log), optionale **Verknüpfung zu einem Projekt** (egal welcher Bereich) → Projekte mit Leads erscheinen in der Vertriebs-Sicht, ohne redundant angelegt zu werden.
|
||
- **Projektmanagement-Tiefe** (operationalisiert Leitfaden v3.0): Projekt-Modus (Relax/Standard/Eskalation/Taskforce), 4-Phasen-Lebenszyklus mit Gate-Reviews, Statusbericht (Ampel/Trend/Text + Verlauf), Stakeholder-Analyse mit Macht/Interesse-Matrix, Risiko- & Chancen-Register (W×A, Showstopper, Owner, Strategie), Änderungs-Log (Scope-Creep), Zieldreieck, strukturierter Abschluss.
|
||
- **Zeiterfassung**: Persistente Timer-Pill in der TopNav (Live-Sekundenzähler), Quick-Picker mit ToDos + Kategorien + Freitext, retroaktiver Nachtrag. „Gesamt" pro Person ist **Wall-Clock** (überlappende parallele Tasks zählen einmal); pro Aufgabe wird die Summe getrennt ausgewiesen („getrackt").
|
||
- **Blocker**: Inline in der Reflexion oder über `n`-Shortcut bzw. den globalen „Neuer Blocker"-Dialog. Auflösung nur durch den Projekt-P1.
|
||
- **Pflicht-Minimum-Check** (Leitfaden §9): 6-Punkte-Checkliste pro Projekt; Punkt „Status-Ampel aktuell (≤ 7 Werktage)" wird per Cron **pro Projekt** aus dem jüngsten Statusbericht berechnet.
|
||
- **Streak**: aufeinanderfolgende Werktage mit voller Reflexion. Wochenenden, **bundesweite Feiertage** und gemeldete Abwesenheiten sind neutral (brechen nicht). Flammen-Pill rechts oben.
|
||
- **Keyboard-Shortcuts**: `g d/b/h/a` Navigation, `g e/v/m` direkt in Entwicklung/Vertrieb/Marketing, `n` Neuer Blocker, `?` Cheatsheet.
|
||
|
||
---
|
||
|
||
## Tech Stack
|
||
|
||
| Layer | Wahl |
|
||
| ------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
||
| Sprache | **TypeScript** strict, ESM überall |
|
||
| Repo-Layout | **pnpm**-Monorepo (`pnpm-workspace.yaml`) |
|
||
| Node | **22+** |
|
||
| Frontend | **Vite 5 + React 18 + Tailwind 4 + React Router 7 + React Query 5 + Sonner** (Toasts) + **lucide-react** (Icons) |
|
||
| Backend | **Fastify 5 + tRPC v11** (`@trpc/server/adapters/fastify`) |
|
||
| ORM | **Drizzle 0.38** + **drizzle-kit** |
|
||
| Datenbank | **Postgres 16** |
|
||
| Auth | **Authentik OIDC** via `openid-client v6` (Auth Code + PKCE), Session-Cookies HttpOnly+SameSite=Lax, Sessions in Postgres |
|
||
| Validation | **Zod 3** (geteilt zwischen FE und BE via `@cockpit/shared`) |
|
||
| Logging | **pino** (Fastify-built-in) |
|
||
| Styling | Tailwind 4 + CSS-Custom-Properties · Hausschrift **Space Grotesk** (selbst gehostet) · Keycap-Button-Stil |
|
||
| Tests | **Vitest** (Unit + tRPC) |
|
||
| Reverse-Proxy | **Caddy 2** (siehe `Caddyfile`) |
|
||
| CI | **Gitea Actions** (`.gitea/workflows/ci.yml`) |
|
||
| Deployment | **Docker Compose** (`docker-compose.yml`) auf ToolCTRL-interner Infra |
|
||
|
||
⚠️ **Namespace-Hinweis**: Die internen pnpm-Packages heißen `@cockpit/*` (Historie), die App selbst heißt **everythingbutwork**. Nicht umbenennen — das würde unzählige Imports brechen ohne Mehrwert.
|
||
|
||
---
|
||
|
||
## Verzeichnisstruktur
|
||
|
||
```
|
||
.
|
||
├── apps/
|
||
│ ├── web/ Vite + React SPA (Port 7173)
|
||
│ │ ├── public/ Brand-Assets (Logo SVG, Fonts, Favicon)
|
||
│ │ ├── src/
|
||
│ │ │ ├── components/
|
||
│ │ │ │ ├── blocker/ BlockerDialog
|
||
│ │ │ │ ├── layout/ AppShell, TopNav
|
||
│ │ │ │ ├── time/ TimerPill, TimelineSection, TeamTimeSection
|
||
│ │ │ │ └── ui/ Button, Card, KeyCaret, SkeletonCard, ToolCtrlLogo
|
||
│ │ │ ├── hooks/ useCurrentUser
|
||
│ │ │ ├── lib/ router, trpc, trpc-provider, voice,
|
||
│ │ │ │ format-duration, time-categories,
|
||
│ │ │ │ lead-meta, project-meta
|
||
│ │ │ ├── routes/ dashboard, bereich, projekt, heute, reflexion,
|
||
│ │ │ │ leads, lead, archiv, login, not-found
|
||
│ │ │ └── styles/globals.css
|
||
│ │ ├── index.html
|
||
│ │ └── vite.config.ts
|
||
│ └── api/ Fastify Backend (Port 7100)
|
||
│ ├── crontab System-Cron-Definitionen (Containerseitig)
|
||
│ └── src/
|
||
│ ├── auth/ oidc, routes (login/callback/logout/dev-login),
|
||
│ │ session, user, middleware
|
||
│ ├── cron/routes.ts /internal/cron/* (Bearer-Token geschützt)
|
||
│ ├── lib/ werktag (Mo-Fr Berlin + Feiertage), holidays,
|
||
│ │ streak, csv, ampel, pflicht-minimum, logger
|
||
│ ├── routers/ _app, user, area, project, deal, gtm, lead,
|
||
│ │ risk, stakeholder, change, campaign, blocker,
|
||
│ │ day-entry, time, dashboard
|
||
│ ├── env.ts Zod-validierte Env-Vars
|
||
│ ├── db.ts Singleton-Drizzle-Client
|
||
│ ├── trpc.ts Context, protectedProcedure, withAudit
|
||
│ ├── types.ts Fastify-Augmentation für req.cockpitUser
|
||
│ └── server.ts Fastify-Bootstrap + Healthcheck
|
||
├── packages/
|
||
│ ├── db/ Drizzle-Schema + Migrationen
|
||
│ │ ├── src/{schema,client,seed,index,migrate,base-data}.ts
|
||
│ │ ├── migrations/ 0000_init … 0011_lead_project_link
|
||
│ │ └── drizzle.config.ts
|
||
│ └── shared/ Zod-Schemas + Konstanten + Types
|
||
│ └── src/schemas/ day-entry, project, deal, campaign, time-entry,
|
||
│ lead, risk, stakeholder, change
|
||
├── _bmad-output/planning-artifacts/ PRD, Architektur, UX-Spec, Epics, Decision-Log
|
||
├── docs/operations.md Ops-Guide (Authentik-Setup, Backup, Restore)
|
||
├── scripts/backup.sh pg_dump-Skript (im Postgres-Container)
|
||
├── docker-compose.yml Produktion (web + api + postgres + caddy)
|
||
├── docker-compose.dev.yml Nur Postgres für lokale Entwicklung
|
||
├── Caddyfile Reverse-Proxy-Config
|
||
├── CONVENTIONS.md Bindende Code-Konventionen
|
||
├── .gitea/workflows/ci.yml Gitea Actions
|
||
├── .env.example Env-Template
|
||
├── eslint.config.mjs Flat-Config (ESLint 9)
|
||
├── tsconfig.base.json Shared TS-Config
|
||
└── pnpm-workspace.yaml
|
||
```
|
||
|
||
---
|
||
|
||
## Lokal loslegen
|
||
|
||
### Vorbereitung (einmalig)
|
||
|
||
```bash
|
||
# Repo & Tooling
|
||
pnpm install # 4 Workspaces installieren
|
||
cp .env.example .env # Defaults reichen für lokale Entwicklung
|
||
```
|
||
|
||
### Dev-Stack starten
|
||
|
||
```bash
|
||
# 1) Postgres im Docker-Container (Port 55432, NICHT 5432!)
|
||
docker compose -f docker-compose.dev.yml up -d postgres
|
||
|
||
# 2) Migration anwenden (Schema initialisieren)
|
||
DATABASE_URL='postgres://cockpit:cockpit_dev@localhost:55432/cockpit' \
|
||
pnpm --filter @cockpit/db migrate:up
|
||
|
||
# 3) Optional: Seed mit Test-Areas (Entwicklung/Vertrieb/Marketing/Allgemein)
|
||
DATABASE_URL='postgres://cockpit:cockpit_dev@localhost:55432/cockpit' \
|
||
pnpm --filter @cockpit/db run seed
|
||
|
||
# 4) API + Web parallel starten (zwei Terminals)
|
||
pnpm --filter @cockpit/api dev # → http://localhost:7100
|
||
pnpm --filter web dev # → http://localhost:7173
|
||
```
|
||
|
||
### Browser öffnen
|
||
|
||
**http://localhost:7173**
|
||
|
||
Im Dev-Modus läuft ein **Auto-Login** als Admin-User `dejan@tool-ctrl.de`. Authentik ist nicht erforderlich; der Redirect zu `/api/auth/dev-login` setzt sofort eine Session.
|
||
|
||
### Wichtige Ports
|
||
|
||
| Service | Port | Quelle |
|
||
| --------------- | --------- | ------------------------------------------------------------------------- |
|
||
| Vite Dev-Server | **7173** | `apps/web/vite.config.ts` |
|
||
| Fastify API | **7100** | `.env` (`PORT`) |
|
||
| Postgres (Dev) | **55432** | `docker-compose.dev.yml` (5432 würde mit Mac-System-Postgres kollidieren) |
|
||
|
||
### Häufig genutzte Scripts
|
||
|
||
```bash
|
||
pnpm typecheck # alle Workspaces parallel
|
||
pnpm lint # ESLint (flat config)
|
||
pnpm format # Prettier write
|
||
pnpm test # Vitest (--passWithNoTests für leere)
|
||
pnpm --filter @cockpit/db studio # Drizzle Studio (Browser-DB-Inspector)
|
||
pnpm --filter @cockpit/db migrate:generate --name=<name> # neue Migration
|
||
```
|
||
|
||
---
|
||
|
||
## Datenbank
|
||
|
||
Postgres 16, Drizzle als ORM und Migrator. **Schema-Single-Source-of-Truth**: `packages/db/src/schema.ts`.
|
||
|
||
### Tabellen (aktueller Stand nach Migration 0011)
|
||
|
||
| Tabelle | Zweck |
|
||
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `user` | Mitarbeiter (`authentik_sub` als Identitäts-Anker), `is_admin`-Flag |
|
||
| `session` | Server-Side Sessions (Postgres-persistent), Browser kennt nur Cookie-`id` |
|
||
| `audit_event` | Audit-Trail (Tabelle vorhanden; `withAudit`-Wrapper aktuell nicht verdrahtet) |
|
||
| `area` | Bereich (entwicklung / vertrieb / marketing / allgemein) |
|
||
| `project` | Projekt: Name, `description`, `maxime`, `ziel`/`nicht_ziele`/`liefergegenstaende`, `zieldreieck_flex`, `modus`, `phase`, Outline-/Plane-URLs, Arbeitsmodus, `pflicht_minimum_state` JSONB, Status-/Trend-Cache, `abschlussvermerk`, Archiv-Felder |
|
||
| `milestone` | Cockpit-Sicht-Meilenstein pro Projekt (+ `ergebnis`, `kundennutzen`) |
|
||
| `project_status` | Statusbericht-Historie pro Projekt (Ampel/Trend/Text, extern-Flag) |
|
||
| `phase_gate` | Gate-Review am Phasenende (Ergebnis erreicht? + Notiz) |
|
||
| `change_entry` | Änderungs-Log pro Projekt (Scope-Creep) |
|
||
| `stakeholder` | Stakeholder-Analyse pro Projekt (Interesse × Einfluss, Haltung) |
|
||
| `risk` | Risiko- & Chancen-Register pro Projekt (W×A, Showstopper, Owner, Strategie) |
|
||
| `day_entry` | Tageseintrag (begun / full / absent), `published_at` für Vortrags-Veröffentlichung |
|
||
| `todo` | Tagesplan-Item, optional an Bezug gebunden, `carried_over`-Flag |
|
||
| `absence_day` | Urlaub/Krank/Sonstiges — zählt nicht gegen Streak |
|
||
| `blocker` | Bereichsübergreifendes Hindernis |
|
||
| `lead` | Vertriebs-Lead-Dossier; optionale `project_id`-Verknüpfung zum (universellen) Projekt |
|
||
| `lead_contact` | Kontaktperson(en) eines Leads |
|
||
| `lead_touchpoint` | Austausch-Log eines Leads (Kanal, Outcome, Follow-up) |
|
||
| `deal` | Pipeline-Deal; wird per Lead→Deal-Promotion erzeugt |
|
||
| `gtm_step`, `campaign`, `lead_handover` | Im Schema vorhanden (Phase-2-Vorbereitung), kein UI |
|
||
| `time_entry` | Zeiterfassungs-Block, optional an ToDo gebunden, Partial-Unique-Index erzwingt max. einen aktiven Timer pro User |
|
||
|
||
Migrationen liegen in `packages/db/migrations/*.sql` und werden über `drizzle-kit migrate` angewendet. Schema-Änderungen brauchen **immer eine neue Migration** — kein Schema-Drift.
|
||
|
||
---
|
||
|
||
## API-Surface
|
||
|
||
### REST-Routen (Fastify)
|
||
|
||
| Methode | Pfad | Zweck |
|
||
| ----------- | ----------------------- | ----------------------------------------------------------------------------------------------------------- |
|
||
| `GET` | `/api/health` | DB- und Authentik-Reachability-Check |
|
||
| `GET` | `/api/me` | Eingeloggter User (oder 401) |
|
||
| `GET` | `/api/auth/login` | Startet OIDC-Flow (Redirect zu Authentik) |
|
||
| `GET` | `/api/auth/callback` | Code-Exchange + Session-Create |
|
||
| `POST` | `/api/auth/logout` | Session löschen + Cookie clearen |
|
||
| `GET\|POST` | `/api/auth/dev-login` | **Nur Dev-Modus**: Auto-Login als Dejan |
|
||
| `POST` | `/trpc/<procedurePath>` | tRPC-Endpunkt (Batch + Stream) |
|
||
| `POST` | `/internal/cron/<job>` | Bearer-Token-geschützt: publish-day-entries, recompute-streaks, recompute-pflicht-minimum-5, pg-dump-backup |
|
||
|
||
### tRPC-Router (`apps/api/src/routers/_app.ts`)
|
||
|
||
```
|
||
health publicProcedure
|
||
user me, list, markAbsent
|
||
area list
|
||
project list, byAreaSlug, byId, create, update, archive,
|
||
setMilestoneStatus, addMilestone, updateMilestone, removeMilestone,
|
||
setModus, setPhase, addPhaseGate, phaseGates,
|
||
addStatus, statusHistory, listArchived
|
||
lead list, byId, forProject, create, update, addContact, removeContact,
|
||
addTouchpoint, removeTouchpoint, promoteToDeal, importCsv
|
||
risk forProject, create, update, remove
|
||
stakeholder forProject, create, update, remove
|
||
change forProject, create, update, remove
|
||
deal list, kpis, create, updateStage
|
||
gtm list, create, setStatus (kein UI, Phase 2)
|
||
campaign list, create, funnel, crosslink (kein UI, Phase 2)
|
||
blocker listOpen, byProject, create, resolve
|
||
dayEntry today, todayTeam, pendingCarryOver, checkIn, updateTodos,
|
||
editTodoTitle, addTodo, submitReflection, feed, todayPresence
|
||
time now, start, stop, listToday, update, upsertManual,
|
||
delete, teamToday
|
||
dashboard overview
|
||
```
|
||
|
||
Frontend importiert den Typ via `import type { AppRouter } from '@cockpit/api/router'` (siehe `apps/web/src/lib/trpc.ts`).
|
||
|
||
### Procedure-Typen
|
||
|
||
- `publicProcedure` — kein Auth-Check
|
||
- `protectedProcedure` — wirft `UNAUTHORIZED` wenn `ctx.user === null`
|
||
|
||
Hinweis: `dayEntryVisibleProcedure` und `projectOwnerProcedure` waren ursprünglich geplant (siehe Architektur-Spec), sind aber implizit in den Routern erfüllt (z. B. `blocker.resolve` prüft P1 manuell).
|
||
|
||
---
|
||
|
||
## Authentication
|
||
|
||
### Produktion: Authentik OIDC
|
||
|
||
Der Code ist vollständig vorhanden — was fehlt sind die Authentik-seitigen Daten:
|
||
|
||
1. Authentik-Admin legt Provider + Application an (siehe `docs/operations.md` § Authentik-Setup).
|
||
2. Client-ID/Secret/Issuer-URL fließen in `.env.production`:
|
||
```
|
||
AUTHENTIK_ISSUER=https://auth.tool-ctrl.de/application/o/everythingbutwork/
|
||
AUTHENTIK_CLIENT_ID=<von Authentik>
|
||
AUTHENTIK_CLIENT_SECRET=<von Authentik>
|
||
AUTHENTIK_REDIRECT_URI=https://everythingbutwork.tool-ctrl.de/api/auth/callback
|
||
```
|
||
3. Flow: `openid-client v6`, Authorization Code + PKCE, Discovery via `/.well-known/openid-configuration`. Code: `apps/api/src/auth/oidc.ts`.
|
||
|
||
**Bekannte offene Stelle (OQ-1)**: Der `groups`-Claim wird aus dem Token gelesen, aber **noch nicht** auf `user.default_area_id` gemappt. Heißt: neue User landen ohne Default-Bereich. Mapping kann später in `auth/user.ts:upsertUserFromOidc` ergänzt werden.
|
||
|
||
### Dev: Auto-Login
|
||
|
||
Im `NODE_ENV=development` ist `/api/auth/dev-login` aktiv. Die Frontend-Login-Route (`/login`) redirected dort automatisch hin, sodass man nie eine Login-Seite sieht. Logik:
|
||
|
||
- Sucht den User mit `email = 'dejan@tool-ctrl.de'`.
|
||
- Existiert er nicht, wird er via `upsertUserFromOidc` mit `sub='local-dejan'` angelegt.
|
||
- Session wird gesetzt, Redirect auf `/dashboard`.
|
||
|
||
In Production ist diese Route nicht registriert, weil die `if (env.NODE_ENV === 'development')`-Guard in `apps/api/src/auth/routes.ts` dann false ist.
|
||
|
||
### Session-Modell
|
||
|
||
- Postgres-Tabelle `session` (id, user_id, expires_at, ip_address, user_agent, created_at)
|
||
- Cookie: `cockpit_session`, HttpOnly + SameSite=Lax + (`Secure` in Prod)
|
||
- TTL: 24h (Env: `SESSION_TTL_HOURS`)
|
||
- Stop-condition: jedes Request läuft durch `auth/middleware.ts` → `getSessionWithUser(sessionId)` → setzt `req.cockpitUser`
|
||
|
||
---
|
||
|
||
## Frontend-Architektur
|
||
|
||
### Routing
|
||
|
||
React Router v7 (`createBrowserRouter`) — alle Routen sind **Lazy** (Code-Split per Route). Definition in `apps/web/src/lib/router.tsx`.
|
||
|
||
Login-Route ist außerhalb der `AppShell` (`/login`). Alle übrigen Routen unter `/` rendern die `AppShell` (TopNav + Main + Outlet + BlockerDialog).
|
||
|
||
### State
|
||
|
||
- **Server-State**: ausschließlich React Query via tRPC (`createTRPCReact<AppRouter>`). Konfig in `apps/web/src/lib/trpc-provider.tsx` (**staleTime 0** → Daten werden bei Mount/Tab-Fokus neu geholt, kein Polling; ein Retry; zentrales onError → Toast). Mutationen invalidieren zusätzlich gezielt die gleichzeitig sichtbaren Sichten.
|
||
- **Auth-State**: React Query `user.me` (Hook `useCurrentUser`).
|
||
- **Lokaler UI-State**: `useState` in der Komponente. Kein globaler Store.
|
||
|
||
### Brand & Voice
|
||
|
||
- **Logo**: ToolCTRL-Emblem (`apps/web/public/toolctrl-emblem.svg`) + Wordmark `everythingbutwork`.
|
||
- **Hausschrift**: Space Grotesk (selbst gehostet in `apps/web/public/fonts/`).
|
||
- **Buttons**: Keycap-Stil (Gradient + Druckeffekt + Tastenkante) — siehe `apps/web/src/styles/globals.css` § Keycap-Buttons.
|
||
- **Mikrocopy**: zentral in `apps/web/src/lib/voice.ts`. Tonalität locker/direkt/deutsch (mit zwei englischen Lehnwörtern: „Lock in" und „Locked in" statt „Einstempeln/Eingestempelt").
|
||
- **Ampel-Farben**: `green=#15803d/#dcfce7`, `amber=#a16207/#fef3c7`, `red=#b91c1c/#fee2e2` (UX-Spec § Visual Design Foundation).
|
||
|
||
### Keyboard-Shortcuts
|
||
|
||
In `apps/web/src/components/layout/AppShell.tsx`:
|
||
|
||
```
|
||
g d Dashboard
|
||
g b Bereich (Default Entwicklung)
|
||
g e Entwicklung
|
||
g v Vertrieb
|
||
g m Marketing
|
||
g h Heute
|
||
g a Archiv
|
||
n Neuer Blocker
|
||
? Hilfe-Cheatsheet
|
||
```
|
||
|
||
---
|
||
|
||
## Geteilte Logik (`packages/shared`)
|
||
|
||
Zod-Schemas + zugehörige TS-Types werden über `@cockpit/shared` re-exportiert. Beispiele:
|
||
|
||
```ts
|
||
import { DayEntrySchemas, TimeEntrySchemas, ProjectSchemas } from '@cockpit/shared';
|
||
|
||
// Im Backend (tRPC):
|
||
checkIn: protectedProcedure.input(DayEntrySchemas.checkInInput).mutation(...)
|
||
|
||
// Im Frontend (React Hook Form):
|
||
const resolver = zodResolver(TimeEntrySchemas.manualTimeEntryInput);
|
||
```
|
||
|
||
Konstanten (`MAX_TODO_TEXT_LENGTH`, `WERKTAGE`, `JOURFIXE_DAY`, `REFLEXION_REMINDER_HOUR`) in `packages/shared/src/constants.ts`.
|
||
|
||
---
|
||
|
||
## Produktion / Deployment
|
||
|
||
### Docker Compose
|
||
|
||
`docker-compose.yml` definiert vier Services:
|
||
|
||
| Service | Image | Rolle |
|
||
| ---------- | ------------------------------- | ----------------------------------------------------------------------------- |
|
||
| `postgres` | `postgres:16-alpine` | DB, persistentes Volume `cockpit_postgres_data`, Backups in `cockpit_backups` |
|
||
| `api` | Build aus `apps/api/Dockerfile` | Fastify auf 3000, hängt an Postgres-Health |
|
||
| `web` | Build aus `apps/web/Dockerfile` | Vite-Build wird in `cockpit_web_dist`-Volume gelegt |
|
||
| `caddy` | `caddy:2-alpine` | Reverse-Proxy (80+443), TLS via Let's Encrypt, Caddyfile mountet |
|
||
|
||
Env-Variablen werden über `.env.production` injiziert. Required: `POSTGRES_PASSWORD`, `SESSION_COOKIE_SECRET` (mind. 32 Z.), `CRON_BEARER_TOKEN`, `AUTHENTIK_*`.
|
||
|
||
### Caddyfile
|
||
|
||
- `cockpit.tool-ctrl.de` als Host (per `${COCKPIT_HOST}` overridbar) — wird beim nächsten Deploy auf `everythingbutwork.tool-ctrl.de` gesetzt
|
||
- `/internal/*` wird **extern geblockt** (403) — Cron-Endpunkte nur internes Netz
|
||
- `/api/*` und `/trpc/*` reverse-proxy zu `api:3000`
|
||
- Statischer Frontend-Content aus `/srv`
|
||
- Standard-Security-Header (HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)
|
||
|
||
### Cron im Postgres-Container
|
||
|
||
`scripts/backup.sh` läuft als System-Cron im Postgres-Container: tägliches `pg_dump` mit 14-Tage-Retention + Heartbeat an `/internal/cron/pg-dump-backup` für Audit.
|
||
|
||
### CI & Auto-Deploy
|
||
|
||
`.gitea/workflows/ci.yml`:
|
||
|
||
```
|
||
quality → install, typecheck, lint, format:check, test
|
||
build → install, web build, api build (needs quality)
|
||
```
|
||
|
||
`.gitea/workflows/deploy.yml` (Runner `runs-on: host` auf dem Homeserver): **bei jedem Push auf `main`** → `git reset --hard origin/main`, `docker compose up -d --build`, dann Warten bis `cockpit-api` healthy. Es gibt **keinen manuellen Trigger** — ein (ggf. leerer) Push löst den Deploy aus.
|
||
|
||
**Startup-Verhalten der API** (`apps/api/src/server.ts`): vor dem Listen wird
|
||
|
||
1. `runMigrations()` ausgeführt (Drizzle-Migrationen idempotent; schlägt eine fehl, startet die API nicht), dann
|
||
2. `ensureBaseAreas()` (legt die 4 Basis-Bereiche idempotent an, damit z. B. der „Neues Projekt"-Dropdown nie leer ist — auch auf einer nicht geseedeten Prod-DB).
|
||
|
||
---
|
||
|
||
## Konventionen — Pflicht-Lesen vor Code-Edits
|
||
|
||
**`CONVENTIONS.md`** im Repo-Root. Zusammengefasst die wichtigsten Punkte:
|
||
|
||
- **DB**: Singular `snake_case` Tabellen, `id` als UUID, FK `<entity>_id`, Enums als `text` + check.
|
||
- **tRPC**: Procedure-Naming `<feature>.<verbNoun>` (camelCase). Inputs **immer** Zod aus `@cockpit/shared`, niemals inline.
|
||
- **Errors**: `TRPCError` mit **deutschen** menschenlesbaren Messages. Frontend zeigt sie via zentralem onError-Toast.
|
||
- **State**: Server-State ausschließlich React Query — niemals `useState` für Server-Loading.
|
||
- **Werktage/Zeit**: Alle Datumslogik im Backend (`apps/api/src/lib/werktag.ts`). Server-Zeit `Europe/Berlin`.
|
||
- **Sichtbarkeit**: Heutige ToDos und Zeit sind **teamweit sichtbar** (Änderung gegenüber Original-PRD). Vortags-Reflexionen werden via Cron publiziert.
|
||
- **No-Gos**: kein Kanban, kein Steckbrief-Editor, kein Plane-Issue-Spiegel — die Wahrheit dieser Dinge lebt in Plane/Outline.
|
||
|
||
---
|
||
|
||
## Was bewusst NICHT gebaut ist (Phase 2)
|
||
|
||
- **Plane-/Outline-API-Sync** — Cockpit verlinkt nur, hängt sich nichts ab
|
||
- **Kanban-Pipeline / Deal-Funnel-UI** — `gtm`/`campaign` ohne UI (Lead-CRM ist gebaut)
|
||
- **Reportings / Wochen-/Monatsauswertungen** — bewusst minimal
|
||
- **Reaktionen / Kommentare** auf Tagesupdates
|
||
- **Mobile schreibbar** — Desktop-first, mobil nur lesbar
|
||
- **Volltextsuche** über Updates
|
||
- **Kimai-Sync** — Kimai läuft parallel ohne Bezug (eigener Pfad)
|
||
- **Loki/Grafana, Sentry** — pino → stdout reicht erstmal
|
||
- **Authentik-Group → Bereich-Mapping** (OQ-1, siehe Auth-Abschnitt)
|
||
|
||
---
|
||
|
||
## Planungs-Artefakte
|
||
|
||
Vollständige PRD/Architektur/UX/Stories im `_bmad-output/planning-artifacts/`-Ordner. Diese Artefakte sind teilweise historisch (z. B. Kanban war ursprünglich geplant, ist aber inzwischen aus dem UI raus) — der Code ist die aktuelle Quelle der Wahrheit. Die Artefakte sind nützlich für:
|
||
|
||
- Hintergrund zur Methodik (ToolCTRL-Leitfaden v3.0)
|
||
- Entscheidungs-Log (`prds/prd-projectmanagement-2026-05-25/.decision-log.md`)
|
||
- Ursprüngliche User Journeys
|
||
- Anti-Ziele und Roadmap-Hinweise
|
||
|
||
| Datei | Inhalt |
|
||
| ---------------------------------------------- | --------------------------------------- |
|
||
| `prds/prd-projectmanagement-2026-05-25/prd.md` | PRD (49 FRs, 16 NFRs) |
|
||
| `architecture.md` | Stack-Entscheidungen, Patterns |
|
||
| `ux-design-specification.md` | Brand-Tokens, Komponenten, Voice & Tone |
|
||
| `epics.md` | 6 Epics, 32 Stories |
|
||
|
||
---
|
||
|
||
## Repo & Remote
|
||
|
||
- **Default-Branch**: `main`
|
||
- **Remote**: `ssh://git@git.hg-int.dev:2222/toolctrl-labs/everythingbutwork.git` (Gitea, non-standard SSH-Port)
|
||
- **Gitignore**: `.env`, `node_modules`, `dist`, `_bmad/`-Tools (Bmad-Framework), `.claude/`, `ToolCTRL Brand Identity/` (215 MB Raw-Assets)
|
||
|
||
---
|
||
|
||
## Quick-Wins fürs nächste Modell / die nächste Session
|
||
|
||
1. **Sanity-Check vor jedem Edit**: `git status`, dann lies `CONVENTIONS.md`.
|
||
2. **Schema ändern?** → neue Drizzle-Migration generieren (`pnpm --filter @cockpit/db migrate:generate --name=<...>`) und applien. NIEMALS direkt am DB-Snapshot rumdoktern.
|
||
3. **Neuer tRPC-Endpoint?** → Zod-Schema in `packages/shared/src/schemas/<feature>.ts`, dann Procedure im passenden Router. Frontend importiert den Typ automatisch über `AppRouter`.
|
||
4. **Englisch-Verbot in UI-Strings** — Ausnahmen: „Lock in", „Locked in" (vom Owner so gewollt), Fachbegriffe wie „Kanban" (im Leitfaden gesetzt).
|
||
5. **Postgres-Port ist 55432, nicht 5432**. Ports sind aus Kollisionsgründen so gewählt: API 7100, Web 7173, DB 55432.
|
||
6. **Dev-User**: `dejan@tool-ctrl.de` (Admin). Wird im Auto-Login angelegt falls nicht vorhanden.
|
||
7. **Beim Bauen neuer Features**: schau wie ähnliche Features umgesetzt sind (z. B. Time-Tracking als Vorbild für strukturähnliche Domain-Slices: Schema → Shared-Zod → Router → Frontend-Komponente).
|