Files
planer/devlog/DEVLOG.md
Florian WildeandClaude Opus 5 7d1f91e740 docs: DEVLOG-Eintrag für M2
Ziel, Erledigtes, die Entwurfsentscheidungen mit Begründung, die
Probleme samt Lösung und die offenen Punkte. Dazu die eigenen Fehler
dieser Sitzung.

Dauer laut zeiten.csv: 44 Minuten. Gesamt bislang 205 Minuten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:46:14 +02:00

229 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Entwicklungstagebuch
Neuester Eintrag oben. Die Dauer je Sitzung stammt aus `devlog/zeiten.csv`,
die Übersicht aus `scripts/zeit.sh summe`.
## Zeit je Meilenstein
| Meilenstein | Art | Minuten | Stunden |
| ----------- | ------ | ------- | -------- |
| M1 | claude | 161 | 2,68 |
| M2 | claude | 44 | 0,73 |
| **Gesamt** | | **205** | **3,42** |
---
## 2026-09-10 – M2: Aufgaben und Projekte
**Dauer:** 44 Minuten (15:57–16:41), Art `claude`.
**Ziel der Sitzung:** Erfassen und Wiederfinden — Aufgaben, Inbox, Ideen und
Projekte. Noch ohne Kalender.
### Erledigt
- Datenmodell `Aufgabe` und `Projekt` samt Migration
- Schnellerfassung, Inbox mit Einsortieren, Aufgabenliste mit Filtern
- Ideen als eigene Ansicht, mit Übernahme in eine Aufgabe
- Projekte mit Arbeitspaketen, Meilensteinen und Fortschritt
- Startseite zeigt jetzt, was überfällig oder demnächst fällig ist
- 53 neue Unit-Tests, 11 neue Integrationsprüfungen, 4 neue E2E-Tests
- Handbuchseiten zu Aufgaben, Ideen und Projekten; Bilder neu aufgenommen
### Entscheidungen mit Begründung
**Die Inbox ist kein eigener Zustand.** Eine Aufgabe liegt genau dann in der
Inbox, wenn sie keinen Bereich hat. Ein zusätzliches Feld „einsortiert"
hätte mit der Zuordnung auseinanderlaufen können; so ist der Zustand nicht
darstellbar.
**Projekt als eigenes Modell, nicht als Aufgabentyp.** Ein Projekt hat
selbst weder Aufwand noch Fälligkeit — beides ergibt sich aus dem Inhalt.
`ARBEITSPAKET` und `MEILENSTEIN` bleiben Typen der Aufgaben _innerhalb_
eines Projekts.
**Verworfene Aufgaben zählen gar nicht in den Fortschritt.** Weder als
erledigt noch als offen. Sonst sähe ein Projekt fertig aus, weil die Hälfte
gestrichen wurde — eine Zahl, die lügt, ist schlechter als keine.
**Sortiert wird in der Anwendung, nicht in SQL.** Die Reihenfolge ist
mehrstufig (offen vor abgeschlossen, dann Termin, dann Priorität, dann Titel
mit deutscher Sortierung). Als geprüfte reine Funktion steht sie an einer
Stelle; eine zweite Fassung in SQL wäre eine Fehlerquelle. Bei den erwarteten
Mengen ist das unkritisch — sollte es einmal eng werden, ist der Ort für die
Änderung klar.
**Filter stehen in der Adresse.** Eine gefilterte Liste lässt sich als
Lesezeichen ablegen, und der Zurück-Knopf tut, was er soll.
**Statuswechsel folgen festen Übergängen.** Aus `ERLEDIGT` und `VERWORFEN`
geht es nur zurück nach `OFFEN`. Wer per Formular etwas anderes einreicht,
bekommt eine Meldung — nicht einen stillen Zustandssprung.
**Fälligkeit in Kalendertagen, nicht in Stunden.** Was heute um 23:00 fällig
ist, ist heute fällig. Alles andere fühlt sich beim Benutzen falsch an.
**Natives `<select>` statt einer eigenen Auswahlkomponente.** Es bedient
sich auf dem Handy mit dem Systemdialog, ist ohne Zusatzcode
tastaturbedienbar und braucht kein JavaScript zum Absenden.
### Probleme und Lösungen
| Problem | Lösung |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ESLint beanstandete `String(antwort.meldung)` | Ich hatte die Rückmeldung als `Record<string, unknown>` typisiert, statt den vorhandenen `Rueckmeldung`-Typ zu nehmen. Mit dem richtigen Typ entfällt die Umwandlung ganz. |
| Zwei Stellen rechneten die Tagesgrenze mit einem `toLocaleString`-Trick | `src/lib/zeit.ts` konnte das längst sauber. Beide Stellen nutzen jetzt `tagesBeginn()` beziehungsweise `alsTagesschluessel()`; die Tests belegen gleiche Ergebnisse bei weniger Code. |
| Typfehler nach der Migration | Der Prisma-Client muss nach jeder Schemaänderung neu erzeugt werden. `npm run pruefen` tut das inzwischen selbst, wenn er fehlt — hier war er nur veraltet. |
### Eigene Fehler, die ich korrigiert habe
- Im Demo-Seed stand versehentlich ein kyrillisches Wort als Titel eines
Meilensteins. Ersetzt durch „Seite ist online".
- Ein Test erwartete, dass 481 Minuten Aufwand erlaubt sind; die Grenze
liegt bei 480. Der Test war falsch, nicht der Code — jetzt prüft er die
Grenze selbst und den ersten Wert darüber.
### Offene Punkte
- Kein Wiederholungsrhythmus für Aufgaben und keine Verknüpfung von Aufgaben
untereinander. Beides steht bewusst unter „Ideen für später" und wartet
darauf, ob es im Alltag wirklich fehlt.
- Die Aufgabenliste lädt bis zu 500 Einträge auf einmal. Für den erwarteten
Bestand reicht das; eine Blätterfunktion kommt, wenn es eng wird.
### Commits
- 63c3c15 feat(aufgaben): Datenmodell und Regeln für Aufgaben und Projekte
- b3ca1c9 feat(aufgaben): Server Actions und Abfrageschicht
- a4a889f feat(aufgaben): Inbox, Aufgabenliste, Ideen und Projekte
- 7c8fe10 test: Erfassungsweg und Eindeutigkeit der Quellen absichern
- 144457b fix(aufgaben): Ideen aus Aufgabenliste und Inbox heraushalten
- 4d0f83f chore: Demo-Daten für die neuen Seiten erweitern
- d01c49b docs: Handbuchseiten zu Aufgaben, Ideen und Projekten
---
## 2026-09-10 – M1: Fundament
**Dauer:** 161 Minuten (12:06–14:47), Art `claude`.
**Ziel der Sitzung:** Das Projekt von Grund auf aufsetzen: Gerüst,
Datenbank, Anmeldung, Rollen, Audit, Instanzsteuerung, Betrieb und
Dokumentation.
### Erledigt
- Projektgerüst mit Next 16, React 19, TypeScript strict, Tailwind 4,
alle Versionen exakt gepinnt
- `npm run pruefen` als Sammelprüfung: Prettier, ESLint, `tsc`, Vitest,
ShellCheck
- Datenmodell und erste Migration: Benutzer mit Rolle, OIDC-Konten,
Passkeys, WebAuthn-Herausforderungen, Bereiche, Audit
- Anmeldung über OIDC, Passkey und Passwort, je Instanz zuschaltbar
- Rollenprüfung in Seite **und** Server Action, Audit je Schreibaktion
- Instanzsteuerung mit 404 statt Hinweis
- Bereiche verwalten, Protokollseite, Startseite, Einstellungen
- `/api/health`, Dockerfile, Compose-Stapel mit Migrationsdienst
- `backup.sh`, `update.sh`, `scripts/release.sh`, `scripts/zeit.sh`
- Unit-, Integrations-, Migrations- und E2E-Tests
- Screenshot-Kette mit eigener Wegwerf-Instanz
- README, Schnellstart, Handbuch, ROADMAP, CHANGELOG
### Entscheidungen mit Begründung
**Prisma 7.10.0 statt der notierten Fassung 6.** Fassung 6 ist überholt,
Fassung 8 liegt nur als RC auf `latest`. Prisma 7 nimmt die Verbindung
nicht mehr im Schema entgegen — sie steht in `prisma.config.ts`, der
Client bekommt den pg-Treiberadapter.
**TypeScript 6.0.3 statt 7.0.2.** TS 7 ist zwar `latest`, aber
`typescript-eslint@8.70` verlangt `typescript >=4.8.4 <6.1.0`. Mit TS 7
wäre die Lint-Kette kaputt gewesen.
**ESLint 9.39.5 statt 10.** Das in `eslint-config-next@16` gebündelte
`eslint-plugin-react` ruft noch `context.getFilename()`, das ESLint 10
entfernt hat. Der Lint-Lauf brach reproduzierbar ab.
**Eigener Auth.js-Adapter.** Der mitgelieferte `@auth/prisma-adapter`
greift fest auf `prisma.user` und `prisma.account` zu; unsere Modelle
heißen `Benutzer` und `Konto`. Der Adapter ist schmal und ersparte es,
die Fachbegriffe der Konvention zu opfern.
**Passkeys direkt über SimpleWebAuthn 14.** Der WebAuthn-Provider von
Auth.js ist als experimentell markiert und auf SimpleWebAuthn 9
festgelegt. Direkt angebunden bleibt die Fassung aktuell und der Ablauf
nachvollziehbar. Für die npm-Auflösung brauchte es `overrides`, weil
next-auth die älteren Fassungen als optionale Peers deklariert.
**Sitzungen als JWT, Rolle trotzdem aus der Datenbank.** Credentials-
Provider verlangen JWT. Damit eine Sperre nicht bis zum Ablauf des Tokens
wirkungslos bleibt, liest der `session`-Callback Rolle und Status bei
jeder Anfrage frisch. Die Tabellen `sitzung` und `verifizierungs_token`
wären damit tot und wurden wieder entfernt, solange es nur eine einzige
Migration gab.
**Migrationen als eigener Compose-Dienst.** Der Versuch, nur die
Prisma-CLI ins schlanke Laufzeitabbild zu kopieren, scheiterte an ihren
transitiven Modulen (`effect` über `@prisma/config`). Alle mitzunehmen
hätte das Abbild aufgebläht. Ein Einmaldienst aus der Bau-Stufe läuft vor
der App und muss sauber durchlaufen.
**Aufgabe, Zeitblock und Kalenderkonto bewusst nicht modelliert.** Sie
kommen mit M2, M4 und M3 — dann mit den Feldern, die der jeweilige
Provider wirklich braucht, statt heute geraten.
### Probleme und Lösungen
| Problem | Lösung |
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `prisma validate` lehnte `url` im Schema ab | Prisma 7 erwartet die Verbindung in `prisma.config.ts` und einen Treiberadapter im Client. |
| Integrationstest brach nach der ersten erwarteten Verletzung ab | In PostgreSQL bricht ein fehlgeschlagener Befehl die ganze Transaktion ab. Der Test läuft jetzt ohne Sammeltransaktion; alle Testdaten tragen eine Kennung und werden am Ende entfernt. |
| `prisma migrate diff` kannte `--from-url` nicht | In Prisma 7 heißen die Flaggen anders. Der Test vergleicht jetzt die eingespielte Wegwerf-Datenbank über `--from-config-datasource` gegen das Schema — und braucht damit keine Schattendatenbank. |
| Nach der Anmeldung ging es nicht zum ursprünglichen Ziel zurück | Das Layout leitet um, bevor die Seite drankommt, und kennt seinen Pfad nicht. Ein schlanker Proxy reicht ihn als Kopfzeile durch — ohne Anmeldelogik, weil er im Edge-Umfeld läuft. |
| Der Docker-Bau brach beim Sammeln der Seitendaten ab | Prisma-Client und Auth.js-Konfiguration wurden beim Laden des Moduls ausgewertet und brauchten Umgebungsvariablen, die es zur Bauzeit nicht gibt. Beide entstehen jetzt faul. |
| Ein Typfehler in `scripts/screenshots.ts` blockierte den Abbild-Bau | Entwicklungsskripte gehören nicht in den Docker-Kontext. `.dockerignore` schließt sie aus; geprüft werden sie von `npm run pruefen`. |
| ESLint beanstandete `setState` im Effekt | Der Hydratations-Trick im Farbschema-Knopf war unnötig: `next-themes` setzt die `dark`-Klasse vor der Hydratation, das erledigt CSS ohne Zustand. |
### Eigene Fehler, die ich korrigiert habe
- Die ersten vier Commits lagen direkt auf `main` statt auf einem
Meilenstein-Zweig und ohne Attributionszeile. Historie umgebaut:
`main` trägt nur noch einen leeren Wurzel-Commit, die Arbeit liegt auf
`m1-fundament`.
- Ein `git add -A` zog die Proxy-Umbenennung in den Doku-Commit. Wieder
getrennt.
- `scripts/screenshots.ts` wurde geschrieben, ohne danach `npm run
pruefen` laufen zu lassen; der Typfehler fiel erst im Docker-Bau auf.
### Offene Punkte
- Die `origin`-URL fehlt noch. Push und Merge stehen ohnehin erst nach
der Freigabe an.
- Der Schnellstart endet beim Passkey statt beim ersten Kalenderkonto und
der ersten Aufgabe — beides gibt es erst mit M3 und M2.
- Der Demo-Seed enthält noch keine erfundenen Termine; einen
Demo-Kalenderprovider gibt es erst mit M3.
- Lokal weichen die Ports ab (Datenbank 5435, App 3001), weil 5432 und
3000 auf diesem Rechner belegt sind. In `.env.example` stehen die
üblichen Werte.
### Commits
- de1f923 chore: Projektgerüst und Werkzeugkette einrichten
- 748d032 feat(datenbank): Datenmodell für Anmeldung, Bereiche und Audit
- e2b00f4 feat(instanz): Instanzsteuerung mit serverseitigem Riegel
- 19ca082 feat(zeit): Zeitrechnung und Anzeigeformate in Europe/Berlin
- 4ff67c1 feat(oberflaeche): Grundlayout, Navigation und Farbschema
- cc013fd feat(anmeldung): OIDC, Passkeys und Passwort mit Auth.js v5
- e9a2b4b feat(bereiche): Bereiche verwalten mit Rollenprüfung und Audit
- 1876ae4 feat(betrieb): Health-Endpunkt und Seed für eine frische Datenbank
- 24a799d chore: tsconfig-Anpassungen von Next 16 übernehmen
- 126ad93 test: Integrations-, Migrations- und E2E-Tests ergänzen
- f91d015 feat(betrieb): Abbild, Compose-Stapel und Betriebsskripte
- 56b5356 docs: README, Schnellstart, Handbuch, Fahrplan und CHANGELOG
- e3390c8 refactor: middleware in proxy umbenennen
- a882731 chore: Screenshot-Kette und Demo-Seed
- cf28ec1 test: Rollentrennung end-to-end absichern
- 6d04bc2 fix(oberflaeche): Kopfleiste einzeilig halten, Demo-Uhr feststellen
- 688d9f5 docs: Handbuchbilder aufnehmen