Fuenf Farbspalten trugen einen fest eingebauten Vorgabewert — primary_color
#4285f4, accent_color #ea4335, today_color #4285f4, month_divider_color und
month_label_color #7090c0. Damit hatte *jeder* Nutzer immer eine eigene
Farbe, und der Fallback settings.X || baseColor(X) kam nie zum Zug. Das
Admin-Versprechen "Gilt fuer alle Nutzer ohne eigene Farbe" war fuer diese
fuenf unerfuellbar, und die Werte widersprachen DEFAULT_COLORS im Frontend,
das laut Kommentar die einzige Quelle sein soll.
Das betraf nicht nur Altbestand: UserSettings wird ohne Farbwerte angelegt,
also bekam auch jeder heute neu angelegte Nutzer das Blau.
- models.py: die fuenf Spalten sind nullable, NULL heisst "erbt".
- settings_router: NULL wird ausgeliefert statt durch einen Standard ersetzt,
und ein explizites null wird gespeichert (NULLABLE_OVERRIDES).
- main.py: einmalige Migration setzt genau die alten Vorgabewerte auf NULL.
Bewusst gewaehlte Farben bleiben unangetastet.
- calendar.js: eine erbende Zeile merkt sich das und sendet beim Speichern
null. "Zurueck" heisst wieder erben, statt den aktuellen Standard in die
Nutzerzeile zu kopieren — sonst erreichen spaetere Aenderungen des
Standards den Nutzer nie mehr.
Verifiziert: frische Installation liefert --primary #58B900 statt #4285f4;
zurueckgeschriebenes Alt-Blau wird beim Neustart geleert; eine eigene Farbe
(#FF00AA) ueberlebt die Migration. Suite 129 gruen, davon 5 neue.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
initCalendar baute mit mergeEffective() korrekt die wirksamen Einstellungen
zusammen, benutzte danach aber die rohe Server-Antwort zum Anwenden:
applyTheme(settings) statt applyTheme(state.settings)
Fuer jede Einstellung, deren Sync-Flag aus ist, ist der wirksame Wert laut
effectiveValue() der browser-lokale — der wurde damit stillschweigend
ignoriert. Die Einstellungstabelle zeigte also einen Wert an, waehrend die
Oberflaeche einen anderen benutzte.
Derselbe Fehler bei setLang: die Zeile mit dem wirksamen Wert wurde wenige
Zeilen spaeter vom rohen Server-Wert ueberschrieben. Die doppelte Zuweisung
faellt weg, die richtige bleibt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Seite trug zweimal autocomplete="one-time-code": im Login und im Feld
zum Einrichten von 2FA in den Einstellungen. Fuer einen Passwortmanager ist
damit nicht eindeutig, zu welchem Feld sein Inline-Symbol gehoert.
Das Einrichtungsfeld bekommt data-bwignore (plus die Entsprechungen fuer
1Password und LastPass) — dort tippt man den Code aus der Authenticator-App
beim Anlegen eines neuen Geheimnisses ab, ein Ausfuellen ergibt da ohnehin
keinen Sinn. Die Passwortfelder bleiben unangetastet, die sollen weiter
befuellbar sein.
Ob das die fehlplatzierte Bitwarden-Anzeige behebt, ist offen: hier laesst
sich keine Erweiterung installieren. Der vorherige Versuch ueber das
Fokus-Timing hat es nicht geloest.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Benutzername und Passwort liegen ab Seitenaufbau im Layout, das 2FA-Feld
taucht erst aus display:none auf — und der Fokus wurde im selben Tick
gesetzt. Passwortmanager oeffnen ihr Inline-Overlay beim Fokus und messen die
Feldposition in genau dem Moment; Bitwarden platzierte sein Symbol dadurch
weit neben dem Feld.
Der Fokus laeuft jetzt ueber setTimeout(0), sodass das Layout vorher steht,
plus ein resize-Event als Anstoss zum Neupositionieren. Bewusst kein
requestAnimationFrame: das feuert in einem nicht sichtbaren Tab nicht und
haette den Fokus ganz verschluckt.
Nicht gegen Bitwarden verifizierbar — hier ist keine Erweiterung installiert.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Die Endpunkte gab es schon, die Oberflaeche fehlte — es war schlicht nicht
sichtbar, ob und womit ein Konto verknuepft ist.
Unter Einstellungen > Profil, zwischen App-Passwoertern und Privatsphaere,
steht jetzt "Single Sign-On" mit den verknuepften Konten (Anbieter, E-Mail,
letzte Nutzung) und je einem Knopf zum Aufheben. Ohne Verknuepfung erscheint
ein Hinweis, wie man eine anlegt; ist auf dem Server ueberhaupt kein Provider
konfiguriert, bleibt der ganze Abschnitt unsichtbar.
Bei einem per SSO angelegten Konto ist die letzte Verknuepfung nicht
loesbar — es hat kein brauchbares Passwort und waere danach dauerhaft
unerreichbar. Der Knopf ist in dem Fall deaktiviert und erklaert sich per
Tooltip; der Server lehnt es zusaetzlich ab.
Im Browser durchgespielt: verknuepfen, Eintrag erscheint mit Anbieter und
Datum, aufheben, Bestaetigung, leerer Zustand.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bisher war eine SSO-Anmeldung ohne passendes Konto eine Sackgasse: rote
Fehlermeldung, kein Weg weiter. Die Verknuepfung ging nur ueber
LINK_BY_EMAIL, das der E-Mail-Verwaltung des Providers vertrauen muss.
Jetzt endet der Callback bei einer besitzerlosen Identitaet nicht im Fehler,
sondern parkt sie in einem kurzlebigen signierten HttpOnly-Cookie und leitet
auf /?sso_link=1. Der Login-Screen bittet daraufhin einmalig um das
Calendarr-Passwort; nach erfolgreichem Login loest der Client
POST /api/auth/oidc/link-pending ein und die Identitaet haengt an genau dem
Konto, das sich gerade ausgewiesen hat. Beide Haelften sind damit bewiesen —
das ID-Token haben wir selbst geprueft, das Konto durch das Passwort — und
die Vertrauensannahme ueber die E-Mail entfaellt.
Das greift fuer alle drei Faelle, in denen eine gueltige Identitaet keinen
Besitzer hat: kein Konto vorhanden, Selbstregistrierung aus, oder E-Mail
bereits vergeben.
Ausserdem: unbekannte Fehler-Slugs werden jetzt mit angezeigt statt hinter
"Anmeldung fehlgeschlagen" zu verschwinden — genau die Information, die zur
Diagnose fehlte. Und oidc_token_exchange_failed hat eine eigene Meldung, die
auf Client-Typ und Redirect-URI hinweist, weil das der haeufigste
Konfigurationsfehler ist.
Sechs Tests dazu; Ablauf zusaetzlich im Browser gegen einen echten
OIDC-Stub-Provider durchgespielt: Hinweis statt Fehler, Verknuepfung nach
Passwort-Login, danach Anmeldung allein per SSO.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zusaetzlicher Anmeldeweg per OIDC neben dem bestehenden Passwort-Login. Ohne
gesetztes OIDC_PROVIDERS aendert sich nichts: /api/auth/oidc/providers meldet
enabled:false und der Login-Screen bleibt unveraendert.
Backend
- oidc_config.py: Provider-Registry rein aus ENV. Ein zweiter Provider ist
reine Konfiguration (OIDC_PROVIDERS + OIDC_<KEY>_* Block), keine Codeaenderung.
- oidc_client.py: Discovery, JWKS, Token-Tausch, ID-Token-Pruefung ueber
authlib.jose mit fest gepinnter Signatur-Allowlist (nur asymmetrisch, damit
alg:none und Algorithmus-Confusion ausgeschlossen sind).
- oidc_identity.py: Zuordnung ueber (provider_key, sub). Ein E-Mail-Treffer auf
ein bestehendes Konto wird standardmaessig abgelehnt statt verknuepft — sonst
erbt jeder, der im IdP eine beliebige E-Mail setzen kann, das passende Konto.
Verknuepfen ist eine explizite, authentifizierte Aktion. Auto-Provisioning
nur per ENV-Flag, standardmaessig aus, optional mit Domain-Allowlist.
- routers/oidc_router.py: /providers, /start, /callback, /complete, /exchange,
/link, /identities. PKCE S256; Flow-State als kurzlebiges signiertes JWT in
einem HttpOnly-Cookie mit jti==state statt eines prozesslokalen Dicts, damit
der Flow Neustarts und mehrere Worker uebersteht. Das Calendarr-JWT wird ueber
ein Einmal-Cookie uebergeben, nie ueber URL oder Fragment.
- /exchange als Kontrakt fuer die AppAuth-Clients: die App fuehrt den Flow als
Public Client selbst aus und tauscht das ID-Token; kein Secret im Request.
- models.py: neue Tabelle oidc_identities, users.auth_source; Migration in
main.py im dortigen try/except-Stil.
Nebenbefund, mitbehoben: auth.verify_password liess ValueError aus
bcrypt.checkpw durch. Ein nicht wohlgeformter password_hash wurde damit zu
HTTP 500 auf /api/auth/login und bei jedem CalDAV-Basic-Auth. SSO-Nutzer
bekommen einen echten bcrypt-Hash ueber ein zufaelliges, nirgends gespeichertes
Geheimnis; fuer CalDAV nutzen sie App-Passwoerter wie 2FA-Nutzer.
Web
- SSO-Block auf dem Login-Screen, aus /providers befuellt; Buttons sind Links,
weil /start eine Top-Level-Navigation braucht.
- consumeSsoHandoff() laeuft vor den Setup-/Token-Pruefungen in boot() und
raeumt die Query per replaceState ab, bevor es awaited.
- 401-Auto-Logout um die SSO-Pfade erweitert, sonst dreht ein fehlgeschlagener
Handoff eine Reload-Schleife.
- i18n-Keys in de + en (Fallback-Kette deckt die uebrigen acht Sprachen).
- Service Worker laesst /api/auth/oidc/ unangetastet durch.
37 neue Tests (Gesamtsuite 60 gruen): Signaturpruefung mit echten RSA-Tokens,
alg:none, falscher iss/aud/nonce, unbekannte client_id, Provisioning-Policies,
Username-Kollision, Einmaligkeit des Handoff-Cookies, unerreichbarer Provider.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace the stale blue calendar icon with the real green Calendarr app
icon (matching iOS/Android) for the installable web app:
- icon-192/512.png regenerated from the branded 1024 app icon
- add dedicated maskable icons (192/512) with brand-green safe-zone
padding so Android/Chrome render a proper adaptive icon (no white box)
- manifest: maskable purpose entries, id, description, lang/dir,
categories; theme_color -> brand green (#16713d)
- index.html: theme-color -> green, add modern mobile-web-app-capable
- favicon.svg + icon.svg recolored to brand green
Bump APP_VERSION v89 -> v90.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Equal rights for all calendar types: the sidebar remove button is now a uniform
"hide" (eye) for every calendar instead of delete-for-some / nothing-for-shared.
- Owned local (incl. birthday) and iCal now hide via the server sidebar_hidden
flag (delete stays available in Settings' calendar table).
- Calendars shared with me get a per-device sidebar hide (they can't carry a
server flag); their events are filtered out while hidden, and the Settings
calendar table's "shared with me" rows gain an un-hide eye toggle.
- External caldav/google/ha unchanged.
Group calendars intentionally left as-is for now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The version line in the Impressum footer ("Calendarr vNN") is now a
subtle link to the Calendarr Gitea repo (git.scarriffle.com), opening
in a new tab. Styled to stay muted at rest and reveal as a link on
hover. Bump APP_VERSION v87 -> v88.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Admin default-theme editor: "Theme importieren" button loads a .theme.json
into the editor (colours only, live preview) so an imported theme can be set
as the instance default (still confirmed via "Standard speichern").
- Branding: drop the "Standard" placeholder; show pixel-dimension hints instead
(logo shown at max 150×40, favicon 128×128). Logo preview box now mirrors its
real topbar footprint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replaces the blue defaults with the provided green theme (primary #58B900,
accent #45A148, today #6FB669, greens for month/selected/today-bg, etc.). These
apply when no admin instance theme is set. THEME.md defaults table updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Admin settings tab (admin-only) now holds user management plus a server-wide
default theme editor (live preview + discard) and logo/favicon branding.
- New singleton InstanceSettings model + /api/instance router (public GET for
the login screen; admin-gated theme PUT and logo/favicon upload/delete,
reusing the avatar PIL/validation pattern).
- Instance default theme is the base users inherit and the target a per-colour
"Reset" returns to (baseColor: instance default -> built-in).
- Custom favicon overrides the primary-colour tinting; custom logo replaces the
top-left glyph+text, hard-capped so it only scales down and never breaks layout.
Branding + defaults load before login (public endpoint).
- Theme export now uses the recognised .theme.json extension (old .theme still
imports); import stays partial/unknown-key aware.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Theme:
- Export/import themes via UI as <date>_<time>.theme (JSON, readable keys,
link to THEME.md). Import is partial-aware: only present params are written
(server accepts partial); prompts before importing files with unknown params.
- Dynamic favicon + theme-color tinted to the primary colour on load/save.
- New themable colours: general hover-highlight, day hover/selected/bg,
today background, plus two unified sidebar action-icon colours
(inactive/active) covering bell, hide, delete and read-only icons.
- All new colours are per-setting syncable; documented in THEME.md.
UX:
- Styled confirm dialog (#modal-confirm) replaces window.confirm() for
calendar delete and account disconnect.
- Birthday/local calendars and iCal subscriptions can now be hidden from the
sidebar via Settings (new sidebar_hidden column + hide toggle).
Backend: additive nullable columns + idempotent migrations for user_settings
colours and local_calendars/ical_subscriptions.sidebar_hidden.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The user-search input and permission dropdown in the share modal were raw white
browser controls. Style them like the rest of the app (dark bg, border, radius,
focus ring) and give the user checkboxes the primary accent colour. v80.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The per-device hide branch was gated on owned!==false (my own calendars) instead
of owned===false (shared with me), so unchecking a shared calendar hit the
owner-only enabled PUT and 404'd. Swap to owned===false.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug 1 — a calendar shared with me stayed visible after unchecking it: the hide
was a one-shot cache filter the server undid on refetch. Add a per-device
hidden set (localStorage 'hiddenLocalCalendars'), honoured in filterEvents
(normal view) and used to drive the checkbox state, so it survives refetch/reload.
Bug 2 — in the group combined view, other members' events showed edit/delete and
403'd on save. The combined endpoint now emits read_only (editable = the group
calendar OR my own events), via a read_only param threaded through
build_local_event_dict/expand_recurring_local. The event popup and edit modal now
treat read_only events as read-only (copy still allowed). Test added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The settings modal is a full-screen OPAQUE overlay at z-index 500. Dialogs
opened from within it (share, add-account, color picker) share z-index 500 but
sit earlier in the DOM, so they rendered BEHIND the opaque settings page — the
Share button appeared to "do nothing". Drop the settings page to z-index 400 so
real modals stack above it. v79.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The global ".form-group input" rule (width:100% + padding + border) also hit the
directory-hidden checkbox, blowing it up to a full-width field that pushed the
label text into a narrow wrapping column. Reset the checkbox to a small native
box (16px, no padding/border/background) so the label reads normally beside it.
v78.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three separate settings bugs (all pre-existing, exposed once the modal actually
opened):
1. renderCalendarTable() referenced a bare `owned` variable (undefined in that
scope; should be cal.owned) → ReferenceError on the first owned local calendar
→ renderAllAccounts threw → the calendar table never rendered. This was the
ROOT cause of "settings won't open" (it threw out of openSettingsModal); the
earlier try/catch only masked it. Fixed to cal.owned.
2. On reload, writeUrlState() (via fetchAndRender) ran before openSettingsModal
activated the saved tab and wrote the HTML-default (Profile) tab into the URL,
so every reload landed on Profile. Now the stab is only rewritten once the
modal is actually shown; otherwise the saved one is preserved.
3. The directory-hidden checkbox label inherited the global ".form-group label"
uppercase/spaced/12px styling, stretching the text into a broken column. A
higher-specificity .checkbox-row rule restores normal inline layout.
v77.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
openSettingsModal() populated every field and ran render helpers
(renderGroupVisibleList, renderAllAccounts, initAppPasswords) BEFORE the final
openModal() call, so an error in any of them left the modal unopened — the
"settings button does nothing" report. Now the modal opens first and the
populate step runs in try/catch (errors logged, not fatal). v76.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
initCalendar() awaited fetchAndRender() BEFORE binding the topbar/settings/menu
handlers, so any error from /caldav/events (e.g. a transient 500) threw out of
init and left the buttons dead — the reported "settings won't open". Same root
cause as the reload-logout bug: app wiring must not depend on event data loading.
Bind all handlers first, then fetch inside try/catch (errors are logged + shown
as a toast, no longer fatal). v75.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the per-device localStorage colour override with the server-backed
per-user colour: the colour dot on a shared calendar now PUTs /calendars/{id}/color
(stores the recipient's own colour server-side, synced across devices) and the
list/events already carry share.color from the server, so the localStorage
re-apply layer is removed. Owners and recipients share one code path. v74.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Shared calendars belong to someone else, so a server colour PUT would 403.
Instead store the recipient's chosen colour locally (localStorage 'sharedCalColors',
keyed by calendar id) and re-apply it to events on every fetch. The colour dot
on a shared calendar now opens the picker instead of showing an error.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Sidebar: don't start an inline rename on a calendar I don't own (owned=false)
— the save would only 403. Colour picker was already gated.
- Sidebar: show a struck-through pencil icon on read-only shared calendars.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Backend: co-member group_visible calendars now surface in /local/calendars
(owned=false, shared_by=owner, read-only, group_shared) and in the normal
/caldav/events merge (via readable_local_calendar_ids), deduped against
direct shares / group calendars so nothing appears twice.
- Backend: new User.directory_hidden — a user can hide from sharing/group
pickers (/users/directory), while admin user management (/users/) still lists
them. Migration + profile GET/PUT.
- Backend: /groups/{id} members carry shares_calendar so clients can drop
phantom rows for members who share nothing.
- Frontend: reachable "Teilen" button on owned local calendars; share modal is
now a checkbox multi-select of users (checked = shared). Hidden-profile toggle
in Settings → Profile. Group member filter only lists members who actually
share (phantom fix). Calendars shared with me moved to a dedicated read-only
"shared with me" section in the manage table.
- Tests: group_visible propagation, no-share absence, dedup, directory_hidden.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A calendar shared with another person now appears to the recipient under the
OWNER's name (Guido's "Persönlich" shows as "Guido"), matching the iOS filter
sheet. The original calendar name moves to the sub-label / settings source
column so it stays identifiable. Sidebar list + settings table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The app-password UI was in the user-menu profile modal, but users manage CalDAV
in Settings, so it went unnoticed. Move the section into the Settings → Profile
panel (next to account/privacy) and drive it from openSettingsModal via a
top-level initAppPasswords().
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CalDAV clients send only user+password over Basic Auth and can't provide a TOTP
code, so account passwords would bypass 2FA. Add revocable app passwords:
- models: AppPassword table (bcrypt hash, label, last_used); auto-created via
create_all
- profile_router: GET/POST/DELETE /profile/app-passwords (plaintext shown once)
- dav_router: Basic Auth accepts any app password; the account password is
accepted only when 2FA is disabled
- frontend: "App-Passwörter (CalDAV)" section in the profile modal (create/show-
once/copy/revoke) + i18n (de/en); login hint now says app password
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- dav_router: add Basic-Auth principal-discovery tree at /caldav/ (and
/.well-known/caldav) so clients can add a CalDAV account with server URL +
username + password; lists all published calendars. Token URL /dav/{token}/
still works without login. Handlers generalised over a base href.
- dav_util: derive the public origin from X-Forwarded-Proto/-Host (or
PUBLIC_BASE_URL) so published URLs are https, not internal http:8080.
- local_router: expose caldav_login_url alongside caldav_url.
- frontend/i18n: show both the no-login token URL and the login URL + hint.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- app.js: reload once on SW controllerchange (guarded against loops / first
install) and poll reg.update() hourly for long-open tabs
- sw.js: bump cache to v24 so the new (network-first) worker replaces any stale
cache-first worker and cleans old caches on activate
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fresh commit so a git pull visibly advances the deployed version and the PWA
service worker refreshes its cached assets. No functional change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend:
- LocalCalendar gains caldav_published + dav_token + dav_ctag; LocalEvent gains
etag (migrations in main.py). bump_dav() refreshes ctag/etag on every local
event write (create/update/delete/import).
- local_router: PUT /calendars/{id} accepts caldav_published (mints/revokes
token), new POST /calendars/{id}/dav-token/rotate, _cal_dict exposes
caldav_published + caldav_url.
- New dav_router mounted at root (/dav/{token}/...): a minimal two-way CalDAV
server (OPTIONS/PROPFIND/REPORT/GET/PUT/DELETE) reusing ical_io build/parse,
ctag-based change detection. Secret token = auth, no login.
Frontend:
- Settings calendar table: per-local-calendar publish toggle + subscribe URL
with copy and token-rotate; i18n (de/en) and styling.
Note: reverse proxy must allow WebDAV methods (PROPFIND/REPORT/PUT/DELETE).
VALARM/reminders are not round-tripped via CalDAV.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The multi-day mini-calendar and short-event week-view fixes were already
committed but not visible because the PWA served cached assets. Bump the
version so clients pick up the new JS/CSS.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace group-category icons with 8 sharing-specific icons in the
Darstellung picker: share (3 nodes), link, send, eye, upload, wifi,
person_add, ios_share. Uses new shareIconSvg() + SHARE_ICON_KEYS/PATHS
constants, separate from group icons. Default changes from 'people' to 'share'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename: Add google and homeassistant branches to the dblclick-rename
save() handler; backend already accepted name updates for both sources
- Group icon: Pass groupIconForLocalCal(cal.id) into sidebar entries so
the group's custom icon (home/heart/work/…) shows instead of always
defaulting to the people icon
- Share icon: New share_calendar_icon field in UserSettings (+ migration)
replaces the hardcoded SHARE_ICON SVG; users pick from GROUP_ICON_KEYS
in Settings → Darstellung via a new icon-picker row
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- groupVisible calendars get a share icon instead of the people icon;
people icon is reserved for actual group calendars
- sidebar scrollbar hidden (scrollbar-width: none + webkit)
- modal-body scrollbar hidden — fixes Impressum scroll indicator
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of absolute positioning (which caused icons to overlay text),
use min-height:40px on .cal-item so the row is always tall enough for
28px mini-btns. Buttons stay in the flex flow with display:none when
hidden — no space reserved, no text truncation, no height shift on hover.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Buttons (.cal-item-remove, .cal-item-bell) are now position:absolute so
they never participate in flex layout — names get full available width at
all times. Buttons fade in over the right edge of the item on hover with
no layout shift and no row-height change.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- modal-card: overflow:hidden + flex column; modal-body scrolls instead,
so native scrollbar never overlaps the 20px border-radius corners
- cal-item-remove / cal-item-bell: display:none→flex replaced with
opacity:0/pointer-events:none→1/auto, eliminating reflow on hover
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
URL gains &stab=<panel> when settings are open (e.g. #...&settings=1&stab=accounts).
Tab switches update the URL; reload reopens settings on the same tab.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All-day continuation bars now use CSS clip-path polygon for the tapered
arrow shape; bars stay inset within the popup (no edge bleed). Version v51.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All-day events in the "+N weitere" popup now render as colored bars matching
the month-grid style: continues-left/right arrows when the event spans beyond
the selected day. Timed events keep dot+time+title format. Version bump v50.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Tab title shows only "Calendarr" (version stays in sidebar/impressum)
- Fix lane assignment in month view: use 2D per-column occupancy grid instead of
global colEnd tracking; eliminates empty rows caused by non-overlapping events
being pushed to lower lanes
- "+N weitere" now opens an inline popup listing all events for that day
(all-day first, then by time); clicking an event opens its detail popup
- Double-click on day cell navigates to day view (was already implemented)
- Remove hardcoded "v18" from login screen impressum button
- Bump version to v49
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Date picker: clicking a day only selects it now; the choice is committed
when you leave the picker (click outside) or press Save — for both date and
datetime modes (previously datetime discarded on click-away, and date
committed instantly on click, neither of which was wanted).
- The day right-click context menu ("Neuen Termin erstellen") now closes when
an event popup opens (the event row stops propagation, so its own
outside-click closer never fired).
- version v47 -> v48.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A native <select> can't render the group icon SVG, so the calendar picker
is replaced with a styled dropdown: colour dot + group icon + name, grouped
by account/source with a "Groups" section. The native <select> is kept
hidden as the value holder + change-event source, so all existing save and
private/reminder-row logic is unchanged. Click-outside / Escape close it.
version v46 -> v47.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>