Commit Graph
89 Commits
Author SHA1 Message Date
Florian WildeandClaude Opus 5 70ed3a5220 chore(release): 0.6.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 09:18:51 +02:00
Florian WildeandClaude Opus 5 928f841adb docs: DEVLOG-Eintrag fuer M6
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 09:02:58 +02:00
Florian WildeandClaude Opus 5 98d170b68a docs: Handbuchbilder fuer M6 neu aufnehmen
Der Hinweis unter dem Wochenraster erklaerte nur die grauen Balken; die
gestreiften aus der anderen Instanz fehlten darin.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 09:00:49 +02:00
Florian WildeandClaude Opus 5 475b1d5c08 docs: Handbuchseite, README und Fahrplan fuer M6
Der Wartungsplaner steht als offener Punkt drin, nicht als Haken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 04:51:38 +02:00
Florian WildeandClaude Opus 5 3c7cdbbbf3 chore: Demo-Daten, Bildaufnahme und Platz in der Kopfleiste
Die Demo-Instanz redet mit niemandem mehr: Solange DEMO_MODUS gilt,
starten die Jobs fuer Zammad, Telegram und den Spiegel gar nicht erst.
Sie ist mit erfundenen Daten gefuellt und steht nur fuer Bilder — ein
Aufruf nach draussen waere bestenfalls sinnlos.

Die Kopfleiste ist breiter als die Inhaltsspalte. Mit dem elften Eintrag
passte sie dort nicht mehr, und "Einstellungen" verschwand
stillschweigend am rechten Rand. Aufgefallen ist das erst im
aufgenommenen Bild.

Der Kartentext beschrieb noch den Weg von vor dem Kopplungscode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 04:51:37 +02:00
Florian WildeandClaude Opus 5 f5eadefdbb feat: Lauf von Hand anstossen und Spiegelschalter in der Liste
Direkt nach dem Einrichten wuesste man sonst erst beim naechsten Takt,
ob die Zugangsdaten stimmen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:59:14 +02:00
Florian WildeandClaude Opus 5 3befcc19a3 feat: Aufgaben einzeln zum Spiegeln freigeben
Der Schalter erscheint nur, wo auch gespiegelt wird.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:58:46 +02:00
Florian WildeandClaude Opus 5 4da2f3d9c6 test: Spiegel-Eingang und Integrationstabellen absichern
Der Eingang des Spiegels ist die einzige Tuer, die ohne Anmeldung
aufgeht. Vier Faelle: ohne Signatur, falsch signiert, zu alt, und der
gute Fall — bei dem eine nicht mehr geschickte Belegung auch hier
verschwinden muss.

Der Testserver bekommt dafuer dasselbe Geheimnis wie der Test. Eine
Pruefung, die immer uebersprungen wird, prueft nichts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:58:43 +02:00
Florian WildeandClaude Opus 5 0d0f8503cc feat: Gespiegelte Belegung in der Wochenansicht zeigen
Sie zaehlt gegen die Tageskapazitaet wie jeder fremde Termin, traegt
aber keinen Betreff und ist gestreift gezeichnet — damit niemand darin
einen Termin vermutet, dem der Titel fehlt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:04:35 +02:00
Florian WildeandClaude Opus 5 1281c3ea96 feat: Integrationen sichtbar und bedienbar machen
Eine Seite zeigt je Integration, ob sie vorgesehen, eingerichtet oder
gestoert ist, wann sie zuletzt geglueckt ist und woran sie klemmt. Der
Wartungsplaner steht sichtbar als offene Luecke darin, nicht als
Leerstelle.

Telegram-Chats werden hier gekoppelt und wieder getrennt, Meldungen je
Chat einzeln geschaltet. Aufgaben bekommen einen Schalter zum Spiegeln —
einzeln und je Aufgabe, denn wer versehentlich einen ganzen Bereich
spiegelt, schickt Kundennamen nach Hause.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:04:33 +02:00
Florian WildeandClaude Opus 5 bc60e5f864 feat: Integrationen takten und Telegram als dritten Meldeweg
Telegram alle zwei Minuten, Zammad und Spiegel alle fuenfzehn. Wer dem
Bot etwas schickt, erwartet die Aufgabe gleich in der Liste; in Zammad
aendert sich dagegen nichts im Minutentakt.

Meldungen gehen jetzt auch per Telegram hinaus — mit einem Knopf zum
Abhaken, wenn es um genau eine Aufgabe geht. Eine klemmende Integration
meldet sich wie ein klemmender Kalender: erst nach dem dritten
Fehlversuch, dann aber deutlich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:04:05 +02:00
Florian WildeandClaude Opus 5 6dad3331e2 feat: Zammad, Telegram und den Spiegel anbinden
Zammad liest ueber die Suche und schreibt nichts zurueck. Telegram holt
per getUpdates statt per Webhook — der Planer soll auch hinter einem
Anschluss ohne feste Adresse laufen. Der Spiegel schickt Belegungen ohne
Titel und nur die ausdruecklich markierten Aufgaben.

Wer dem Telegram-Bot schreibt, ohne im Adressbuch zu stehen, bekommt
keine Antwort. Die einzige Ausnahme ist ein gueltiger Kopplungscode: Er
ist der Beweis, dass jemand mit Zugang zur Anwendung diesen Chat will.
Ohne ihn muesste man die Chatkennung von Hand eintragen — und um sie zu
erfahren, muesste der Bot Unbekannten antworten.

Jede Sendung des Spiegels traegt eine Signatur ueber den Rumpf und gilt
fuenf Minuten. Der Empfaenger antwortet auf jeden Fehlschlag gleich:
falsches Geheimnis, verstellte Uhr oder Unsinn im Inhalt sind von aussen
nicht zu unterscheiden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:04:03 +02:00
Florian WildeandClaude Opus 5 e4d3547457 feat: Aufnahmeregeln fuer Integrationen als reine Logik
Was aus einem Fund wird, entscheidet fuer alle Integrationen dieselbe
Stelle: anlegen, aktualisieren, schliessen oder nichts tun.

Gefuehrt werden nur Titel, Notiz, Verweis und Faelligkeit. Bereich,
Projekt, Prioritaet und geschaetzter Aufwand gehoeren der Planung — wer
ein Ticket einsortiert und schaetzt, will nicht, dass der naechste Lauf
das wieder wegraeumt.

Dazu die Regeln des Spiegels: Aufbau der Sendung und ihre Frist. Ohne
Frist liesse sich ein abgefangenes Paket beliebig oft erneut einspielen.

21 Tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:03:33 +02:00
Florian WildeandClaude Opus 5 7f8f505126 feat: Datenmodell und Umgebung fuer Integrationen
Ein Zustand je Quelle haelt fest, wie weit gelesen wurde und ob es
klappt. Der Merker ist bewusst ein freier Text: Zammad braucht darin
einen Zeitstempel, Telegram den Zaehler der zuletzt geholten Nachricht.

Dazu die gespiegelte Belegung ohne Titel, das Adressbuch der erlaubten
Telegram-Chats und der Einmalcode, mit dem sich ein Chat anmeldet.

