- 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>
- 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>
New 'Monatswechsel' setting (synced flag already existed as month_view_paged):
- Continuous scroll (week): current behaviour — wheel scrolls by a week,
buttons/swipe jump 4 weeks.
- Page by page (month): wheel navigation is disabled; buttons and horizontal
swipe jump a whole calendar month.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- new user_settings.surface_color (nullable, device-local default, not synced):
drives the web sidebar / top bar / surfaces; NULL derives from bg_color as
before. Added to schema, migration, GET/PUT, NULLABLE_OVERRIDES, DEFAULT_SYNC.
- web: surface colour row in the settings table; applyTheme derives the whole
surface family from it
- web: per-row sync icon now has real styling — ON shows the primary-tinted
glyph on a pill, OFF shows a slashed, muted glyph (was previously unstyled,
so synced/not-synced looked identical)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- style the settings-table dropdowns for the dark theme (were white)
- share-icon picker on one even row (no lopsided wrap)
- more breathing room between colour hex and swatch
- user management: promote/demote admin (new PUT /users/{id}/admin,
guarded against self-change and removing the last admin)
- calendar management table: clip/ellipsis cells so narrow columns no
longer overlap (full-width detail rows still wrap)
- centralise default colours: utils.applyTheme now derives every fallback
from settings-sync.DEFAULT_COLORS (single place for coders to edit)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the ad-hoc appearance panel with a uniform table (sync icon | name |
value): dropdowns instead of button groups, every colour with a reset to a
canonical default, a per-row sync toggle, and a global "sync all" master
switch.
- new settings-sync.js: canonical default colours, declarative table
definition, browser-local value copy, effective-value resolution
- calendar.js: generalised sync engine (pull applies only synced keys from the
server, save pushes only synced keys + the flag map), unified the two save
buttons into one, dropped the dead text/line-contrast code, moved default
duration into the appearance table, effective UI language on load
- account settings (private visibility, group calendar, hide profile) stay in a
dedicated profile block, not the sync table
- app.css: settings table / sync toggle / sync icon styles
- i18n: sync-all / sync-this / share-icon strings; sw cache bump
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- add "Geburtstagskalender" to the sidebar +calendar dropdown and a "+ Geburtstage"
button to Settings > Calendars; both call ensureBirthdayCalendar (server enforces
one per account) and toast "already exists" if present
- Settings birthday section: when none exists, just a hint pointing to "+ Geburtstage"
(creation lives in the add area now, not a separate activate button)
- resizable calendar table: sensible default column widths instead of raw measured
- i18n de/en
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Column widths are draggable via a handle on each header's right edge and
persisted in localStorage (re-applied on re-render). Uses fixed table layout
locked to measured px widths so resizing one column doesn't reflow the others.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- new BirthdaySyncDevice table + /api/birthdays/sync-report & /devices so the
web can show "birthdays come from these devices" (iOS reports its device on
each Contacts sync)
- settings: reject setting a birthday calendar as the group-visible calendar
(it can still be shared directly)
- web: Settings > Calendars > Birthdays shows the device list; exclude birthday
calendars from the group-visible picker
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- one dedicated birthday calendar per user; enable it in Settings > Calendars
(or from the New-birthday dialog) — it then shows in the sidebar like any
calendar (colour/visibility)
- New-birthday dialog: no target picker; day/month selects + a year field that
hides when "year unknown"; fixed the year-unknown checkbox layout
- create split-button is now one seamless pill; the caret opens the menu
- Settings > Calendars gains a Birthdays section (enable + notify-days-before)
- removed the birthday toggle from the generic new-calendar modal
- profile settings: right-align the section save button for consistency
- bump service-worker cache to v25 so assets refresh
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- views render display_title (age) and a cake icon for is_birthday events
(month/week/agenda + quarter tooltip) via shared eventTitle/birthdayIconSvg
- create split-button caret opens a menu: new event / new birthday
- new birthday modal: name + date + "year unknown" + target birthday calendar,
saved as an all-day FREQ=YEARLY local event with birth_year
- local-calendar modal gains a "birthday calendar" toggle + "notify N days before"
- i18n de/en strings
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>
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>
- Sidebar: a calendar shared with read access shows "· Nur lesen" next to the
"shared with me · <owner>" label.
- Event editor calendar picker: exclude read-only shared calendars (own +
read_write, incl. group calendars, stay) so a save can't 403.
Parity with the server/iOS/Android sharing changes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CalDAV and Home Assistant sync failures were previously only logged
server-side, leaving clients unable to distinguish an empty calendar
from a broken sync. Unify error reporting across CalDAV, Home
Assistant, and Google into a single errors list on GET
/api/caldav/events, shaped as {source, name, message}. Messages are
fixed generic strings, never raw exception text, to avoid leaking
URLs or credential fragments. get_ha_events and get_google_events now
return (events, errors) tuples so per-calendar failures propagate to
the caller in addition to account-level failures. Frontend toast now
picks its label from err.source instead of assuming Google/err.email.
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>
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>
- Mini calendar: mark every day an event spans (not just the start day) so
multi-day events (Urlaub/Ferien) show a dot across the whole range
- Week view: render short events on one line (time next to title) so the title
stays visible; drop the location line when there's no room
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Don't start a header drag when the pointer goes down on a button/link, so
edit/copy/delete/close work again
- Make the popup body text selectable (user-select: text) for copying
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>
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>
- Render event descriptions as sanitized HTML (links/line breaks) instead of
raw escaped text; no script execution
- Reminder picker: presets + custom number+unit (minutes/hours/days/weeks)
- Grey out + hint the reminder editor when a calendar's notifications are off
(reminders are kept, just not fired)
- New synced setting default_event_duration_minutes (default 60) for new events
Co-Authored-By: Claude Opus 4.8 <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>
- Stop the blinking text caret / selection on non-editable UI chrome
(headings, buttons, icons, dropdowns): body is user-select:none, with text
selection opted back in for inputs/textareas. Felt like an editable field.
- Reminder options trimmed to 5/15/30 min, 1 h, 1 day, 1 week (dropped 10 min,
2 h, 2 days; added 1 week). Labels now read "5 Minuten vorher", "1 Tag
vorher", "1 Woche vorher" instead of "vor 5 Min." / "vor 1 Tag(en)".
- Group calendars can now be muted (reminder bell) in the sidebar when owned.
- Event modal corners rounded (20px) to match the pill buttons.
- version v45 -> v46.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Event popup: new "Reminders" section for local events — show/add/remove
multiple reminders, prefilled with the global default on new events; sent
in the local create/update body and synced like any other event field.
- Sidebar calendar list: per-calendar reminder bell toggle (PUTs the new
reminders_enabled flag); disabled state shown dimmed.
- Date picker: a day click now commits immediately in date-only mode instead
of requiring an extra "Save" click.
- Start/End: moving the end before the start now shifts the start back
(mirroring the start handler) instead of showing an error toast.
- Calendar dropdown: group calendars moved under a "Groups" optgroup; dropped
the 👥 emoji prefix (a native <select> can't render the icon SVG).
- Month grid: selectedDate defaults to today so the highlight no longer
follows the cells while navigating before any click.
- Today cell tint now derives from --today-color instead of hardcoded blue.
- version v44 -> v45.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Moved the group member filter from a top bar into the left sidebar (a
"Mitglieder" section that replaces the personal calendar list while a group is
active). Each member row has a checkbox (show/hide their events) and a colour
dot that opens the colour picker to recolour that member just for this viewer —
stored per-device in localStorage and applied over the server colour. Bumped v44.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Group icons are semantic keys rendered as inline SVG (mirroring iOS SF Symbols /
Android Material) in the picker, group list and sidebar flags — instead of OS
emoji that vary per platform. Legacy emoji values still render as a fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In the group overlay a per-member filter bar appears under the banner: tap a
member (or the group calendar) to hide/show their events, Outlook-style.
Filtering is client-side by event owner; members + colours load from the group
detail. Bumped version.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Use the server-provided display_title (group icon + owner prefix) instead of
the client-side prefix, so web/iOS/Android show group events identically.
Falls back to the local prefix for older servers. Bumped to v41.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The event detail popup looked plainly "HTML". Redesigned it: translucent
glass surface (backdrop-blur), the event's colour as an accent strip + tinted
header + glowing dot, body rows with subtle leading icons (time/location/notes/
calendar/creator), rounded 16px corners, layered shadow, and a soft scale+fade
entrance (respecting prefers-reduced-motion). Body rows restructured to
icon+text; JS now drives row visibility and sets --ev-color. Bumped to v40.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>