Bisher bekam nur .topbar Safe-Area-Padding, aber .content-wrapper
rechnete weiter starr mit --topbar-h. Im PWA-Standalone-Modus auf
iPhones mit Notch lief der Kalender dadurch oben in die Status-Bar
und unten in den Home-Indicator hinein — die Wochentag-Header und
Tagesnummern der ersten Zeile waren verdeckt, die letzte Zeile
zu kurz.
- .content-wrapper: margin-top und height berechnen jetzt safe-top
und safe-bottom mit ein
- .sidebar (Mobile-Overlay): top startet ebenfalls unterhalb der
vergrösserten Topbar
Version v10 → v11.
- Erstellen-Button in der Sidebar: SVG-Path war asymmetrisch
(M19 13h-6v9...) — Vertikalbalken hing nach unten heraus. Jetzt
Standard-Plus mit gleich langen Armen.
- Service Worker holt index.html und version.js ab sofort per
Network-First — neue Releases greifen damit beim nächsten
Seiten-Reload, ohne dass der SW manuell deregistriert werden muss.
Statics bleiben Cache-First für Offline-Tauglichkeit; auf
Aktivierung wird der alte Cache komplett gelöscht.
Version v9 → v10.
- Titel im Topbar wird auf Mobile auf 2 Zeilen aufgeteilt: Hauptlabel
(z.B. "Mai – Jun") oben, Jahr ("2026") darunter in kleinerer Schrift.
Auf Desktop bleibt es einzeilig durch margin-left auf der Year-Span.
- Event-Popup: 44px-Mindestgröße der Icon-Buttons greift hier nicht
mehr — Buttons bleiben kompakt 32px, weniger Gap, schmaleres Popup
(max 92vw / 340px), sodass das Schließen-X nicht aus dem Rand
herausragt.
- Monatsansicht auf Mobile: Startuhrzeit ("00:00 Lemgo") wird
versteckt, nur der Titel ist sichtbar. Auf Desktop wie bisher mit
Uhrzeit-Präfix. Die Info bleibt im Termin-Popup verfügbar.
Version v8 → v9.
- "Heute"-Button auf Mobile wieder im Topbar-Center sichtbar
(kompakter mit weniger Padding) statt nur im View-Popup.
- Neuer runder Floating-Action-Button unten rechts auf Mobile mit
Plus-Icon, öffnet das "Termin erstellen"-Modal — Google-Calendar-
artige Bedienung.
- Der "Erstellen"-Button in der Sidebar wird auf Mobile ausgeblendet,
weil der FAB ihn ersetzt. Auf Desktop bleibt alles wie bisher.
- iOS-Safe-Area unten respektiert (Home-Indicator).
Version v7 → v8.
Damit lädt beim Swipen durch Monate erst nach ~10 Monaten in beide
Richtungen erneut Daten nach. Vorher reichte der Cache nur ±2 Monate,
sodass nach 2-3 Wischen ein Spinner kam.
- CACHE_BUF 56 → 300 Tage (initial ±10 Monate)
- PREFETCH_EXT 56 → 180 Tage (Verlängerung bei Edge ~6 Monate)
- PREFETCH_EDGE 28 → 90 Tage (Trigger ~3 Monate vor Cache-Rand)
Version v6 → v7.
Wenn aktiviert, bekommt der JWT-Token statt der üblichen 7 Tage eine
Lebensdauer von 180 Tagen. Der Token liegt wie bisher in localStorage,
bleibt also bis zum manuellen Löschen / Cookie-Reset gültig.
- backend/routers/auth_router.py: LoginRequest.remember_me, längere
expires_delta beim Token-Erstellen
- index.html: Checkbox unter dem 2FA-Feld
- api.js: login() reicht remember_me als 4. Parameter durch
- app.js: Wert aus #login-remember lesen und mitschicken
- Version v5 → v6
- View-Switcher auf Mobile in Popup-Menü ausgelagert (neuer Icon-Button
rechts in der Topbar). Dadurch wird in der Topbar Platz frei für
prev/next + Monatstitel ("Mai 2026" usw.).
- Topbar-Settings-Icon auf Mobile ausgeblendet, dafür neuer
"Einstellungen"-Eintrag im User-Dropdown. "Heute" wandert ins
View-Popup.
- KW-Bubble: von oben-links nach unten-links verschoben — überlappt
jetzt nicht mehr die Tagesnummer.
- Termine in der Monatsansicht zeigen wieder ihren Text (kleinere
14px-Höhe, 9px Schrift) statt nur farbiger Punkte.
- Long-Press auf einen Tag öffnet das Kontextmenü "Termin erstellen"
(synthetisches contextmenu-Event nach 500 ms ohne Bewegung). Der
nachfolgende synthetische Click wird unterdrückt.
- Settings-Modal: Sidebar (Darstellung/Konten/Benutzerverwaltung) auf
Mobile als slide-in Overlay mit Hamburger-Toggle. Auf Desktop bleibt
sie immer sichtbar.
- Version v4 → v5 (auch SW-Cache)
- Viewport: maximum-scale=1, user-scalable=no — kein Pinch-Zoom mehr
- Profil-Dropdown öffnet wieder: overflow:hidden auf .topbar-right
in der Mobile-Media-Query entfernt (hatte das absolut positionierte
Dropdown abgeschnitten)
- Long-Press auf Kalenderzellen markiert keinen Text mehr:
user-select/touch-callout/tap-highlight in der ganzen Mobile-UI aus
- Long-Press auf Avatar zeigt nicht "Bild speichern":
-webkit-touch-callout:none + pointer-events:none auf <img>
- Kalenderwochen erscheinen als kleine Bubble oben links in jeder
Zeile statt als eigene 38px-Spalte
- Status-Bar-Overlap im Settings-Modal behoben: safe-area-inset-top
auf .settings-page-header und Modal-Header in der Mobile-Media-Query
- Swipe links/rechts auf #view-container navigiert prev/next
(≥60 px, überwiegend horizontal, < 700 ms)
- Version v3 → v4 (auch SW-Cache)
Macht Calendarr installierbar (Manifest + Service Worker) und auf
Smartphones bedienbar — additive Änderungen, kein Refactoring der
bestehenden Logik, Theme/Variablen unverändert.
PWA:
- frontend/manifest.json (theme #4285f4, bg #0e0e14, name/icons/scope)
- frontend/sw.js (cache-first für Statics, network-first für /api/*)
- frontend/icons/icon-192.png + icon-512.png + icon.svg
- backend/main.py: Routen für /manifest.json, /sw.js, /icons/* damit
diese Pfade nicht vom SPA-Fallback abgefangen werden
- index.html: manifest-Link, theme-color, apple-touch-icon, apple-* Meta
- app.js: Service-Worker-Registrierung am Ende
Mobile (≤ 768px, additiv am Ende von app.css):
- Sidebar als Overlay mit body.sidebar-open + Backdrop-Element
- View-Switcher horizontal scrollbar wenn er nicht passt
- Monatsansicht zeigt nur farbige Punkte statt Titel
- Wochenansicht reduziert auf Tagesspalte (heute) wenn heute in der
Woche ist (via :has()), sonst Standard-7-Spalten
- Modale auf voller Breite/Höhe
- Tap-Targets ≥ 44px (icon-btn, btn)
- Kein horizontaler Page-Overflow
- iOS-Safe-Area für Notch/Home-Indicator
Version v2 → v3.
iCal speichert DTEND exklusiv (Tag NACH dem letzten Tag). Bisher
führte das dazu, dass ein Termin mit Ende=18.08 nur bis zum 17.08
angezeigt wurde, obwohl der User 18.08 als letzten Tag erwartete.
Fix: Im Date-Picker arbeiten wir jetzt mit inklusiven End-Daten
('endet am 18.08' = 18.08 ist letzter Tag) und konvertieren beim
Speichern auf exklusiv (DTEND=19.08). Beim Laden umgekehrt: -1 Tag
fürs Anzeigen im Picker.
Betrifft: openEditEventModal, openCopyEditModal, Save-Handler.
Über der Kalenderliste im Kopieren-Menü gibt es jetzt eine Checkbox
'Vor dem Kopieren bearbeiten'. Wenn aktiviert und ein Ziel-Kalender
geklickt wird, öffnet sich der Termin-erstellen-Dialog mit allen
Daten des Quell-Termins vorausgefüllt (Titel, Datum, Ort, Beschreibung,
Farbe, Wiederholung) und dem Ziel-Kalender vorausgewählt.
- populateCalendarSelect: filtert jetzt nach !sidebar_hidden statt
enabled. Unchecked (versteckte) Kalender bleiben so im
Termin-erstellen-Dropdown verfügbar
- buildWritableCalendars: HA-Kalender werden als Kopier-Ziele aufgeführt
- copyEventToCalendar: routet HA-Ziele über /homeassistant/events
Endpoint (vorher fielen sie in den CalDAV-Fallback)
Nach dem Speichern eines Termins wird das gecachte Event-Objekt
direkt in-place gepatcht und die View neu gerendert. Vorher war die
neue Farbe erst nach F5 sichtbar, weil zwar fetchAndRender(true)
aufgerufen wurde, aber der Render-Pfad das Update nicht zuverlässig
übernommen hat.
- POST /api/homeassistant/events Endpoint mit calendar.create_event
- Frontend: HA-Termine erstellen statt 'nicht unterstützt' Toast
- Datetime-Format an HA-Konvention angepasst:
'YYYY-MM-DD HH:MM:SS' (Space-Separator, ohne Timezone)
- _ha_format_dt Helper für ISO → HA Datetime-Konvertierung
- HA Update/Delete: UID wird URL-encoded (@ → %40), Delete mit
Fallback auf Service-Call API für ältere HA-Versionen
- Lösch-Dialog: Event-Popup wird geschlossen BEVOR der Bestätigungsdialog
erscheint, kein Überlappen mehr
- HA-Events: Update/Delete-Endpoints via HA REST API implementiert
- HA read-only Guard entfernt, stattdessen korrekte API-Anbindung
- Selected-Day: Outline-Ring statt gefüllter Kreis (Today bleibt gefüllt)
- Serien-Löschung: RECURRENCE-ID aus CalDAV-Events erkennen, damit
expandierte Serientermine als recurring markiert werden und der
Lösch-Dialog Einzel-/Serienlöschung anbietet
- caldav_client: del+add statt direkter Zuweisung bei VEVENT-Properties
(behebt "DTSTART MUST appear exactly once" Validierungsfehler)
- HA-Events als read-only behandeln (kein Bearbeiten/Löschen im Popup)
- [object Object] Toast behoben: HA-Events fallen nicht mehr in CalDAV-Pfad
- End-Datum passt sich automatisch an wenn Start geändert wird (Duration bleibt erhalten)
- Erstellen-Button nutzt den aktuell angesehenen Tag statt immer heute
- Monatsansicht: Einzelklick = Tag auswählen, Doppelklick = Tagesansicht, Rechtsklick = Kontextmenü
- CalDAV URL-Matching robuster (Normalisierung, Path-Fallback, calendar_id Parameter)
- iCal-Abo-Termine sind nicht mehr bearbeitbar (Read-Only-Schutz)
- Wiederkehrende Termine mit RRULE-Support (täglich/wöchentlich/monatlich/jährlich/benutzerdefiniert)
Home Assistant unterstützt keinen Password-Grant — deshalb kam immer
"Ungültige Anmeldedaten", egal was eingegeben wurde. Jetzt wird der
Nutzer nach demselben Muster wie bei Google zur HA-Login-Seite
weitergeleitet, meldet sich dort an und kommt zurück zu Calendarr.
Änderungen:
- Neuer POST /api/homeassistant/auth-url und GET /callback Endpoint
- Account speichert client_id für spätere Token-Refreshes
- Modal: "Benutzername/Passwort" → "Mit Home Assistant anmelden"
- Frontend behandelt ?ha_connected=1 / ?ha_error=... nach Rückkehr
- Version v1 → v2
Vorher wurde die Version erst in initCalendar() gesetzt – wenn JS
vorher fehlschlug, blieb der Text leer. Jetzt steht v1 direkt im
HTML (Titel, Login-Button, Sidebar-Button, Impressum-Modal).
Für künftige Releases: v1 → v2 in index.html + version.js ersetzen.
Neue version.js als Single Point of Truth (APP_VERSION).
Sidebar, Login-Screen und Impressum-Modal zeigen die aktuelle
Version an — ab jetzt bei jeder Änderung v2, v3 ... hochzählen.
Startet bei v1.
- Backend gibt 400 statt 401 bei falschen HA-Credentials zurück, damit
der globale api.js-Logout-Handler nicht ausgelöst wird
- Null-Guard im JS nach api.post verhindert den "calendars of null"-Crash
- Radio-Buttons für Anmeldemethode nicht mehr in form-group, damit
input[type=radio] kein width:100% bekommt und sauber nebeneinander liegt
Ergänzt die HA-Integration um Password-Grant OAuth2: Nutzer können sich
nun wahlweise mit einem Long-Lived Token oder mit Benutzername/Passwort
anmelden. Access Tokens werden automatisch per Refresh-Token erneuert.
Der Cursor war relativ zum .gcp-Container positioniert, aber ohne den
Offset des Canvas innerhalb des Containers (Padding). Jetzt wird die
Canvas-Position via getBoundingClientRect() eingerechnet, sodass der
Cursor exakt auf der Farbpalette bleibt.
updateUI verwendete svCanvas.width (HTML-Attribut, 220px) statt der
tatsächlich gerenderten Breite. Wenn CSS den Canvas größer rendert,
stoppte der Cursor vor dem rechten Rand. Jetzt wird getBoundingClientRect()
verwendet, konsistent mit handleSV.
Beim Ausblenden eines Kalenders (sidebar_hidden) wurde fetchAndRender()
ohne force=true aufgerufen, wodurch der Cache nie invalidiert wurde und
die Events weiterhin angezeigt wurden. Jetzt wird der Cache sofort
gefiltert (wie beim Checkbox-Deaktivieren), ohne einen neuen Netzwerkaufruf.
- Neue Integration: Home Assistant als Kalenderquelle via REST-API
(GET /api/calendars + GET /api/calendars/{entity_id})
- Authentifizierung per Long-Lived Access Token
- Neues Modal zum Verbinden (Name, URL, Token) mit Fehlerbehandlung
- Kalender einzeln aktivierbar/deaktivierbar, Farbe änderbar
- Ausgeblendete HA-Kalender in Einstellungen wiederherstellbar
- Sync- und Trennen-Buttons in den Einstellungen
- Bugfix: CalDAV- und Google-Kalender mit sidebar_hidden=true
liefern nun keine Events mehr im Kalender
Der bisherige multiDayAllDayEvs-Filter hatte einen Timezone-Fehler
bei der Datumsberechnung (UTC-Parsing vs. lokale Zeit in UTC+2).
Neue Lösung: das bereits korrekt arbeitende alldayLayout wird direkt
als Quelle verwendet. Items mit colEnd > colStart sind mehrtägig —
die Spaltenindizes aus dem Layout ergeben den Tint-Bereich exakt.
Ausblenden: Events werden sofort client-seitig aus dem Cache gefiltert
(calendar_id-Match), kein Netzwerkaufruf für die Ansicht nötig.
Einblenden: fetchAndRender(force, silent=true) überspringt showLoading(),
die aktuelle Ansicht bleibt sichtbar und wird nach dem Fetch aktualisiert.
Mehrere mehrtägige Events am selben Tag erzeugen jetzt einen vertikalen
Farbverlauf (linear-gradient) statt gestapelter Ebenen, bei denen nur
die letzte Farbe sichtbar war.
- fetchAndRender(true) beim Ein-/Ausblenden eines Kalenders erzwingt
einen Neu-Abruf statt Cache-Treffer, damit die Änderung sofort sichtbar ist
- Tint-Berechnung in der Wochenansicht berücksichtigt jetzt auch
mehrtägige Ganztags-Events (z.B. Urlaub), nicht nur mehrtägige
Termin-Events — exclusive Enddaten werden dabei korrekt normalisiert
Wenn der Access-Token eines Google-Accounts abläuft und der Refresh
fehlschlägt, wurde die leere Terminliste bisher still zurückgegeben
(kein Log, keine UI-Meldung). Jetzt wird der Fehler geloggt, an den
Aufrufer weitergegeben und als Toast-Meldung im Frontend angezeigt
("Token abgelaufen – bitte Konto trennen und neu verbinden").
Das Events-Endpoint gibt nun {events, errors} statt ein reines Array
zurück; das Frontend extrahiert die Events entsprechend.
month.js: MAX_LANES wird jetzt aus der tatsächlichen Container-Höhe berechnet (kein hartes Limit von 3 mehr).
week.js: All-Day-Zeile verwendet jetzt dieselbe Overlay-Logik wie die Monatsansicht – Termine spannen als einzelner Balken über mehrere Tage.
Timed-Events die mehrere Tage überspannen werden neu in der Ganztags-Zeile für jeden betroffenen Tag als Bar angezeigt (am Starttag mit Uhrzeit). Die Tagesspalten erhalten einen 15%-Farbhintergrund (col-span-tint) um die Abdeckung zu visualisieren.
- caldav_client: client.event() → caldav.Event() mit resource.load() für update/delete (DAVClient hat keine event()-Methode)
- Popup: Copy-Menü wird beim Öffnen eines neuen Events immer zurückgesetzt
- copyEventToCalendar: start/end via new Date().toISOString() normalisiert → verhindert 2h-Verschiebung bei Terminen ohne Timezone-Info
Enddatum wird im Event-Popup angezeigt wenn Termin über Mitternacht geht. Neuer Kopieren-Button (📋) im Popup öffnet Kalender-Auswahl und dupliziert den Termin in den gewählten Kalender (CalDAV / Lokal / Google).
Timed-Events in Wochen-/Tagesansicht werden jetzt auf Mitternacht (24:00) des Starttages gekürzt – keine kilometerhohen Balken mehr bei tagesübergreifenden Terminen. Stundenhöhen: 36/54/72/90 → 28/44/60/80px; Kompakt (28px) zeigt 24h = 672px.
Wheel-Scroll ändert Zeitraum jetzt nur noch in Monats- und Quartalsansicht. In Wochen-, Tag- und Terminansicht scrollt die Seite normal. Stundenhöhen: 40/60/80/100 → 36/54/72/90px; Kompakt (36px) zeigt 24h auf 1080p ohne Scrollen.
Neue Ansicht zeigt 3 Monate eines Quartals nebeneinander mit farbigen Event-Dots, Quartal-Navigation und Titelanzeige (z.B. Q2 2026). Klick auf Tag wechselt in Tagesansicht. Zweisprachig (DE/EN).
Statt nach Farbänderung den Cache zu invalidieren und neu zu laden,
wird calendarColor direkt in-place auf allen gecachten Events gepatcht
und dann nur renderView() aufgerufen. Kein Netzwerk-Request, sofortige
Darstellung der neuen Farbe.
Wenn die aktuelle Ansicht weniger als 4 Wochen vom Cache-Rand entfernt
ist, werden im Hintergrund 8 weitere Wochen in diese Richtung geladen
und in den Cache gemergt. Der Cache wächst damit automatisch mit der
Navigation mit — kein sichtbarer Ladevorgang auch bei langen Sprüngen.
Beim ersten Laden wird ein Fenster von ±8 Wochen um die aktuelle
Ansicht geholt. Wochenweise Navigation trifft danach den Cache
sofort (kein Spinner, kein Netzwerk). Nach echten Datenänderungen
(Event speichern/löschen, Sync, Konto-Änderungen) wird der Cache
invalidiert und neu geladen.
Month view now shows 5 weeks starting from the week containing
currentDate (not fixed to month boundaries), enabling views like
"mid-April to mid-May". Prev/Next buttons jump 4 weeks; mouse
wheel scrolls 1 week at a time with 500ms debounce.