Welche Integration eine Instanz darf, entscheidet weiterhin INSTANZ. Ein
Zammad-Token in der Instanz daheim bleibt wirkungslos — das steht in den
Faehigkeiten, nicht in einer Einstellung, die sich verstellen liesse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 23:03:32 +02:00
Florian WildeandClaude Opus 5 ab1b898e44 Merge: Meilenstein 5 – Benachrichtigungen (0.5.0)
Der Planer sagt jetzt, was ansteht: Tagesueberblick am Morgen, faellige
Aufgaben, Vorlauf vor einem Zeitblock und ein Kalender, der sich nicht
abgleichen laesst. Per Web-Push, per E-Mail oder beides.

Ab Werk meldet er gar nichts. Eine Anwendung, die zu oft meldet, wird
stummgeschaltet und ist dann wertlos — deshalb ist die Einrichtung ein
bewusster Handgriff, dafuer kommt dann auch nur, was gewollt ist.

Der Nachweis einer Meldung wird vor dem Senden geschrieben: lieber eine
verpasste Nachricht als eine, die im Minutentakt erneut versucht wird.

Geprueft vor dem Merge: npm run pruefen mit 280 Unit-Tests, 47
Integrationspruefungen, Migrationstest und 20 E2E-Tests. Der Versandweg ist
gegen die Logik geprueft, nicht gegen echte Dienste — dafuer fehlen
VAPID-Schluessel und SMTP-Zugang.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 20:43:55 +02:00
Florian WildeandClaude Opus 5 1da2068440 chore(release): 0.5.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 13:39:02 +02:00
Florian WildeandClaude Opus 5 01d7b10599 docs: DEVLOG-Eintrag fuer M5
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 13:37:04 +02:00
Florian WildeandClaude Opus 5 350f5fdbfc docs: Fahrplan und Aenderungen fuer M5 fortschreiben
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 13:37:02 +02:00
Florian WildeandClaude Opus 5 bb7b189e53 docs: Handbuchbilder fuer M5 neu aufnehmen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 13:34:44 +02:00
Florian WildeandClaude Opus 5 35ecb2fa78 chore: Screenshot-Instanz mit eingerichteten Meldewegen
Die Wegwerf-Instanz bekommt je Lauf ein frisch erzeugtes VAPID-Paar und
einen Mailhost, den es nie geben wird. Damit zeigen die Bilder den
eingerichteten Normalfall statt zweier Warnhinweise — und im Verzeichnis
bleibt kein privater Schluessel liegen.

Kein Push-Geraet mehr in den Demo-Daten: Seine Schluessel liegen
verschluesselt, und der Versand entfernt jedes Geraet, dessen Schluessel
er nicht lesen kann. Der erfundene Eintrag waere beim ersten Lauf wieder
verschwunden.

Dass der Browser im Bild "blockiert" meldet, laesst sich nicht abstellen:
Chromium im Kopflosbetrieb antwortet auf Notification.permission immer so.
Das Handbuch sagt es jetzt dazu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 13:34:40 +02:00
Florian WildeandClaude Opus 5 7bef4351ec fix: Serverzustand geht der Browsermeldung vor
Fehlen die Push-Schluessel auf dem Server, stand unter "Dieses Geraet"
trotzdem, der Browser habe blockiert — zwei Aussagen nebeneinander, von
denen nur eine zaehlt. Jetzt sagt die Karte zuerst das, was den Ausschlag
gibt.

Aufgefallen ist es erst im aufgenommenen Bild.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 13:34:39 +02:00
Florian WildeandClaude Opus 5 6e1f2791cd test: Benachrichtigungen end-to-end und in der Datenbank absichern
Die Einstellung haelt ueber das Neuladen, ohne eingeschalteten Weg geht
nichts hinaus, und ein unsinniger Vorlauf wird auch dann abgewiesen, wenn
er an der Oberflaeche vorbei hereinkommt.

Der Anmeldehelfer liegt jetzt einmal in e2e/hilfe.ts und fasst nach: ein
Klick, der vor der Hydratation ankommt, verpufft folgenlos — das hat die
Testlaeufe unter Last wiederholt scheitern lassen.

Der Integrationstest prueft die drei neuen Tabellen samt Kaskade.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 10:28:32 +02:00
Florian WildeandClaude Opus 5 f03ea73dcd docs: Handbuchseite und README-Abschnitt zu Benachrichtigungen
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 09:00:23 +02:00
Florian WildeandClaude Opus 5 5bbbdd5209 chore: Demo-Daten und Bildaufnahme fuer Benachrichtigungen
Die Aufnahme kann jetzt einen einzelnen Ausschnitt statt der ganzen Seite
festhalten. Ein Bild der kompletten Einstellungsseite zeigt im Kapitel zu
den Benachrichtigungen zu wenig vom Eigentlichen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 09:00:20 +02:00
Florian WildeandClaude Opus 5 047a4a1c74 feat: Meldungen alle zehn Minuten pruefen und verschicken
Der Takt passt zu den Toleranzfenstern: ein Anlass in einem
Viertelstundenfenster wird sicher getroffen, dank Sperre aber nur einmal
gemeldet. Eine Erinnerung kommt damit hoechstens ein paar Minuten frueher
als gewollt, nie spaeter.

Der Kalenderabgleich zaehlt seine Fehlversuche jetzt mit und setzt den
Zaehler bei Erfolg zurueck.

Alte Nachweise raeumt der naechtliche Lauf weg.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 09:00:00 +02:00
Florian WildeandClaude Opus 5 b622c1bb28 feat: Benachrichtigungen in den Einstellungen bedienen
Anmelden dieses Browsers, Liste der angemeldeten Geraete, Auswahl der
Anlaesse, Ruhezeit und Vorlauf. Ist ein Weg auf dem Server nicht
eingerichtet, steht das direkt daneben statt in einem Fehler beim
Speichern.

Der Knopf "Probe senden" umgeht Ruhezeit und Sperre absichtlich: wer
darauf drueckt, will jetzt wissen, ob es ankommt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 08:59:57 +02:00
Florian WildeandClaude Opus 5 e0e11c85b2 feat: Versand per Web-Push und E-Mail
Push ueber VAPID, Mail ueber SMTP, beide unabhaengig zuschaltbar. Die
Schluessel eines Push-Geraets liegen verschluesselt in der Datenbank.

Ein Geraet, das der Push-Dienst mit 404 oder 410 ablehnt, wird sofort
entfernt; andere Fehler zaehlen hoch und fuehren nach fuenf Versuchen
zum selben Ergebnis. Eine Liste toter Abos putzt sich so von allein.

Der Nachweis wird vor dem Senden geschrieben, nicht danach: lieber eine
verpasste Nachricht als eine, die im Minutentakt erneut versucht wird.

Weder Endpunkt noch Schluessel noch Mailinhalt landen im Protokoll.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 08:59:31 +02:00
Florian WildeandClaude Opus 5 ddb13d7dd0 feat: Regeln fuer Benachrichtigungen als reine Logik
Anlaesse, Ruhezeit ueber Mitternacht, Wiederholungssperre und die
Entscheidung, ob eine Meldung jetzt hinausgeht. Ohne Datenbank und ohne
Systemzeit — der Zeitpunkt kommt immer als Parameter herein, damit sich
alles ohne Warten pruefen laesst.

