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>
boot() ran token validation and launchApp() inside the SAME try/catch, so ANY
error during app init (a failed calendar/event fetch, a render error) cleared
the token and bounced a validly-authenticated user to the login screen — the
"logged out on every F5" bug. Now /auth/me validates the token alone; launchApp()
runs outside that catch, so a data/render error can no longer log the user out.
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>
- 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>
- 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>
The agenda lists the whole cached range, so clicking Today only reset scroll
to the top (earliest past event). Scroll the agenda to the current date (or the
next day with events) after rendering, so Today/mini-cal clicks land correctly.
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>
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>
- 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>
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>
- 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>
Mein voriges flex/align-center hat "1 JUN" in die Mitte der ganzen Zelle
geschoben. Jetzt: Tageszahl bleibt oben links (wie alle Tage), Monatskürzel
sitzt klein/dezent inline daneben (cell-day inline-flex + kleiner Marker).
Version v38.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Pro Mitglied eine Farbe (group_members.color, auto aus Palette, vom Owner
oder Mitglied selbst änderbar via PUT /groups/{id}/members/{uid}/color).
- Gruppentermin-Farbe = Farbe des Gruppenkalenders.
- API liefert Farben aus: GET /groups & /groups/{id} (member.color,
group_calendar_color), GET /groups/{id}/combined (display_color pro Event)
-> Apps können dieselben Farben anzeigen. Test ergänzt (18 grün).
- Web nutzt display_color; Gruppenkalender im Termin-Editor mit 👥 markiert
(Gruppentermine ohne Gruppenansicht erstellbar); Mitglieder-Farben im
Verwalten-Dialog editierbar. Version v37.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Monatsmarker ("JUN") sitzt jetzt inline neben der Tageszahl ("1 JUN") statt
darüber -> einheitliche Zeilenhöhe; Termine der Woche rutschen nicht mehr
nach unten und überlappen nicht mehr mit "+X weitere".
- Gruppenkalender erscheint in "Meine Kalender" (mit 👥-Markierung) und kann
aus-/eingeblendet werden; Besitzer kann ihn umfärben. Recolor fremder
Kalender abgefangen (nur Besitzer). Version v36.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>