Der 6-Uhr-Schnitt legt Folgetage mit checkedInAt=06:00 an — das ist kein
echter Lock-in, die Anwesenheitsanzeige lief dadurch fälschlich seit
06:00 mit (z. B. "6h 6m" mittags trotz spätem Arbeitsbeginn). Neues Flag
day_entry.auto_checked_in (Backfill: bestehende 06:00:00-Check-ins);
teamToday, Heute-Balken und "Zeit heute" zeigen Anwesenheit nur noch bei
echtem Check-in.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Behebt die rote CI (format:check schlug an 36 Alt-Bestand-Dateien fehl:
migrations/meta-Snapshots, seed.ts, schemas/lead.ts, README u. a.) und erzwingt
nebenbei einen frischen web-Build beim Auto-Deploy (apps/web-Dateien geaendert
-> COPY-Layer invalidiert -> Vite baut neu).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Der Abschluss verlangte eine URL + freie Vermerk-Textarea, half aber nicht beim
Strukturieren der Retro. Jetzt gefuehrte Felder (Ziel erreicht? / Aufwand vs.
Plan / was lief gut / was nicht), gespeichert als JSONB project.lessons_learned
(Migration 0018). Anzeige im Projekt-Detail als "Retrospektive"; Altprojekte
fallen auf den freien Abschlussvermerk zurueck.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bisher reine uuid-Spalten ohne referenzielle Integritaet -> Orphan-Risiko, wenn
das Ziel geloescht wird. Jetzt echte FKs mit ON DELETE SET NULL:
- day_entry.blocker_id -> blocker
- blocker.area_id -> area
- blocker.project_id -> project
Migration bereinigt vorab verwaiste IDs (UPDATE ... SET NULL), damit ADD
CONSTRAINT auf Altdaten nicht bricht. Verifiziert: alle 18 Migrationen laufen
gegen frische Postgres 16 durch, FKs angelegt.
(todo.bezug_id / time_entry.bezug_id bleiben FK-frei: echt polymorph ueber
bezug_kind project/deal/campaign — kein einzelner FK moeglich.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Logo kann bei der Projekterstellung und nachträglich im Projekt-Header
hochgeladen werden. Client-seitiger Center-Crop auf 256px WebP, gespeichert
als Data-URL in neuer Spalte project.logo_url (Migration 0016). Anzeige in
der Bereichs-Projektliste (Card) und im Detail-Header, mit Initialen-
Platzhalter wenn kein Logo gesetzt ist.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
User-sichtbare Hinweise (Meilenstein-Mindestanzahl, Stakeholder-Empty,
Freitags-Warnung) ohne Quellenangabe umformuliert; Dev-Kommentare von
"Buch Kap. X" auf "Leitfaden" geändert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Daten-Migration entfernt alle alten 'break'-Zeiteinträge (das Pause-Feature
wurde abgeschafft). Sie erschienen sonst als graue Blöcke in der Team-Timeline.
Läuft beim Deploy automatisch über runMigrations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Arbeitsantritt = bestehender Lock-in (setzt checkedInAt); neu dazu:
- Check-Out / Feierabend: dayEntry.checkedOutAt + Endpoint dayEntry.checkOut;
Heute-Seite zeigt „Arbeitsantritt HH:MM" und einen „Feierabend / Check-Out"-
Button, danach Antritt/Feierabend/Dauer.
- Kimai-Anbindung (config-gated, best-effort, nicht blockierend): lib/kimai.ts
mit clockIn/clockOut. Check-In startet ein Kimai-Timesheet, Check-Out stoppt
es; ID + Sync-Status auf dem dayEntry (kimaiTimesheetId, kimaiSynced).
No-Op solange KIMAI_BASE_URL/API_TOKEN/PROJECT_ID/ACTIVITY_ID nicht gesetzt.
Migration 0014 (checkedOutAt, kimaiTimesheetId, kimaiSynced). Env-Vars als
optional ergänzt + in .env.example dokumentiert.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mehrere Tasks gleichzeitig tracken. Unique-Index "ein aktiver Timer pro User"
entfernt (Migration 0013); time.start stoppt den laufenden nicht mehr, sondern
läuft parallel.
- time.now liefert jetzt ALLE laufenden Timer (Array) inkl. ToDo-Titel.
- time.stop({id}) stoppt einen bestimmten Timer; time.stopAll stoppt alle.
- time.pause stoppt ALLE laufenden Tasks und startet eine Pause (passt zum
Pause-Ticket „alle laufenden Tasks pausiert").
- TimerPill: zeigt Anzahl laufender Timer; Panel listet alle mit Live-Zähler +
einzelnem Stop, „Weiteren Task starten", Grund-Nachtrag für Pausen.
- Heute-ToDo-Zeile: Start/Stop pro ToDo unabhängig (Array-basiert).
Zusammenspiel: Team-Timeline zeigt parallele Stränge, „Gesamt"-Zeit zählt
Überlappung weiterhin nur einmal (Wall-Clock).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drive-by-Aufgaben ("btw, Yevgen-Security-Fix hab ich auch noch gemacht")
strukturiert erfassen statt nur im Freitext. Neues Feld todo.unplanned +
Endpoint dayEntry.addUnplanned: legt sofort ein erledigtes, als „ungeplant"
markiertes ToDo an.
- Schnell-Eingabe „Ungeplant erledigt" auf Heute (Mid-Day) und in der
Reflexion (End-of-Day-Recall).
- "ungeplant"-Badge in Heute, Reflexion und Historie.
- historyForDate liefert das unplanned-Flag mit.
- Reflexions-Freitext bleibt für die Tages-Story.
Migration 0012 (todo.unplanned).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Backend:
- Vortags-Feed (dashboard.overview + dayEntry.feed) sortierte aufsteigend
mit limit(50) → zeigte die ÄLTESTEN statt neuesten Einträge. Jetzt desc.
- deal.updateStage: wonValueEur wurde beim Gewinnen mit der Wahrscheinlichkeit
gewichtet → zu niedriger Won-Umsatz. Jetzt voller Auftragswert.
- Pflicht-Minimum „Status aktuell" wurde global (letzte Reflexion irgendwo)
statt pro Projekt berechnet. Jetzt je Projekt aus dem jüngsten Statusbericht.
- time.listToday/teamToday nutzten fixen +02:00-Offset → im Winter (CET) um
1h verschobene Tagesgrenze. Jetzt DST-sicher via AT TIME ZONE 'Europe/Berlin'.
Frontend:
- lead.tsx + projekt.tsx: Datumsanzeigen nutzten UTC (toISOString) → abends
Off-by-one. Jetzt Berlin-lokal formatiert.
- reflexion.tsx: Submit mappte nur den initialen ToDo-Snapshot → mitten am Tag
ergänzte ToDos verloren ihren Status. Jetzt über die aktuelle Liste.
- leads.tsx: CSV-Zeilenzähler konnte „-1" zeigen (Math.max).
- TimerPill: Label ohne Control (a11y-Lint-Error, war repo-weit rot) + unused
clsx-Import behoben → pnpm lint jetzt 0 Errors.
DB: seed.ts unbedingten updatedAt-Bump aller User entfernt (Debug-Rest).
README: vollständig auf aktuellen Stand gebracht (Leads/CRM, CSV-Import,
PM-Tiefe, Phasen/Status/Risiken/Stakeholder/Änderungen, Tabellen bis 0011,
tRPC-Router, staleTime 0, Auto-Deploy-Runner + Startup-Migrationen).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ein Projekt lebt in genau einem Heimat-Bereich und wird einmal angelegt;
Leads bekommen eine optionale Verknüpfung dorthin (lead.projectId), egal in
welchem Bereich das Projekt wohnt — kein redundantes Anlegen mehr.
- Migration 0011: lead.project_id (FK → project, ON DELETE SET NULL).
- Lead anlegen / CSV-Import / Lead-Detail: Projekt zuordnen; CSV ordnet alle
importierten Leads einem Projekt zu.
- Projektseite: neue "Leads"-Sektion (Vertriebs-Sicht des Projekts).
- Vertrieb: "Projekte mit Vertriebsaktivität" — bereichsübergreifend alle
Projekte mit verknüpften Leads (so erscheint ein Entwicklungsprojekt hier,
WEIL es Leads hat). Rein interne Projekte ohne Leads tauchen nicht auf.
- Lead-Liste/Detail zeigen das verknüpfte Projekt.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zwei Ursachen behoben:
- Frontend: areaId/Default wurden nur beim Mount aus dem (noch ladenden)
areas-Prop berechnet → kein Default, keine Platzhalter-Option. Jetzt via
useEffect Default setzen sobald Areas geladen sind + Platzhalter-Option.
- Prod: Deploy seedet nicht → frische DB hatte keine Areas. Neue
ensureBaseAreas() legt die Basis-Bereiche idempotent beim API-Start an
(nach den Migrationen), analog zu runMigrations.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- .gitea/workflows/deploy.yml: deployt bei Push auf main via Host-Runner
(git reset --hard origin/main in /opt/build/cockpit, docker compose up
-d --build in /opt/stacks/cockpit, Healthcheck auf cockpit-api).
- Auto-Migrate: runMigrations() (drizzle-orm Migrator) wird beim API-Start
aufgerufen; Schema wird vor dem Listen idempotent aktualisiert.
- API-Dockerfile: Workspace-Libs (@cockpit/db, @cockpit/shared) werden jetzt
mit Emit kompiliert und main/exports im Image auf dist gebogen. Vorher war
das Prod-Image nicht reproduzierbar baubar (Node konnte die TS-Quelle der
Libs nicht laden → ERR_MODULE_NOT_FOUND). Dev-Modus und Web-/Vite-Build
bleiben unberührt.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Neues Lead-Modul im Vertriebsbereich: qualitative Dossiers (Stammdaten,
Pain & Bedarf, Soft Factors, Strategie), Kontaktpersonen, Austausch-Log
mit Follow-up-Daten und Lead→Deal-Promotion. Routen /leads (Liste, nach
Status gruppiert) und /lead/:id (Dossier), Teaser im Bereich Vertrieb.
Migration 0008_lead_crm (lead, lead_contact, lead_touchpoint).
Datenmodell-Tabellen liegen in der gemeinsamen schema.ts (Folge-Commit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Zeiterfassung
- Neue Tabelle time_entry mit started_at/ended_at/duration_seconds,
optional todo_id/bezug, category-Enum (focus/meeting/break/admin/learning/other),
description, day_entry_id-Verknüpfung. Partial-Unique-Index erzwingt
max. einen aktiven Timer pro User.
- tRPC time-Router: now, start (stoppt aktiven automatisch), stop,
listToday (Liste + Tagessumme), update, upsertManual (retroaktiv),
delete, teamToday (personenübergreifend mit byCategory + aktivem
Timer pro User).
- TopNav: persistente TimerPill links neben Streak. Quick-Picker mit
heutigen ToDos, Kategorie-Auswahl und Freitext-Start. Live-Sekundenzähler.
- /heute: TimelineSection mit Edit/Delete pro Eintrag, ToDo-Bindung
nachträglich änderbar, Block-nachtragen-Dialog. Pro ToDo-Zeile
▶-Start- und 🕐-Nachtrag-Button.
- /puls: TeamTimeSection mit aktiver Timer-Anzeige + Tagessumme +
Kategorie-Breakdown pro User (refresh alle 30s, Live-Tick jede Sekunde).
Visual
- Space Grotesk als Hausschrift (selbst-gehostet, latin + latin-ext)
- Keycap-Button-Stil (Gradient + Druckeffekt) für primary/secondary/destructive
- Neue Helper-Module: format-duration, time-categories
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>