Der Tagesueberblick ist von der Ruhezeit ausgenommen: wer ihn auf 06:00
stellt, bekaeme ihn sonst nie.

Die Voreinstellung ist bewusst zurueckhaltend. Wer beim ersten Start mit
Meldungen ueberschuettet wird, schaltet alles ab — und dann auch das
Nuetzliche.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 08:59:27 +02:00
Florian WildeandClaude Opus 5 24f613d372 feat: Datenmodell und Umgebung fuer Benachrichtigungen
Drei Tabellen: die Einstellung je Benutzer, angemeldete Push-Geraete und
ein Nachweis je verschickter Meldung. Der Nachweis ist das Gedaechtnis
gegen Wiederholungen — ohne ihn kaeme dieselbe Sache bei jedem Durchlauf
erneut.

Kalenderkonten bekommen einen Zaehler fuer Fehlversuche in Folge, damit
ein Abgleichfehler erst nach mehreren Versuchen gemeldet wird.

VAPID- und SMTP-Werte sind freiwillig: fehlen sie, laeuft der Planer
ohne den jeweiligen Weg weiter und sagt es in den Einstellungen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 08:58:59 +02:00
Florian WildeandClaude Opus 5 ef79b24a96 Merge: Meilenstein 4 – Wochenplanung und Zeitblöcke (0.4.0)
Damit steht die Kernidee des Projekts: Aufgaben werden per Ziehen und
Ablegen zu echten Kalenderterminen, und Verschiebungen aus dem Kalender
kommen zurück.

Der Schleifenschutz über sha256(startISO|endeISO) verhindert, dass App
und Kalender einander endlos hinterherschreiben. Bei HTTP 412 gewinnt der
Kalender.

Geprüft vor dem Merge: npm run pruefen mit 250 Unit-Tests, 40
Integrationsprüfungen, Migrationstest und 17 E2E-Tests. Der Schreibweg ist
gegen die Logik geprüft, nicht gegen die echten APIs — dafür fehlen die
Zugangsdaten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 06:33:28 +02:00
Florian WildeandClaude Opus 5 57bf7748be chore(release): 0.4.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 06:33:19 +02:00
Florian WildeandClaude Opus 5 30387ea5af docs: M4-Datum auf den Freigabetag setzen
Der Eintrag trägt jetzt das Datum des Tags, damit ROADMAP und CHANGELOG
dieselbe Zahl nennen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-11 06:29:45 +02:00
Florian WildeandClaude Opus 5 e9e2ae51ec docs: DEVLOG-Eintrag für M4
Ziel, Erledigtes, die Entscheidungen mit Begründung, die Probleme samt
Lösung und die offenen Punkte — allen voran, dass auch der Schreibweg
nicht gegen die echten APIs geprüft werden konnte.

Dauer laut zeiten.csv: 167 Minuten. Gesamt bislang 413 Minuten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 23:32:21 +02:00
Florian WildeandClaude Opus 5 5429b960fd docs: Handbuchbilder für M4 neu aufnehmen
44 Bilder aus der Wegwerf-Instanz: elf Seiten, je hell und dunkel,
Schreibtisch und Handy. Neu dabei die Wochenansicht mit eingeplanten
Zeitblöcken.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 23:32:20 +02:00
Florian WildeandClaude Opus 5 0b967d4a33 fix(woche): Wochenansicht aus dem schmalen Inhaltsrahmen befreien
Der Fehler fiel erst im aufgenommenen Bild auf: Bei sieben Spalten plus
Aufgabenliste war der Sonntag abgeschnitten — der Inhaltsrahmen von 64rem
ist für eine Wochenansicht zu eng.

Genau diese eine Seite bricht jetzt aus und nimmt bis zu 100rem, mittig
und mit demselben Seitenabstand wie überall. Die anderen Seiten bleiben
bei ihrer Lesebreite; eine Aufgabenliste über 100rem zu ziehen wäre keine
Verbesserung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 23:32:18 +02:00
Florian WildeandClaude Opus 5 0974695d93 docs: Handbuchseite zur Wochenplanung
Erklärt nicht nur, wo man zieht, sondern warum der Kalender immer gewinnt,
warum sich das nicht aufschaukelt und was das Warnzeichen im Block
bedeutet.

Dazu der ehrliche Abschnitt "Was noch nicht geht": die Dauer lässt sich
nicht am Balken ziehen, und ein Zeitblock landet immer im ersten
verbundenen Kalender.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 23:17:23 +02:00
Florian WildeandClaude Opus 5 5b1e9981e4 test: Wochenansicht und Ausplanen absichern
Drei E2E-Tests: der Hinweis ohne verbundenen Kalender, ein Zeitblock im
Raster samt Ausplanen, und das Blättern durch die Wochen.

Der mittlere Test läuft bewusst mit einem Kalenderkonto ohne gültige
Tokens. Der Schreibweg zum Anbieter scheitert dann — und genau das ist
prüfenswert: Die Planung muss auch überleben, wenn der Kalender nicht
erreichbar ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 23:17:22 +02:00
Florian WildeandClaude Opus 5 fada4450bd feat(woche): Wochenansicht mit Ziehen und Ablegen
Links die einzuplanenden Aufgaben, rechts sieben Tage im
Viertelstundenraster. Eine Aufgabe ins Raster ziehen legt einen Zeitblock
an und schreibt ihn in den Kalender; ein Block lässt sich am Griff
verschieben, das Kreuz plant ihn wieder aus.

Die Dauer eines neuen Blocks kommt aus dem geschätzten Aufwand der
Aufgabe — wer schätzt, bekommt die passende Größe geschenkt; ohne
Schätzung sind es 60 Minuten.

Fremde Termine liegen grau im Hintergrund und lassen sich hier nicht
anfassen. Sie gehören dem Kalender, nicht dem Planer.

Zwei Details:
- Ziehen beginnt erst nach acht Pixeln Bewegung. Sonst löst jeder Klick
  einen Zug aus und die Knöpfe im Block wären nicht mehr bedienbar.
- Ein Zeitblock steht auch im Terminspiegel; er würde sonst doppelt
  erscheinen, einmal als eigene Planung und einmal als fremder Termin.
  Die Abfrage filtert ihn heraus.

Der Wochenversatz steht in der Adresse, damit eine bestimmte Woche als
Lesezeichen taugt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 22:37:53 +02:00
Florian WildeandClaude Opus 5 bcb4b8eb02 feat(woche): Rasterlogik für die Wochenansicht
Aus Zeitpunkt, Stundenfenster und Blöcken entsteht alles, was die Ansicht
braucht: Spalten ab Montag, Zeilen im Viertelstundenraster und die Lage
jedes Balkens darin. Reine Logik, 21 Tests.

Drei Feinheiten, die ohne Test leicht falsch geraten:
- Das Stundenfenster ist die Hülle über alle Arbeitsfenster, mit einer
  Stunde Luft. Ohne die Luft läge ein Block, der genau am Rand beginnt, an
  der Kante und ließe sich kaum greifen.
- Balken werden am Fensterrand abgeschnitten. Ein Termin von 6 bis 23 Uhr
  würde sonst das Raster sprengen.
- Sich überschneidende Termine bekommen Spuren nebeneinander, sonst läge
  einer unsichtbar hinter dem anderen. Eine Spur wird wieder frei, sobald
  sie zu Ende ist.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 22:37:52 +02:00
