Fünf Wünsche aus dem Durchtesten:
1. Wiederholungen einstellbar (täglich/wöchentlich/monatlich/jährlich,
alle N, Ende nie/nach Anzahl/an einem Datum). Der eigentliche Aufwand
lag nicht im Formular, sondern im Aufklappen: eine wöchentliche Reihe
erschien sonst als *ein* Eintrag. Der Server klappt sie jetzt im
angefragten Zeitfenster auf (?from=&to=, rrule-go über go-icals
RecurrenceSet), die Ansichten fragen ihren sichtbaren Ausschnitt an.
Geprüft: 5 Termine wöchentlich, nach Umstellung auf 14-tägig korrekt
neu gerechnet, Fenster einer Woche liefert genau einen.
Regeln, die das Formular nicht abbildet (etwa jeder zweite Dienstag
im Monat), werden weiterhin korrekt *angezeigt* -- geprüft, die vier
Termine stimmen -- aber nicht zum Bearbeiten angeboten (409). Ein
halb verstandenes Speichern würde die Regel sonst still beschneiden.
2. Tag wird erst geöffnet, statt sofort einen Termin anzulegen: Klick
auf einen Tag zeigt dessen Termine als Liste, dort wählt man einen
aus oder legt bewusst einen neuen an.
3. Eigene Farbe je Termin (COLOR nach RFC 7986), leer = Kalenderfarbe.
4. Kalenderliste ohne Kästchen: der Farbpunkt selbst ist der Schalter --
gefüllt heißt sichtbar, leerer Ring ausgeblendet.
5. Ganztägig als Schiebeschalter statt Kästchen -- es stellt das
Formular um, das ist keine Nebenangabe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei gemeldete Fehler, alle nachgestellt:
1. Ein vorhandener Termin ließ sich nicht öffnen. Der Klick auf den
Termin reichte an die Tageszelle darunter durch, die sofort 'Neuer
Termin' öffnete und den Bearbeiten-Dialog überschrieb. Fehlendes
stopPropagation.
2. Klick auf den 3. legte den Termin am 2. an. Ein ganztägiger Termin
ist ein *Datum*, kein Zeitpunkt - die Oberfläche rechnete ihn über
new Date(...).toISOString() trotzdem in UTC um, was östlich von UTC
den Vortag ergibt. Nachgestellt und bestätigt: DTSTART landete als
20260902 statt 20260903. Ganztägige Termine laufen jetzt über
dates.ts (allDayISO/eventDay), damit Dialog und Raster dieselbe
Rechnung benutzen. Nebenbefund mitgefixt: DTEND war gleich DTSTART,
was RFC 5545 verbietet (DTEND ist exklusiv) - ein eintägiger Termin
am 3. schreibt jetzt DTEND 20260904, nach außen bleibt die
Schnittstelle beim letzten belegten Tag.
3. 'Fühlt sich starr an': Wochenansicht ist jetzt ein echtes
Stundenraster (Klick trifft die Stunde statt Mitternacht, ganztägige
in eigener Zeile, überlappende Termine nebeneinander, Jetzt-Linie),
mehrtägige Termine stehen in jeder Zelle statt nur am Starttag, neue
Termine schlagen die nächste volle Stunde bzw. 9 Uhr vor,
Kalenderauswahl im Termindialog, Escape schließt, Hover-Rückmeldung,
Kalenderliste auf schmalen Schirmen einklappbar.
Außerdem: ein neu angelegter Kalender blieb unsichtbar, weil er in
keiner Sichtbar-Liste stand - die Liste merkt sich jetzt die
ausgeblendeten statt der sichtbaren.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Neue Seite CalendarPage (Menüpunkt 'Calendar' in der Seitenleiste,
zwischen Home und Papierkorb) mit Kalenderliste zum Anlegen/Umbenennen/
Löschen/Ein- und Ausblenden, Export/Import als .ics über die
Phase-2-API, und Termine anlegen/ändern/löschen über einen neuen
JSON-Endpunkt.
Backend: neuer events_api.go-Handler unter /api/v1/calendars/{id}/events
(Session-Auth) neben dem bestehenden CalDAV-Protokoll – ein Termin, den
die Web-Oberfläche anlegt, ist ein ganz normales CalDAV-Objekt und
synchronisiert genauso zu DAVx5 wie umgekehrt. Ein per CalDAV mit RRULE
angelegter, wiederkehrender Termin zeigt sich hier nur (recurring:true),
lässt sich aber bewusst nicht über dieses vereinfachte Formular
bearbeiten (409) – das würde die Wiederholungsregel eher beschädigen
als korrekt anpassen.
Gegen den laufenden Container getestet: Kalender/Termin anlegen, per
Web-API ändern und löschen, ein per CalDAV mit RRULE angelegter
Wiederkehrer taucht als recurring:true auf und blockt PATCH mit 409.
TypeScript-Build (tsc + vite build) sauber.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neuer APIHandler unter /api/v1/calendars (Session-Auth, wie der Rest
der API) neben dem bestehenden CalDAV-Handler (App-Passwörter,
/caldav/) – zwei Handler, zwei Auth-Modelle, wie schon bei /dav.
- GET/POST /api/v1/calendars – Liste, Anlegen
- PATCH/DELETE /api/v1/calendars/{id} – Umbenennen, Farbe, Zeitzone,
Löschen (go-webdav kennt PROPPATCH und Kalender-Löschen nicht – dafür
gibt's im CalDAV-Protokoll selbst keinen Weg)
- GET .../export – alle Termine eines Kalenders als eine .ics-Datei,
VTIMEZONE-Blöcke dedupliziert
- POST .../import – .ics mit mehreren VEVENT/VTODO einlesen, jede
Komponente wird ein eigenes Objekt wie bei einem einzelnen CalDAV-PUT
Gegen den laufenden Container getestet (Session-Login, CSRF-Token):
Anlegen, Umbenennen, zwei Termine importieren, sauberer Re-Export,
Löschen, und dass ohne Login 401 statt Daten kommt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neue Spalte cal_calendars.timezone (Default UTC). CalDAV selbst braucht
sie nicht - jeder Termin trägt seine Zeitzone bereits im eigenen .ics,
und die TZID-Auflösung darin läuft schon korrekt über die
System-Zeitzonendatenbank. Gebraucht wird das Feld erst von der
künftigen Web-Oberfläche zum Anlegen neuer Termine (Phase 3) als
Vorgabe, wenn der Nutzer keine eigene Zeitzone wählt - jetzt schon
angelegt, damit dafür keine weitere Migration nötig wird.
Gegen den laufenden Container geprüft: Migration lief durch, Home-Set-
PROPFIND (nutzt die geänderten SELECTs) weiterhin grün.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Manche Clients folgen dem 308-Redirect von /.well-known/caldav mit GET
statt PROPFIND (curl -L reproduziert das exakt), landen dann auf einer
Sammlung (Principal/Home-Set/Kalender) und bekamen dort bisher ein
hartes 404 von GetCalendarObject – ein Client, der so die
Erreichbarkeit prüft, bricht die Einrichtung dann vorzeitig ab, bevor
er je ein PROPFIND schickt. GET/HEAD auf Nicht-.ics-Pfade antworten
jetzt mit einer schlichten 200-Textantwort statt an den CalDAV-Backend
durchgereicht zu werden.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
go-webdav leitet GET/HEAD auf jeden Pfad unverändert an
GetCalendarObject weiter, auch auf Principal- oder Home-Set-Pfade (ein
Browser kann nur GET, kein PROPFIND). Ohne Prüfung hielt
parseCalendarPath dann z.B. das Wort 'caldav' aus dem Pfad für eine
Kalender-ID und gab einen internen UUID-Parsing-Fehler nach außen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neues, eigenständiges Modul internal/calendar: implementiert
caldav.Backend (emersion/go-webdav) gegen zwei neue Tabellen
(cal_calendars, cal_objects). Endpunkt /caldav/ mit eigener Basic Auth
(nur App-Passwörter, wie /dav), plus /.well-known/caldav für die
automatische Einrichtung nach RFC 6764.
Gegen echte Container getestet (nicht nur go build): Discovery-Kette
Principal -> Home-Set -> automatisch angelegter Standardkalender,
PUT/GET/DELETE eines Termins, REPORT mit Zeitraumfilter (Treffer und
Nicht-Treffer), If-Match/If-None-Match-Vorbedingungen (412), und dass
ein fremder Nutzer auf einen Kalender-Pfad mit 404 statt 403 abgewiesen
wird.
Zwei Fallen unterwegs gefunden und dokumentiert:
- go-webdav vergleicht von CurrentUserPrincipal/CalendarHomeSetPath
zurückgegebene Pfade direkt gegen r.URL.Path (inklusive Prefix) und
bestimmt den Ressourcentyp allein an der Segmentzahl dahinter – der
Nutzername beim Principal steht deshalb in einem Segment
(principals-max), nicht als Unterpfad.
- chi kennt REPORT/MKCALENDAR nicht von Haus aus (nur PROPFIND & Co.
waren für /dav bereits registriert) und hätte sie sonst mit 405
abgewiesen, bevor der CalDAV-Handler überhaupt lief.
Migration 000021 nur mit CREATE TABLE IF NOT EXISTS, wie in
kalender-plan.md festgelegt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>