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>
Alle Bausteine waren da (Ampel/Trend, ToDo-Status, Anwesenheit), aber niemand
sah je seinen eigenen Verlauf. Neue Query dayEntry.myWeek + Route /meine-woche
(pro Tag Ampel, Antritt→Feierabend, ToDo-Zähler). Nav-Tab „Woche" + Shortcut g w.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Alt-Bestand nie formatiert — isolierter Format-Lauf vor der myWeek-Query und
dem Woche-Nav-Tab.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Keine Sprung-zu-Suche bei wachsender Projekt-/Lead-Zahl. Neuer search-Router
(ILIKE ueber Projekte, Leads, offene Blocker) + Command-Palette ueber die
Modal-Basis, getriggert per Cmd/Ctrl+K oder „/". Klick springt zum Ziel.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
naechsterSchrittDatum wurde erfasst, aber nie als faellige Liste geb√ºndelt —
warme Leads gingen unter. Neue Query lead.dueFollowUps (aktive Leads mit
faelligem/ueberfaelligem naechsten Schritt) + "Faellige Wiedervorlagen"-Block
oben auf /leads.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
time_entry erfasst bezugKind='project'+bezugId, aber niemand sah je, wie viel
Zeit ein Projekt frisst. Neue Query time.byProject (Gesamtsumme + pro Person);
neue "Getrackte Zeit"-Sektion im Projekt-Detail (blendet sich aus, wenn nichts
getrackt wurde).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
withAudit war als tRPC-Wrapper definiert, aber an keine Procedure gehaengt
(audit_event blieb leer). Jetzt eine echte tRPC-v11-Middleware-Procedure
(auditedProcedure), die nach erfolgreicher Mutation best-effort einen
audit_event schreibt (Fehler beim Audit kippen die Mutation nicht).
Verdrahtet an die folgenreichen Zustandsuebergaenge:
- blocker.resolve
- project.archive, project.setPhase, project.addPhaseGate
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Nachtarbeit über Mitternacht brach bisher den Check-Out ("Du bist heute
noch nicht locked in") und sperrte nach versehentlichem Feierabend den Tag.
- Tag = Kalendertag des Check-ins; eine über Mitternacht laufende Session
zaehlt zum Starttag, bis 06:00 des Folgetags (isWorkdaySealed)
- checkOut/today finden den offenen Eintrag statt stur "heute"
- reopen ("Arbeit fortsetzen") hebt einen Feierabend wieder auf
- applyWorkdayBoundary: nahtloser 6-Uhr-Schnitt (Auto-Checkout + Folge-Tag,
offene ToDos wandern mit)
- Reflexion/ToDo-Edit folgen der 6-Uhr-Grenze statt Mitternacht
- Cron apply-workday-boundaries fuer Team-Dashboard-Konsistenz
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deadline lässt sich jetzt direkt im Projekt-Header bearbeiten (Inline-
Datepicker mit Freitag/Wochenende-Warnung), via project.update. Der
Arbeitsmodus (Kanban/Cycle) ist in ebw bedeutungslos und fliegt aus der
Projekterstellung und der Header-Zeile raus. DB-Spalte bleibt ungenutzt
bestehen (keine destruktive Migration nötig).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Logo erscheint jetzt zusätzlich bei "Anstehende Meilensteine" (neben dem
Projekt-Link) und bei "Pflicht-Minimum offen" (vor dem Namen). Dashboard-
Queries liefern dafür project.logoUrl mit.
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>
Im Dashboard-Feed wird je User nur noch die aktuellste Reflexion als
eine Card gezeigt (Dedup über userId, feedRows ist bereits nach Datum
absteigend sortiert). Ältere Einträge sieht man weiterhin unter
"Ganze Historie". Angezeigter Text ist der Freitext "Sonst noch was?"
(reflectionText).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Projektübergreifende Liste offener/überfälliger Meilensteine mit Datum,
sortiert nach Fälligkeit. Überfällige rot markiert, relative Tagesangabe
("in 3 Tagen" / "überfällig"), direkt verlinkt aufs Projekt. Sitzt oben
zwischen Showstoppern und kritischen Modi.
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>
Pause-Feature entfernt: kein Pause-Button, kein break-Buchen mehr. Man bucht
nur Tasks; stoppt man eine Task und es läuft keine andere, ist die Lücke
automatisch eine Pause — im Kalender/Tagesansicht schlicht leer. Tasks bleiben
pausierbar (Stop = Pause, erneut Start = Fortsetzen → mehrere Segmente auf
dieselbe Task, Lücke dazwischen leer). Wall-Clock zählt Lücken ohnehin nicht.
- time.pause-Endpoint entfernt; 'break' aus der angebotenen Kategorienliste
raus (bleibt im Enum für Altdaten).
- TimerPill: Pause-Button + Pause-Panel-Grund entfernt; Stop/Start je Task bleibt
(= pausieren/fortsetzen). Heute-ToDo-Zeile entsprechend beschriftet.
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>
Neue Seite /team (Tab „Team", g t): horizontale Tages-Timeline mit einer Spur
pro Person. Zeit-Blöcke werden nach Start/Ende auf einer Stundenachse
positioniert, eingefärbt nach Kategorie; überlappende Blöcke landen auf
parallelen Sub-Spuren ("parallele Zeitstränge"). Laufende Blöcke (heute) ticken
live und sind rot umrandet. Datums-Navigation (Tag vor/zurück, Picker, „Heute"),
Hover-Tooltip mit Label/Zeitraum/Dauer, Kategorie-Legende.
Endpoint time.teamTimeline({date?}) liefert pro Person die Roh-Einträge
(Start/Ende/Kategorie/Label); Achse & Sub-Spuren rechnet das Frontend.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Neue Seite /historie: pro vergangenem Tag sieht man, was jede/r gemacht hat —
Reflexion (Ampel/Trend/Text) + ToDos (erledigt/offen/verworfen), plus Liste
„ohne Eintrag". Datums-Navigation (vorheriger/nächster Werktag, Datum-Picker);
Default = vorheriger Werktag. Heutige Reflexionen bleiben privat (nur ToDos).
- Endpoint dayEntry.historyForDate({date?}) liefert Einträge + prev/next-Werktag.
- TopNav-Tab „Historie" (g i), Link „Ganze Historie →" im Dashboard-Feed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Der Vortags-Feed verlangte published_at IS NOT NULL, gesetzt nur vom Cron
publish-day-entries. Auf Prod ist dieser Cron nicht eingeplant (keine crontab/
kein Scheduler) → published_at blieb bei allen echten Reflexionen null → Feed
immer leer (auf Dev sah's gut aus, weil der Seed published_at setzt).
Feed jetzt vom Cron entkoppelt: zeigt volle Reflexionen mit Datum < heute
(heutige bleiben privat bis morgen), sortiert nach Datum/Reflexionszeit.
Gilt für dashboard.overview.feed und dayEntry.feed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ein um 23:00 gestarteter, noch laufender Timer hatte Start-Datum = gestern und
fiel nach Mitternacht aus listToday/teamToday (Filter nach Start-Datum=heute) —
nur die TimerPill zeigte ihn noch.
- listToday/teamToday: Filter „overlapsToday" = heute gestartet ODER heute
beendet (Mitternacht überschritten) ODER laufend → Eintrag bleibt sichtbar.
- Tages-Summen clampen den Anteil auf den heutigen Tag (berlinDayStartMs,
DST-korrekt) → über Mitternacht gelaufene Blöcke werden nicht falsch zugezählt.
- ToDo-Titel werden tagesübergreifend serverseitig aufgelöst (time.now +
listToday) → Task bleibt benannt, auch wenn das ToDo von gestern ist.
- Timeline zeigt das Start-Datum, wenn ein Eintrag nicht heute begonnen hat.
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>
Nach dem Lock-in konnte man ToDos nur bearbeiten/abhaken, aber keine neuen
anlegen. Neuer Endpoint dayEntry.addTodo hängt ein einzelnes ToDo an den
heutigen Tageseintrag an (erlaubt solange der Eintrag vom heutigen
Berlin-Tag ist, auch nach der Reflexion). Inline-Feld „Neues ToDo
hinzufügen" in der EinstempeltView; aktualisiert Heute + Dashboard-Sichten.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Feiertage: isWerktag berücksichtigt jetzt die 9 bundesweiten gesetzlichen
Feiertage (Oster-Computus + feste Tage) → ein Feiertag bricht die Streak
nicht mehr (zählt wie ein Wochenende neutral). Landesspezifische Feiertage
bewusst (noch) nicht enthalten.
- Wochenend-Edge: Reflexion an Sa/So setzte die angezeigte Streak auf 0
(Schleife brach am Nicht-Werktag ab). Jetzt wird übersprungen statt gebrochen.
- Lookback von 120 auf 400 Tage erhöht, damit lange Streaks nicht
abgeschnitten werden.
- Streak-Zähllogik in reine, getestete countStreakDays() ausgelagert
(8 Streak- + 5 Feiertags-Tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
"Zeit im Team heute" (und "Zeit heute") summierten alle Task-Dauern —
wer 5 Tasks parallel trackt, hatte 10h statt 2h Anwesenheit. Gesamt wird
jetzt als Vereinigung überlappender Intervalle berechnet (unionSeconds).
Pro-Aufgabe/Kategorie bleibt separat als Summe erhalten und wird als
"getrackt X" angezeigt; der Verteilungsbalken nutzt diese Summe als Nenner.
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>
"CSV importieren"-Dialog auf /leads. Serverseitiger lead.importCsv:
eigener CSV-Parser (Quotes/Kommata/Zeilenumbrüche, mit Tests),
header-basiertes Mapping (umlaut-tolerant). Pro Zeile entsteht ein Lead
(Adresse → standorte) + primärer Kontakt; Notizen werden als erster
Austausch-Log-Eintrag gespeichert. Dedup über Firmenname.
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>
ToDos in der Reflexion lassen sich per Klick auf den Titel korrigieren
(z. B. Tippfehler), Enter speichert / Escape bricht ab. Neuer Endpoint
dayEntry.editTodoTitle — nur eigene ToDos vom heutigen Berlin-Tag,
ändert den Titel in-place (Status/Reihenfolge/IDs bleiben).
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>