Florian WildeandClaude Opus 5 9c1c916b58 feat(zeitbloecke): Schreibweg in den Kalender und Rückweg zurück
Anlegen, verschieben und lösen schreiben in den Kalender des Anbieters.
Jeder Zugriff auf einen bestehenden Termin geht mit If-Match und dem
zuletzt gelesenen Etag heraus; antwortet der Anbieter mit 412, gewinnt der
Kalender: der Planer schreibt nicht, verwirft seinen Fingerabdruck und
überlässt dem nächsten Abgleich, den fremden Stand zu holen.

Der Rückweg läuft nach jedem Abgleich und vergleicht die eigenen
Zeitblöcke mit dem aufgefrischten Spiegel. Wurde im Kalender verschoben,
zieht der Planer nach. Wurde dort gelöscht, verliert der Zeitblock seine
Verankerung und die Aufgabe fällt zurück auf offen, wenn nichts anderes
mehr für sie geplant ist.

Zwei Entscheidungen zum Fehlerfall:
- Scheitert das Schreiben beim Anlegen, bleibt der Zeitblock trotzdem
  bestehen, mit vermerktem Fehler. Ihn zu verwerfen hieße, die Planung
  wegen eines Netzproblems zu vergessen; das Verschieben trägt ihn nach.
- Scheitert das Entfernen, verschwindet der Zeitblock dennoch. Bliebe er
  stehen, weil der Kalender gerade nicht erreichbar ist, wäre die Planung
  blockiert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 20:49:52 +02:00
Florian WildeandClaude Opus 5 5791047982 feat(zeitbloecke): Datenmodell und Schleifenschutz
Ein Zeitblock ist eine Aufgabe, die zu einer bestimmten Zeit in einem
Kalender steht. Er lebt an zwei Orten — hier und beim Anbieter — und beide
Seiten dürfen ihn verschieben. Daraus entsteht die Gefahr einer
Endlosschleife: Die App schreibt, der Abgleich liest die eigene Änderung,
hält sie für fremd, schreibt zurück, der Abgleich liest wieder.

Der Ausweg ist ein Fingerabdruck über den Zeitraum, sha256(startISO|endeISO).
Beim Schreiben merkt sich der Zeitblock, was er in den Kalender gelegt hat;
kommt derselbe Fingerabdruck zurück, war es die eigene Änderung. Weicht er
ab, hat jemand anderes verschoben — und dann gewinnt der Kalender.

Bewusst nur über Beginn und Ende: Titel und Beschreibung ändern sich auch
aus harmlosen Gründen, und für die Planung zählt allein, wann der Block
liegt.

30 Tests, darunter zwei, die den eigentlichen Zweck prüfen: dass es sich
über mehrere Runden nicht hochschaukelt und nach einer übernommenen
fremden Verschiebung wieder zur Ruhe kommt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 20:49:52 +02:00
Florian WildeandClaude Opus 5 bd9df22a18 Merge: Meilenstein 3 – Kalender lesen (0.3.0)
Kalenderkonten für Microsoft 365 und Google verbinden, Termine örtlich
spiegeln und die Belegung des Tages gegen die Tageskapazität rechnen.
Tokens liegen verschlüsselt in der Datenbank.

Geprüft vor dem Merge: npm run pruefen mit 199 Unit-Tests, 40
Integrationsprüfungen, Migrationstest und 14 E2E-Tests. Der Abgleich ist
gegen anonymisierte Beispielantworten geprüft, nicht gegen die echten
APIs — dafür fehlen die Zugangsdaten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 20:42:00 +02:00
Florian WildeandClaude Opus 5 dad2db929b chore(release): 0.3.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 20:41:59 +02:00
Florian WildeandClaude Opus 5 e4b08b4c30 docs: DEVLOG-Eintrag für M3
Ziel, Erledigtes, die Entscheidungen mit Begründung, die Probleme samt
Lösung und die offenen Punkte — allen voran, dass der Abgleich nicht gegen
die echten APIs getestet werden konnte.

Dauer laut zeiten.csv: 41 Minuten. Gesamt bislang 246 Minuten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:34:51 +02:00
Florian WildeandClaude Opus 5 7e11ef736c test: Wartepunkt nach dem Abhaken ergänzen
Der Test navigierte direkt nach dem Klick weiter, ohne das Ende der Server
Action abzuwarten. In M2 war die Seite schnell genug, mit der zusätzlichen
Belegungsabfrage aus M3 nicht mehr — ein Zeitproblem im Test, kein
Produktfehler. Er wartet jetzt auf die Bestätigung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:34:51 +02:00
Florian WildeandClaude Opus 5 80f86b4cff docs: Handbuchbilder für M3 neu aufnehmen
40 Bilder aus der Wegwerf-Instanz: zehn Seiten, je hell und dunkel,
Schreibtisch und Handy. Neu dabei die Kalenderkonten und die Belegung des
Tages auf der Startseite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:34:51 +02:00
Florian WildeandClaude Opus 5 a8b2ff0235 docs: Handbuchseite und Einrichtung der Kalenderanbieter
Die Handbuchseite erklärt, was der Planer liest und was ausdrücklich nicht,
warum alle Kalender gegen jeden Bereich zählen und was beim Trennen mit den
Terminen passiert.

Die README bekommt eine Schritt-für-Schritt-Anleitung für Entra ID und die
Google Cloud Console — mit den genauen Rückleit-URIs und Berechtigungen.
Dazu der Hinweis, dass ein Google-Projekt im Status "Test" die
Aktualisierungstoken nach sieben Tagen verfallen lässt; das kostet sonst
einen halben Abend Fehlersuche.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:12:05 +02:00
Florian WildeandClaude Opus 5 df50627a20 test: Verschlüsselung und Kalendermodelle absichern
Sieben Tests für die Tokenverschlüsselung: Rückweg, Umlaute, verschiedene
Chiffren bei gleichem Klartext, und dass eine Veränderung am Chiffrat null
ergibt statt Unsinn.

Der Integrationstest deckt jetzt auch ab, dass dasselbe Konto mit
verschiedenen Kalendern erlaubt ist, ein Upsert den Termin aktualisiert
statt zu doppeln, und ein Bereich das Löschen seines Standard-Kalenderkontos
überlebt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:12:05 +02:00
Florian WildeandClaude Opus 5 d8d3227c82 feat(kalender): Belegung des Tages auf der Startseite
Jeder Bereich zeigt, wie voll der heutige Tag schon ist: Termine aus allen
verbundenen Kalendern gegen die Tageskapazität dieses Bereichs.

Alle Kalender zählen gegen jeden Bereich, weil ein Termin die Zeit ohnehin
blockiert, gleich zu welchem Lebensbereich er gehört. Wer um 14 Uhr beim
Zahnarzt sitzt, kann zur selben Zeit nichts für die Firma tun.

Ohne verbundenen Kalender bleibt der Balken weg. Er wäre sonst immer bei
null und sähe nach "alles frei" aus — eine Zahl, die lügt, ist schlechter
als keine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:12:04 +02:00
Florian WildeandClaude Opus 5 42c98c8b29 feat(kalender): Hintergrundjobs für Abgleich und Aufräumen
Alle 15 Minuten abgleichen, nachts alte Termine und abgelaufene
WebAuthn-Herausforderungen entfernen. Ohne das Aufräumen bliebe der
Terminspiegel für immer stehen.

