# 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 | | **Gesamt** | | **413** | **6,88** | --- ## 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 `