491 lines
28 KiB
Markdown
491 lines
28 KiB
Markdown
# 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 |
|
||
| M3 | claude | 41 | 0,68 |
|
||
| M4 | claude | 167 | 2,78 |
|
||
| M5 | claude | 420 | 7,00 |
|
||
| **Gesamt** | | **833** | **13,88** |
|
||
|
||
---
|
||
|
||
## 2026-09-11 – M5: Benachrichtigungen
|
||
|
||
**Dauer:** 420 Minuten (06:35–13:35), Art `claude`.
|
||
|
||
**Ziel der Sitzung:** Sagen, was ansteht — und dabei so selten stören, dass
|
||
niemand auf die Idee kommt, alles abzuschalten.
|
||
|
||
### Erledigt
|
||
|
||
- Regeln als reine Logik: Anlässe, Ruhezeit, Wiederholungssperre (30 Tests)
|
||
- Web-Push über VAPID samt Service Worker, An- und Abmeldung je Browser
|
||
- Mailversand über nodemailer, als Text und als HTML
|
||
- Vier Anlässe: Tagesüberblick, Fälligkeit, Vorlauf vor einem Zeitblock,
|
||
Abgleichfehler
|
||
- Bedienung in den Einstellungen mit Probemeldung und Geräteliste
|
||
- Ein Lauf alle zehn Minuten, Nachweise räumt die Nachtrunde weg
|
||
- 2 neue E2E-Tests, 7 neue Integrationsprüfungen, Handbuchseite
|
||
|
||
### Entscheidungen mit Begründung
|
||
|
||
**Ab Werk meldet der Planer gar nichts.** Kein Weg ist eingeschaltet. Wer
|
||
beim ersten Start mit Meldungen überschüttet wird, schaltet alles ab — und
|
||
dann auch das Nützliche. Die Einrichtung ist ein bewusster Handgriff, und
|
||
das ist der Preis dafür, dass die Meldungen später ernst genommen werden.
|
||
|
||
**Der Tagesüberblick ist von der Ruhezeit ausgenommen.** Sonst bekäme ihn
|
||
nie, wer ihn auf 06:00 stellt — die voreingestellte Ruhezeit läuft bis
|
||
07:00. Die Ruhezeit schützt vor Störungen, nicht vor der Nachricht, um die
|
||
ausdrücklich gebeten wurde.
|
||
|
||
**Der Nachweis wird vor dem Senden geschrieben, nicht danach.** Bricht der
|
||
Versand mittendrin ab, gilt die Meldung als zugestellt und kommt nicht
|
||
wieder. Andersherum würde ein hängender Mailserver dieselbe Nachricht alle
|
||
zehn Minuten erneut versuchen. Lieber eine verpasste Nachricht als eine,
|
||
die im Minutentakt klopft.
|
||
|
||
**Was in die Ruhezeit fällt, wird nicht nachgeholt.** Eine Erinnerung an
|
||
einen Zeitblock, der längst begonnen hat, ist nur der Beweis, dass die
|
||
Anwendung zu spät kommt.
|
||
|
||
**Abgleichfehler erst ab dem dritten Versuch in Folge.** Ein Netz hustet,
|
||
ein Anbieter hat Wartung. Beim ersten Fehlschlag zu melden hieße, für
|
||
Dinge zu wecken, die sich von selbst erledigen. Nach dem dritten Mal ist es
|
||
dagegen dringend — solange steht die Wochenansicht auf altem Stand.
|
||
|
||
**Der Tagesüberblick entfällt, wenn es nichts zu sagen gibt.** „Heute steht
|
||
nichts an" ist keine Information, sondern Lärm.
|
||
|
||
**Tote Abos putzen sich selbst weg.** 404 und 410 heißen endgültig, das
|
||
Gerät verschwindet sofort; andere Fehler zählen hoch und führen nach fünf
|
||
Versuchen zum selben Ergebnis. Eine Liste, die von Hand gepflegt werden
|
||
muss, wird nicht gepflegt.
|
||
|
||
**Push-Schlüssel liegen verschlüsselt, Endpunkte nie im Protokoll.** Ein
|
||
Endpunkt genügt, um jemandem Nachrichten zu schicken — er gehört damit in
|
||
dieselbe Klasse wie ein Token.
|
||
|
||
### Probleme und Lösungen
|
||
|
||
| Problem | Lösung |
|
||
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| `pushManager.subscribe` lehnte den VAPID-Schlüssel als `Uint8Array<ArrayBufferLike>` ab | Der Puffer wird jetzt ausdrücklich als `ArrayBuffer` angelegt; ein geteilter Puffer ist dort nicht erlaubt. |
|
||
| Der Integrationstest brach mit einem Fremdschlüsselfehler ab | Die neuen Prüfungen standen hinter dem Löschen des Testbenutzers. Jetzt davor — und danach wird geprüft, dass Geräte und Nachweise mit ihm verschwinden. |
|
||
| E2E-Läufe scheiterten unter Last immer wieder beim Anmelden | Ein Klick, der vor der Hydratation ankommt, verpufft folgenlos. Der Anmeldehelfer liegt jetzt einmal in `e2e/hilfe.ts` und fasst nach, bis die Startseite da ist. |
|
||
| Die Karte meldete gleichzeitig „Browser blockiert" und „Server nicht eingerichtet" | Zwei Aussagen, von denen nur eine zählt. Der Serverzustand geht jetzt vor. Gefunden nur durch das Ansehen des aufgenommenen Bildes. |
|
||
| Das erfundene Push-Gerät in den Demo-Daten wäre beim ersten Versandlauf gelöscht worden | Seine Schlüssel lagen als Klartext-Platzhalter, und unlesbare Schlüssel führen zum Entfernen. Die Demo-Daten enthalten jetzt kein Gerät mehr — die Bilder zeigen den Zustand davor. |
|
||
| Im Bild meldet der Browser „blockiert", obwohl die Erlaubnis erteilt wurde | Chromium im Kopflosbetrieb antwortet auf `Notification.permission` immer mit `denied`; `grantPermissions` ändert daran nichts. Das Handbuch schreibt es jetzt unter das Bild. |
|
||
| Ein Docker-Bau brauchte 14 Minuten, ein E2E-Lauf 18 | Systemlast, kein Codeproblem: dieselben Läufe danach in 1,5 beziehungsweise 0,4 Minuten. Ein vergessener Testserver auf Port 3100 hat kräftig mitgeholfen. |
|
||
|
||
### Offene Punkte
|
||
|
||
- Der Versandweg ist gegen die Logik geprüft, nicht gegen echte Dienste:
|
||
ohne VAPID-Schlüssel und SMTP-Zugang geht lokal nichts hinaus. Das braucht
|
||
einen Lauf auf dem Server.
|
||
- Mehrere Anlässe erscheinen als mehrere Nachrichten. Ob eine Sammelmeldung
|
||
fehlt, zeigt erst der Alltag.
|
||
|
||
### Commits
|
||
|
||
- 24f613d feat: Datenmodell und Umgebung für Benachrichtigungen
|
||
- ddb13d7 feat: Regeln für Benachrichtigungen als reine Logik
|
||
- e0e11c8 feat: Versand per Web-Push und E-Mail
|
||
- b622c1b feat: Benachrichtigungen in den Einstellungen bedienen
|
||
- 047a4a1 feat: Meldungen alle zehn Minuten prüfen und verschicken
|
||
- 5bbbdd5 chore: Demo-Daten und Bildaufnahme für Benachrichtigungen
|
||
- f03ea73 docs: Handbuchseite und README-Abschnitt zu Benachrichtigungen
|
||
- 6e1f279 test: Benachrichtigungen end-to-end und in der Datenbank absichern
|
||
- 7bef435 fix: Serverzustand geht der Browsermeldung vor
|
||
- 35ecb2f chore: Screenshot-Instanz mit eingerichteten Meldewegen
|
||
- bb7b189 docs: Handbuchbilder für M5 neu aufnehmen
|
||
|
||
---
|
||
|
||
## 2026-09-10 – M4: Wochenplanung und Zeitblöcke
|
||
|
||
**Dauer:** 167 Minuten (20:42–23:29), Art `claude`.
|
||
|
||
**Ziel der Sitzung:** Die Kernidee des Projekts — Aufgaben per Ziehen und
|
||
Ablegen in echte Kalendertermine verwandeln, mit Rückweg.
|
||
|
||
### Erledigt
|
||
|
||
- `Zeitblock` samt Migration und Schleifenschutz (30 Tests)
|
||
- Schreibweg zu Graph und Google mit `If-Match`, Rückweg im Abgleich
|
||
- Rasterlogik für die Wochenansicht (21 Tests)
|
||
- Wochenansicht mit `@dnd-kit/core`: einplanen, verschieben, ausplanen
|
||
- Kapazität und Belegung je Wochentag
|
||
- 3 neue E2E-Tests, Handbuchseite
|
||
|
||
### Entscheidungen mit Begründung
|
||
|
||
**Der Fingerabdruck geht nur über Beginn und Ende.** Titel und Beschreibung
|
||
ändern sich auch aus harmlosen Gründen — etwa weil der Aufgabentitel
|
||
bearbeitet wurde. Für die Planung zählt allein, wann der Block liegt; alles
|
||
andere in den Fingerabdruck zu nehmen hieße, den Anbieter grundlos zu
|
||
behelligen.
|
||
|
||
**Der Kalender gewinnt, ausnahmslos.** Wer im Kalender etwas verschiebt,
|
||
meint es so. Säße der Planer am längeren Hebel, zöge er dem Benutzer den
|
||
Termin wieder weg — und der wüsste nicht, warum. Bei HTTP 412 verwirft der
|
||
Planer sogar seinen eigenen Fingerabdruck, damit der nächste Abgleich die
|
||
fremde Änderung sicher als solche erkennt.
|
||
|
||
**Scheitert das Schreiben, überlebt die Planung.** Der Zeitblock bleibt mit
|
||
vermerktem Fehler stehen. Ihn zu verwerfen hieße, die Planung des Benutzers
|
||
wegen eines Netzproblems zu vergessen; das nächste Verschieben trägt ihn
|
||
nach. Beim Löschen ist es umgekehrt: Da verschwindet der Zeitblock auch bei
|
||
unerreichbarem Kalender, sonst wäre die Planung blockiert.
|
||
|
||
**Die Dauer kommt aus dem geschätzten Aufwand.** Wer schätzt, bekommt die
|
||
passende Blockgröße geschenkt. Das belohnt genau die Gewohnheit, die die
|
||
Planung überhaupt erst tragfähig macht.
|
||
|
||
**Ziehen beginnt erst nach acht Pixeln.** Ohne diese Schwelle löst jeder
|
||
Klick einen Zug aus und die Knöpfe im Block wären nicht mehr bedienbar.
|
||
|
||
**Überbuchte Tage werden rot, aber nicht gesperrt.** Der Planer ist ein
|
||
Werkzeug, kein Aufpasser. Manchmal muss ein Tag überbucht sein.
|
||
|
||
### Probleme und Lösungen
|
||
|
||
| Problem | Lösung |
|
||
| --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||
| Ein Zeitblock steht auch im Terminspiegel und erschien doppelt — einmal als eigene Planung, einmal als fremder Termin | Die Wochenabfrage filtert Termine heraus, deren `extId` zu einem Zeitblock gehört. |
|
||
| Ein Build lief 32 Minuten statt Sekunden | Systemlast durch einen gleichzeitigen Docker-Bau. Ein sauberer Neubau danach: 46 Sekunden. Kein Codeproblem. |
|
||
| Sich überschneidende Termine lagen unsichtbar übereinander | `spurenVerteilen` legt sie nebeneinander und gibt eine Spur wieder frei, sobald sie zu Ende ist. |
|
||
|
||
### Offene Punkte
|
||
|
||
- **Der Schreibweg ist nicht gegen die echten APIs geprüft.** Wie in M3
|
||
fehlen die Zugangsdaten. Die Logik ist getestet, der erste echte Schreib-
|
||
und Rückweg ist Florians.
|
||
- Die Dauer lässt sich nicht am Balken ziehen; wer eine andere will, ändert
|
||
den Aufwand der Aufgabe.
|
||
- Ein Zeitblock landet immer im ersten verbundenen Kalender. Das
|
||
Standard-Kalenderkonto je Bereich ist im Datenmodell vorbereitet, aber
|
||
noch nicht in der Oberfläche.
|
||
|
||
### Commits
|
||
|
||
- 5791047 feat(zeitbloecke): Datenmodell und Schleifenschutz
|
||
- 9c1c916 feat(zeitbloecke): Schreibweg in den Kalender und Rückweg zurück
|
||
- bcb4b8e feat(woche): Rasterlogik für die Wochenansicht
|
||
- fada445 feat(woche): Wochenansicht mit Ziehen und Ablegen
|
||
- 5b1e998 test: Wochenansicht und Ausplanen absichern
|
||
- 0974695 docs: Handbuchseite zur Wochenplanung
|
||
|
||
---
|
||
|
||
## 2026-09-10 – M3: Kalender lesen
|
||
|
||
**Dauer:** 41 Minuten (17:48–18:29), Art `claude`.
|
||
|
||
**Ziel der Sitzung:** Termine aus Microsoft 365 und Google lesen, damit der
|
||
Planer weiß, wann überhaupt Platz ist.
|
||
|
||
### Erledigt
|
||
|
||
- `Kalenderkonto` und `Termin` samt Migration; Bereich bekommt sein
|
||
Standard-Kalenderkonto für M4
|
||
- Tokenverschlüsselung mit AES-256-GCM
|
||
- Antworten beider Anbieter mit zod geprüft und in eine gemeinsame Form
|
||
gebracht, geprüft gegen anonymisierte Beispielantworten
|
||
- Belegungsrechnung: Kapazität gegen Termine, freie Fenster für M4
|
||
- OAuth-Verbinden, Tokenerneuerung, seitenweiser Abgleich mit Rückfall auf
|
||
einen vollständigen Durchlauf
|
||
- Seite für die Kalenderkonten, Belegung des Tages auf der Startseite
|
||
- Hintergrundjobs für Abgleich und nächtliches Aufräumen
|
||
- 68 neue Unit-Tests, 10 neue Integrationsprüfungen
|
||
- Handbuchseite und Einrichtungsanleitung für Entra ID und Google
|
||
|
||
### Entscheidungen mit Begründung
|
||
|
||
**Tokens verschlüsselt, Schlüssel aus AUTH_SECRET abgeleitet.** Wer die
|
||
Datenbank oder eine Sicherung bekommt, hätte sonst direkten Zugriff auf
|
||
fremde Kalender — das wiegt schwerer als bei den OIDC-Tokens aus M1, die
|
||
nur die Anmeldung betreffen. Eine eigene Pflichtvariable wäre eine weitere
|
||
Stelle, an der die Einrichtung scheitert; die Ableitung über HKDF kostet
|
||
nichts und hat die richtige Nebenwirkung: wer AUTH_SECRET wechselt, muss
|
||
die Konten neu verbinden — dann sind ohnehin alle Sitzungen ungültig.
|
||
|
||
**Bei fremder Zeitzone abbrechen statt raten.** Graph liefert Datum und
|
||
Zeitzone getrennt, ohne Versatz im Text. Wir fragen ausdrücklich UTC an;
|
||
kommt etwas anderes zurück, wäre jede Umrechnung geraten und würde Termine
|
||
um Stunden verschieben. Ein Abbruch mit klarer Meldung ist besser als ein
|
||
Kalender, der leise falsch liegt.
|
||
|
||
**Unbekanntes showAs zählt als belegt.** Lieber eine Stunde zu viel
|
||
blockiert als eine Doppelbuchung.
|
||
|
||
**Schreibrechte schon jetzt anfragen.** M3 liest nur, aber M4 schreibt.
|
||
Später erneut zuzustimmen ist genau der Schritt, an dem Einrichtung
|
||
scheitert — einmal breiter fragen ist das kleinere Übel.
|
||
|
||
**Ganztägige Termine zählen nicht zur Belegung.** Ein Urlaubstag oder
|
||
Geburtstag blockiert sonst den ganzen Tag, obwohl er keine Arbeitszeit
|
||
kostet.
|
||
|
||
**Alle Kalender zählen gegen jeden Bereich.** Ein Termin blockiert die Zeit
|
||
ohnehin, gleich zu welchem Lebensbereich er gehört.
|
||
|
||
**Kein SDK für Graph und Google.** Es geht um drei Aufrufe je Anbieter, und
|
||
die Antworten werden ohnehin mit zod geprüft. Ein SDK brächte vor allem
|
||
Abhängigkeiten und blinde Flecken.
|
||
|
||
### Probleme und Lösungen
|
||
|
||
| Problem | Lösung |
|
||
| ----------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||
| Prisma lässt in einem zusammengesetzten Unique-Schlüssel kein null zu | `kalenderId` ist nicht optional, sondern trägt den sprechenden Wert `"standard"` für den Standardkalender. |
|
||
| Googles `syncToken` verträgt sich nicht mit `timeMin`/`timeMax` | Folgeabgleiche liefern auch Änderungen außerhalb des Fensters. Was daneben liegt, wird beim Spiegeln verworfen — sonst wüchse der Spiegel unbegrenzt. |
|
||
| Der Verweis für die nächste Runde kann ablaufen (HTTP 410) | Verweis verwerfen, Spiegel leeren, von vorn lesen. Der Ablauf ist so gebaut, dass ein vollständiger Durchlauf jederzeit möglich ist. |
|
||
| Die Shell zerlegte in mehreren Anläufen Template-Literale und Backticks | Auf `node - <<'SKRIPT'` umgestellt; damit geht der Text unverändert durch. |
|
||
|
||
### Offene Punkte
|
||
|
||
- **Nicht gegen die echten APIs getestet.** Ich habe keine Zugangsdaten für
|
||
Entra ID oder Google. Provider und Abgleich sind gegen anonymisierte
|
||
Beispielantworten geprüft; den ersten echten Verbindungsversuch muss
|
||
Florian machen. Die Anleitung dafür steht in der README.
|
||
- Nur der Standardkalender je Konto. Mehrere Kalender desselben Kontos sind
|
||
im Datenmodell vorgesehen, es fehlt die Auswahl in der Oberfläche.
|
||
- Der Abgleich läuft in einer Schleife über alle Konten. Bei vielen Konten
|
||
wäre das nacheinander zu langsam; bei der erwarteten Zahl ist es das
|
||
einfachere Verhalten.
|
||
|
||
### Commits
|
||
|
||
- 79e6c0d feat(kalender): Datenmodell für Kalenderkonten und Termine
|
||
- 6fda2a5 feat(kalender): Antworten von Graph und Google lesen, Belegung rechnen
|
||
- 9946e7c feat(kalender): OAuth und Abgleich für Graph und Google
|
||
- 0c67b73 feat(kalender): Seite für die Kalenderkonten
|
||
- 42c98c8 feat(kalender): Hintergrundjobs für Abgleich und Aufräumen
|
||
- d8d3227 feat(kalender): Belegung des Tages auf der Startseite
|
||
- df50627 test: Verschlüsselung und Kalendermodelle absichern
|
||
- a8b2ff0 docs: Handbuchseite und Einrichtung der Kalenderanbieter
|
||
|
||
---
|
||
|
||
## 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
|