Der Riegel über globalThis ist nötig, weil Next im Entwicklungsbetrieb
Module mehrfach lädt — sonst liefen nach ein paar Änderungen ein Dutzend
Zeitpläne parallel. Im Demo-Modus starten gar keine Jobs: die
Screenshot-Instanz soll sich nicht selbst verändern, während sie
abfotografiert wird.

Protokolliert werden nur Zahlen, nie Kontonamen oder Termininhalte.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:05:07 +02:00
Florian WildeandClaude Opus 5 0c67b73a62 feat(kalender): Seite für die Kalenderkonten
Verbinden, umbenennen, von Hand abgleichen und trennen. Alle Aktionen
arbeiten nur auf Konten des angemeldeten Benutzers — die Kennung geht in
die where-Bedingung, nicht in einen nachträglichen Vergleich.

Ein Anbieter erscheint nur, wenn die Instanz ihn erlaubt *und* er
konfiguriert ist. Ist er erlaubt, aber nicht konfiguriert, sagt die Seite
das ausdrücklich, statt den Knopf wortlos wegzulassen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:05:07 +02:00
Florian WildeandClaude Opus 5 9946e7c308 feat(kalender): OAuth und Abgleich für Graph und Google
Verbinden, Token erneuern und seitenweise abrufen — mit blankem fetch,
ohne SDK. 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.

Entscheidungen:
- Der Zustandswert im OAuth-Ablauf ist mit AUTH_SECRET signiert und trägt
  die Benutzerkennung. Ohne das ließe sich ein fremdes Kalenderkonto an
  ein beliebiges Konto hängen.
- Die Berechtigungen schließen das Schreiben ein, obwohl M3 nur liest.
  Sonst müsste ab M4 jedes Konto neu zustimmen — und die Zustimmung ist
  genau der Schritt, an dem Einrichtung scheitert.
- Läuft der Abgleichverweis ab (beide Anbieter antworten mit 410), wird er
  verworfen und von vorn gelesen. Der Ablauf ist so gebaut, dass ein
  vollständiger Durchlauf jederzeit möglich ist.
- Googles syncToken trägt die Zeitgrenzen nicht mit; Folgeabgleiche
  liefern also auch Änderungen außerhalb unseres Fensters. Was daneben
  liegt, wird verworfen — sonst wüchse der Spiegel unbegrenzt.

Die Instanzregel greift serverseitig: Google gibt es nur daheim, in der
Firmeninstanz liefert die Route 404 statt eines Hinweises.

Fehlermeldungen nennen nie den Antwortkörper des Token-Endpunkts — der
enthält das Client-Secret oft im Echo.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:05:06 +02:00
Florian WildeandClaude Opus 5 6fda2a579e feat(kalender): Antworten von Graph und Google lesen, Belegung rechnen
Reine Logik ohne fetch: die Antworten beider Anbieter werden mit zod
geprüft und in eine gemeinsame Form gebracht. Geprüft wird gegen
anonymisierte Beispielantworten in test/fixtures — 61 Tests.

Zwei Stellen, an denen bewusst abgebrochen statt geraten wird:
- Graph liefert Datum und Zeitzone getrennt, ohne Versatz im Text. Wir
  fragen mit Prefer: outlook.timezone="UTC" an; kommt etwas anderes
  zurück, wäre jede Umrechnung geraten und würde Termine um Stunden
  verschieben.
- Ein unbekanntes showAs wird UNBEKANNT und zählt damit als belegt. Lieber
  eine Stunde zu viel als eine Doppelbuchung.

Zur Belegung: Ganztägige Termine zählen nicht — ein Urlaubstag oder
Geburtstag im Kalender blockiert sonst den ganzen Tag, obwohl er keine
Arbeitszeit kostet. Ebenso wenig zählen ausdrücklich als frei markierte
Termine. Überlappende Termine werden zusammengelegt, damit eine
Doppelbuchung nicht doppelt zählt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:04:41 +02:00
Florian WildeandClaude Opus 5 79e6c0d556 feat(kalender): Datenmodell für Kalenderkonten und Termine
Kalenderkonto mit Tokens und Abgleichverweis, Termin als örtlicher Spiegel
mit genau so viel, wie die Belegungsrechnung und die Wochenansicht
brauchen. Bereich bekommt sein Standard-Kalenderkonto für M4.

Die Tokens liegen verschlüsselt (AES-256-GCM). Wer die Datenbank oder eine
Sicherung in die Hände bekommt, hätte sonst direkten Zugriff auf fremde
Kalender — das wiegt schwerer als bei den OIDC-Tokens aus M1, die nur die
Anmeldung betreffen. Der Schlüssel wird aus AUTH_SECRET abgeleitet statt
separat konfiguriert: eine weitere Pflichtvariable wäre eine weitere
Stelle, an der die Einrichtung scheitert.

kalenderId ist nicht optional, sondern trägt den Wert "standard" für den
Standardkalender — Prisma lässt in einem zusammengesetzten Unique-Schlüssel
kein null zu.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 18:04:41 +02:00
Florian WildeandClaude Opus 5 a6bcaf8982 Merge: Meilenstein 2 – Aufgaben und Projekte (0.2.0)
Schnellerfassung, Inbox mit Einsortieren, Aufgabenliste mit Filtern in der
Adresszeile, Ideen als eigene Ansicht und Projekte mit Fortschritt.

Geprüft vor dem Merge: npm run pruefen mit 131 Unit-Tests, 30
Integrationsprüfungen, Migrationstest und 14 E2E-Tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 17:47:43 +02:00
Florian WildeandClaude Opus 5 04414af00d chore(release): 0.2.0
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 17:47:33 +02:00
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
Florian WildeandClaude Opus 5 881fdf0819 test: Selektor der Anmeldung präzisieren
Die Startseite hat seit M2 zusätzlich einen Link "Bereiche verwalten";
der bisherige Selektor traf damit zwei Elemente. Kein Produktfehler,
aber der Test muss eindeutig sein.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:46:13 +02:00
Florian WildeandClaude Opus 5 36370f8ff8 docs: Handbuchbilder für M2 neu aufnehmen
36 Bilder aus der Wegwerf-Instanz: neun Seiten, je hell und dunkel,
Schreibtisch und Handy. Ausschließlich erfundene Daten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:46:13 +02:00
Florian WildeandClaude Opus 5 d01c49b5a0 docs: Handbuchseiten zu Aufgaben, Ideen und Projekten
Jede Seite erklärt nicht nur, wo man klickt, sondern warum es so gebaut
ist: warum die Schnellerfassung nur ein Feld hat, warum die Inbox kein
eigener Zustand ist, warum Ideen getrennt liegen und warum Verworfenes
nicht in den Fortschritt zählt.

README und Schnellstart auf den Stand von M2 gebracht; der Schnellstart
endet jetzt mit der ersten erfassten Aufgabe.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:33:34 +02:00
Florian WildeandClaude Opus 5 4d0f83ff51 chore: Demo-Daten für die neuen Seiten erweitern
Erfundene Aufgaben mit einer Mischung aus überfällig, heute, später und
ohne Termin, dazu Inbox, Ideen und ein Projekt mit Arbeitspaketen und
einem Meilenstein. Die Fälligkeiten stehen als Versatz zum festen
Demo-Zeitpunkt, damit die Bilder zwischen zwei Läufen gleich bleiben.

Eine Aufgabe trägt eine erfundene Zammad-Herkunft, damit die Darstellung
der Quelle in den Bildern zu sehen ist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:33:34 +02:00
Florian WildeandClaude Opus 5 144457b9f0 fix(aufgaben): Ideen aus Aufgabenliste und Inbox heraushalten
Der Fehler fiel erst in den aufgenommenen Bildern auf: Ideen standen
mitten in der Aufgabenliste, obwohl der ganze Sinn der getrennten
Ideenseite ist, dass sie das nicht tun. Ohne Termin hätten sie dort
entweder unsichtbar unten gestanden oder mit erfundenem Termin oben.

Der Filter kennt jetzt ohneIdeen; ein ausdrücklich gewählter Typ gewinnt,
wer nach Ideen filtert, sieht sie also weiterhin.

Die Startseite filterte Ideen bisher von Hand nach dem Laden — das
erledigt jetzt dieselbe Abfrage wie überall sonst.

Ein E2E-Test hält den Fall fest.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:33:33 +02:00
Florian WildeandClaude Opus 5 7c8fe10f35 test: Erfassungsweg und Eindeutigkeit der Quellen absichern
E2E über den Weg, der in M2 zählt: schnell erfassen, einsortieren,
abhaken — und nachsehen, dass Erledigtes aus der Standardansicht fällt,
aber mit Filter wieder auftaucht.

Der Integrationstest deckt jetzt auch ab, was die Integrationen ab M6
brauchen: dieselbe Quelle mit derselben Kennung nur einmal, dieselbe
Kennung bei anderer Quelle erlaubt, mehrere Aufgaben ohne Quellverweis
nebeneinander, und ein Upsert aktualisiert statt zu doppeln.

Dazu der Nachweis, dass Aufgaben das Löschen ihres Projekts und ihres
Bereichs überleben und dabei nur die Zuordnung verlieren.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:17:45 +02:00
Florian WildeandClaude Opus 5 a4a889fcb8 feat(aufgaben): Inbox, Aufgabenliste, Ideen und Projekte
Die Schnellerfassung braucht ein Feld und einen Knopf; wer beim Erfassen
erst ein Formular ausfüllen muss, erfasst nichts. Der Fokus bleibt danach
im Feld, damit mehrere Sachen hintereinander hineingehen.

Die Filter der Aufgabenliste stehen in der Adresse, nicht im Zustand der
Komponente — so lässt sich eine gefilterte Liste als Lesezeichen ablegen,
und der Zurück-Knopf tut, was er soll.

Die Startseite zeigt jetzt, was überfällig, heute oder morgen fällig ist,
statt nur die Bereiche aufzuzählen.

Für die Auswahlfelder bewusst das native <select>: es bedient sich auf
dem Handy mit dem Systemdialog und ist ohne Zusatzcode tastaturbedienbar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:17:44 +02:00
Florian WildeandClaude Opus 5 b3ca1c9ce7 feat(aufgaben): Server Actions und Abfrageschicht
Anlegen, ändern, Status setzen, einsortieren, aus einer Idee eine Aufgabe
machen und löschen — jede Aktion prüft die Rolle selbst und hinterlässt
einen Audit-Eintrag.

Statuswechsel gehen nur die Wege, die uebergangErlaubt() zulässt:
Abgeschlossenes wird erst wieder geöffnet, bevor es weitergeht. Wer per
Formular etwas anderes einreicht, bekommt eine Meldung statt eines stillen
Zustandssprungs.

Sortiert wird in der Anwendung, nicht in SQL. Die Regel ist mehrstufig
und steht als geprüfte reine Funktion in src/lib — zwei Fassungen
derselben Regel wären eine Fehlerquelle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:17:43 +02:00
Florian WildeandClaude Opus 5 63c3c15a3c feat(aufgaben): Datenmodell und Regeln für Aufgaben und Projekte
Aufgabe mit Typ, Status, Priorität, Aufwand, Fälligkeit und Herkunft;
Projekt als eigene Klammer darum.

Zwei Entwurfsentscheidungen:
- Die Inbox ist kein eigener Zustand, sondern schlicht eine Aufgabe ohne
  Bereich. Einsortieren heißt: einen Bereich setzen. Ein zusätzliches
  Feld könnte mit der Zuordnung auseinanderlaufen.
- Projekt ist ein eigenes Modell, kein Aufgabentyp. Ein Projekt hat
  selbst weder Aufwand noch Fälligkeit, sondern leitet beides aus dem
  ab, was drinsteckt. ARBEITSPAKET und MEILENSTEIN sind Typen der
  Aufgaben *innerhalb* eines Projekts.

@@unique([quelle, quelleRef]) ist die Grundlage für die Integrationen ab
M6: sie upserten darüber, statt zu doppeln. Von Hand erfasste Aufgaben
tragen quelleRef = null; PostgreSQL sieht NULL-Werte als verschieden an,
sie behindern sich also nicht.

Verworfene Aufgaben zählen bewusst gar nicht in den Fortschritt eines
Projekts — sonst sähe es fertig aus, weil die Hälfte gestrichen wurde.

Fälligkeit wird in Kalendertagen in Europe/Berlin gerechnet, nicht in
24-Stunden-Schritten: was heute um 23:00 fällig ist, ist heute fällig.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 16:17:41 +02:00
Florian WildeandClaude Opus 5 704c490368 Merge: ROADMAP-Status für M1 nachziehen
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 15:54:31 +02:00
Florian WildeandClaude Opus 5 01ab74f71d docs: M1 in der ROADMAP als erledigt führen
Der Status stand noch auf "in Arbeit", obwohl 0.1.0 getaggt und nach main
gemergt ist.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 15:54:31 +02:00
Florian WildeandClaude Opus 5 56e07a40e8 Merge: Meilenstein 1 – Fundament (0.1.0)
Anmeldung über OIDC, Passkeys und Passwort, Rollen mit serverseitiger
Prüfung in Seite und Server Action, Audit-Protokoll, Instanzsteuerung,
Bereiche, Betrieb über Docker Compose sowie die vollständige Test- und
Dokumentationskette.

Geprüft vor dem Merge: npm run pruefen, 19 Integrationsprüfungen,
Migrationstest und 9 E2E-Tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 15:52:14 +02:00
Florian WildeandClaude Opus 5 8ab4b97655 chore(release): 0.1.0
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 15:51:52 +02:00
Florian WildeandClaude Opus 5 b4d8c19ce4 docs: DEVLOG-Eintrag für M1
Ziel, Erledigtes, die Entscheidungen mit Begründung, die Probleme samt
Lösung und die offenen Punkte. Dazu die eigenen Fehler dieser Sitzung —
die Commits auf main statt auf dem Meilenstein-Zweig, der vermischte
Doku-Commit und das ungeprüfte Skript — damit sie nicht wiederkehren.

Dauer laut zeiten.csv: 161 Minuten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:48:55 +02:00
Florian WildeandClaude Opus 5 688d9f59a8 docs: Handbuchbilder aufnehmen
20 Bilder aus der Wegwerf-Instanz: fünf Seiten, je hell und dunkel,
Schreibtisch und Handy. Ausschließlich erfundene Daten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:46:16 +02:00
Florian WildeandClaude Opus 5 6d04bc2c1c fix(oberflaeche): Kopfleiste einzeilig halten, Demo-Uhr feststellen
Zwei Mängel, die erst in den aufgenommenen Bildern auffielen.

Auf Handybreite brach die Kopfleiste um und drängte den Namen der
Anwendung in die zweite Zeile, mitten zwischen die Menüpunkte. Sie bleibt
jetzt auf allen Breiten einzeilig; wird es eng, scrollt die Navigation
waagerecht.

Das Tagesdatum auf der Startseite wird serverseitig gerendert, weshalb
Playwrights Uhr nicht griff — die Bilder hätten sich täglich geändert. In
der Screenshot-Instanz (DEMO_MODUS=true) steht die Uhr jetzt fest. Reine
Funktionen bekommen den Zeitpunkt weiterhin als Parameter; src/server/
zeitgeber.ts ist die eine Stelle, an der er entsteht.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:36:35 +02:00
Florian WildeandClaude Opus 5 cf28ec19f5 test: Rollentrennung end-to-end absichern
Legt einen Benutzer ohne Verwaltungsrechte an und prüft, dass der direkte
Aufruf der Protokollseite auf den Hinweis führt — nicht nur, dass der
Menüpunkt fehlt. Sich darauf zu verlassen, dass die Oberfläche Knöpfe
ausblendet, wäre kein Nachweis.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:32:52 +02:00
Florian WildeandClaude Opus 5 a88273117c chore: Screenshot-Kette und Demo-Seed
npm run screenshots startet eine Wegwerf-Instanz in einem eigenen
Compose-Projekt mit eigener Datenbank, spielt ausschließlich erfundene
Daten ein, fährt die Seiten mit Playwright ab und räumt am Ende alles
wieder weg. Je Seite entstehen vier Bilder: hell und dunkel, Schreibtisch
und Handy. Die Uhr steht fest, damit sich zwischen zwei Läufen nur das
ändert, was sich wirklich geändert hat.

Der Demo-Seed verweigert den Dienst ohne DEMO_MODUS=true — er würde sonst
erfundene Daten in eine echte Datenbank schreiben.

Test- und Werkzeugordner sind jetzt aus dem Docker-Kontext ausgenommen.
Ein Typfehler in scripts/screenshots.ts hatte den Bau des Abbilds
blockiert, obwohl die Datei dort nichts zu suchen hat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:29:52 +02:00
Florian WildeandClaude Opus 5 e3390c88f4 refactor: middleware in proxy umbenennen
Next 16 hat die Dateikonvention "middleware" abgekündigt und in "proxy"
umbenannt; der Bau warnte bei jedem Lauf. Umbenannt sind Datei und
Funktion, die Aufgabe bleibt dieselbe.

Die E2E-Tests laufen unverändert durch: die Umleitung samt gemerktem Ziel
funktioniert weiter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:29:52 +02:00
Florian WildeandClaude Opus 5 56b5356708 docs: README, Schnellstart, Handbuch, Fahrplan und CHANGELOG
README mit Funktionsumfang, Bildergalerie, allen Umgebungsvariablen als
Tabelle und dem Betrieb inklusive Sicherung und Wiederherstellung.

Der Schnellstart führt in acht Schritten von git clone zur ersten
Anmeldung, jeder Schritt mit erwartetem Ergebnis. Er endet mit dem
Hinterlegen eines Passkeys statt beim ersten Kalenderkonto oder der
ersten Aufgabe — beides gibt es erst mit M3 und M2. Das ist in ROADMAP
und Handbuch so vermerkt, damit niemand danach sucht.

Handbuch mit Seiten zu Anmelden, Bereichen und Protokoll.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:29:51 +02:00
Florian WildeandClaude Opus 5 f91d015cc4 feat(betrieb): Abbild, Compose-Stapel und Betriebsskripte
Dreistufiges Dockerfile mit standalone-Ausgabe; im Laufzeitabbild liegen
weder Quelltext noch Bauwerkzeuge, und es läuft unter einem eigenen
Benutzer.

Die Migrationen erledigt ein eigener Einmaldienst aus der Bau-Stufe. Der
Versuch, nur die Prisma-CLI ins Laufzeitabbild zu kopieren, scheiterte an
ihren transitiven Modulen; sie alle mitzunehmen hätte das Abbild
aufgebläht. Der Dienst läuft vor der App und muss sauber durchlaufen.

Zwei Stellen mussten faul werden, weil Next beim Bauen Seitendaten
sammelt und dabei Module lädt, die es zur Bauzeit noch ohne
Umgebungsvariablen gibt:
- Der Prisma-Client entsteht erst beim ersten Zugriff, über einen Proxy.
- Die Auth.js-Konfiguration wird als Funktion übergeben.
Ebenso liest prisma.config.ts die URL über process.env statt über env(),
das beim Bauen ohne Datenbank abbricht.

Dazu backup.sh, update.sh mit Rückrollpfad und scripts/release.sh. Der
Dump wurde nachweislich in eine Wegwerf-Datenbank zurückgespielt und
stimmt zeilengenau mit dem Original überein. Alle Skripte sind
shellcheck-sauber.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 14:08:04 +02:00
Florian WildeandClaude Opus 5 126ad93301 test: Integrations-, Migrations- und E2E-Tests ergänzen
Der Integrationstest prüft gegen eine echte Datenbank, was Vitest nicht
sieht: Eindeutigkeit, Fremdschlüssel, Kaskaden und dass das Protokoll den
gelöschten Benutzer überlebt. Er läuft bewusst ohne umschließende
Transaktion — er prüft absichtlich Fälle, die scheitern müssen, und ein
gescheiterter Befehl bricht in PostgreSQL die ganze Transaktion ab.
Stattdessen tragen alle Testdaten dieselbe Kennung und werden am Ende
wieder entfernt.

Der Migrationstest legt eine Wegwerf-Datenbank an, spielt alle
Migrationen ein und vergleicht das Ergebnis mit dem Schema. Dass er
Abweichungen wirklich bemerkt, wurde mit einem probeweise ergänzten Feld
nachgewiesen.

Der E2E-Rauchtest deckt den Weg ab, der in M1 trägt: Umleitung ohne
Anmeldung samt gemerktem Ziel, Abweisung falscher Zugangsdaten,
erfolgreiche Anmeldung und die Verwaltungsseite.

Dafür neu: eine Middleware, die nur den angefragten Pfad als Kopfzeile
durchreicht. Ein Layout kennt seinen Pfad nicht, braucht ihn aber für die
Rückkehr nach der Anmeldung. Anmeldelogik steht dort bewusst nicht — die
Middleware läuft im Edge-Umfeld ohne Prisma.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 13:10:54 +02:00
Florian WildeandClaude Opus 5 24a799d6ba chore: tsconfig-Anpassungen von Next 16 übernehmen
Next setzt beim Bauen jsx auf react-jsx und ergänzt seinen Typpfad. Wird
das zurückgedreht, schreibt es der nächste Build wieder hin.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 13:03:06 +02:00
Florian WildeandClaude Opus 5 1876ae4c6b feat(betrieb): Health-Endpunkt und Seed für eine frische Datenbank
/api/health meldet Status, Version, Instanz und Erreichbarkeit der
Datenbank – ohne Anmeldung, deshalb ohne Zählstände, Namen oder Adressen.
update.sh wird sich später darauf stützen.

Der Seed legt die vier Bereiche an und ein erstes Verwaltungskonto aus
ADMIN_EMAIL und ADMIN_PASSWORT. Er läuft mehrfach ohne Schaden; ohne
ADMIN_PASSWORT entsteht ein Konto, das sich nur über OIDC oder Passkey
anmelden kann.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 13:02:49 +02:00
Florian WildeandClaude Opus 5 e9a2b4b463 feat(bereiche): Bereiche verwalten mit Rollenprüfung und Audit
Bereiche mit Farbe, Arbeitsfenster und Tageskapazität – die erste
schreibende Funktion und damit der Beleg, dass die Kette trägt: zod prüft
jede Eingabe, die Rolle wird in der Seite und noch einmal in der Server
Action geprüft, jede Änderung hinterlässt einen Audit-Eintrag.

Die Audit-Details werden vor dem Speichern gesäubert: Felder, deren Name
nach Passwort, Token, Betreff oder Mailinhalt aussieht, werden ersetzt.
Das gilt auch in verschachtelten Objekten und Listen.

Dazu die Protokollseite für die Verwaltung und die Startseite.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 13:02:49 +02:00
Florian WildeandClaude Opus 5 cc013fd6a1 feat(anmeldung): OIDC, Passkeys und Passwort mit Auth.js v5
Drei Wege hinein, je Instanz zuschaltbar: generischer OIDC-Anbieter
(Entra ID beziehungsweise authentik), Passkeys und wahlweise E-Mail mit
Passwort.

Entscheidungen:
- Passkeys laufen direkt über SimpleWebAuthn 14, nicht über den
  experimentellen WebAuthn-Provider von Auth.js, der noch auf Fassung 9
  festgelegt ist.
- Eigener Prisma-Adapter, weil der mitgelieferte fest auf prisma.user und
  prisma.account zugreift, unsere Modelle aber Benutzer und Konto heißen.
- Sitzungen als JWT, weil Credentials-Provider das verlangen. Damit eine
  Sperre trotzdem sofort wirkt, liest der session-Callback Rolle und
  Status bei jeder Anfrage frisch aus der Datenbank. Die Tabellen sitzung
  und verifizierungs_token entfallen deshalb wieder.
- allowDangerousEmailAccountLinking ist bewusst an: je Instanz gibt es
  genau einen vertrauenswürdigen Anbieter, der die Adresse selbst prüft.

Anmeldung, Passwort und Passkey antworten bei Fehlschlag immer gleich,
damit sich nicht abfragen lässt, wer hier ein Konto hat. Die
WebAuthn-Herausforderung wird nach einem Versuch verworfen.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 13:02:34 +02:00
Florian WildeandClaude Opus 5 4ff67c15c2 feat(oberflaeche): Grundlayout, Navigation und Farbschema
Deutsche Oberfläche, Hell und Dunkel über next-themes, Navigation mit
Rollenfilter und die shadcn-Primitiven, die M1 braucht.

Der Farbschema-Knopf kommt ohne Effekt und ohne Zustand aus: welches
Symbol gilt, entscheidet die dark-Klasse und damit CSS. next-themes setzt
sie schon vor der Hydratation, deshalb blitzt nichts auf — und die
React-Compiler-Regel gegen setState im Effekt bleibt eingehalten.

Farben als OKLCH, damit Hell und Dunkel dieselbe wahrgenommene Helligkeit
halten.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 13:02:34 +02:00
Florian WildeandClaude Opus 5 19ca0826e6 feat(zeit): Zeitrechnung und Anzeigeformate in Europe/Berlin
Tagesgrenzen, Wochenbeginn am Montag und Wochentagsindex passend zu den
Kapazitätsfeldern eines Bereichs. Alle Funktionen bekommen den Zeitpunkt
als Parameter, lesen also nie selbst die Uhr.

Die Tests laufen bewusst in UTC, damit sofort auffällt, wenn irgendwo die
Ortszeit des Servers durchschlägt; sie decken beide Zeitzonen-Halbjahre
und die Umstellungswoche ab.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 12:41:30 +02:00
Florian WildeandClaude Opus 5 e2b00f4a57 feat(instanz): Instanzsteuerung mit serverseitigem Riegel
INSTANZ entscheidet, welche Kalenderanbieter, Postfächer und
Integrationen es gibt. Was eine Instanz nicht darf, liefert dort 404
statt eines Hinweises. Ein falsch geschriebener Wert bricht ab, statt
still auf einen Standard zu fallen.

Die Konfiguration wird mit zod geprüft; Fehlermeldungen nennen den
Variablennamen, nie den Wert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 12:41:29 +02:00
Florian WildeandClaude Opus 5 748d032f70 feat(datenbank): Datenmodell für Anmeldung, Bereiche und Audit
Benutzer mit Rolle, OIDC-Konten, Sitzungen, Passkeys samt kurzlebiger
WebAuthn-Herausforderung, Bereiche mit Tageskapazität und Arbeitsfenster
sowie ein unveränderlicher Audit-Eintrag je schreibender Aktion.

Aufgabe und Zeitblock kommen mit M2 und M4, Kalenderkonto mit M3 — dann
mit den Feldern, die der jeweilige Provider wirklich braucht.

Prisma 7 nimmt die Verbindung nicht mehr aus dem Schema entgegen: die URL
steht in prisma.config.ts, der Client bekommt den pg-Treiberadapter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 12:41:29 +02:00
Florian WildeandClaude Opus 5 de1f923767 chore: Projektgerüst und Werkzeugkette einrichten
Next 16 mit App Router und standalone-Ausgabe, React 19, TypeScript strict,
Tailwind 4, Vitest und Playwright. Alle Versionen exakt gepinnt.

Abweichungen vom ursprünglich notierten Stand, jeweils begründet:
- Prisma 7.10.0 statt 6, weil 6 überholt ist und 8 nur als RC vorliegt.
- TypeScript 6.0.3 statt 7, weil typescript-eslint 8 nur "<6.1.0" trägt.
- ESLint 9.39.5 statt 10, weil das in eslint-config-next gebündelte
  eslint-plugin-react noch context.getFilename() benutzt.
- npm-overrides für SimpleWebAuthn und nodemailer, weil next-auth dort
  ältere optionale Peers deklariert. Die betroffenen next-auth-Provider
  werden nicht benutzt: Passkeys laufen direkt über SimpleWebAuthn 14.

"npm run pruefen" bündelt Prettier, ESLint, tsc, Vitest und ShellCheck.
ShellCheck läuft über das Docker-Abbild, wenn es lokal fehlt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 12:41:28 +02:00
Florian Wilde 8220adc449 chore: Repository anlegen 2026-09-10 12:40:52 +02:00