Remove the per-field weather checkboxes from the quick-settings sidebar;
those belong in the full settings modal only.
Also make the toggle more reliable: it now updates both the per-view
viewSettings override (saveViewSetting) AND the local profile state
(setProfile) so that effectiveWeatherEnabled is guaranteed to flip on
the next render regardless of which branch getEffective() takes.
v1.83.4
The previous condition only checked weatherLat || weatherLon, so users
who enabled weather in settings but hadn't picked a city yet would never
see the sidebar toggle. Now shows if weatherEnabled OR lat/lon is set.
v1.83.3
When weather is enabled and a location is configured, the sidebar now
shows per-item checkboxes (Symbol, Temperatur, Gefühlt, Wind, Böen,
Regenw., Niederschlag, Luftfeuchte, UV) below the weather toggle.
Checkboxes are hidden when weather is toggled off. All changes go
through saveViewSetting("weatherDisplay", ..., true) — same path as
the settings modal — so they stay view-specific and persist correctly.
v1.83.2
Someday, All-day, Checkboxes, Project-Icons, and Weather toggles in the
left quick-settings panel were calling saveSetting() (global profile PATCH)
instead of saveViewSetting(..., true) (per-view override).
getEffective() checks the per-view override first, so whenever a per-view
setting existed (set via the settings modal) the global update was silently
ignored and the toggle appeared broken.
All five toggles now call saveViewSetting(key, !value, true) which updates
viewSettings[currentView][key] directly — matching exactly what the settings
modal's view-tab checkboxes do.
v1.83.1
- Fix onNotes prop in all three TaskItem render sites: was calling
setSelectedTaskForNotes (ignoring HTML), now correctly calls
updateTaskNotes so inline RichTextEditor saves on every change
- NotesSidebar: replace plain markdown textarea+toolbar with RichTextEditor;
add URL field with open/clear actions
- Kanban detail modal: add URL field; replace plain textarea with RichTextEditor
- Tasks API: add sanitizeUrl() helper — strips javascript:/data: schemes,
normalises missing protocol to https:// before storing
- Remove dead code in TaskItem: notesRef, handleNotesBlur, insertMarkdown
(all replaced by RichTextEditor)
v1.83.0
TaskItem (used for all someday list tasks) was missing URL support entirely.
Added:
- onUrl prop wired to updateTaskUrl at both someday TaskItem render sites
(slot tasks and unindexed/remaining tasks)
- URL indicator (clickable link badge) shown inline on the task row
- Link action button in the task-actions bar that opens an inline URL editor
- URL popup with input, clear button, and open-in-new-tab shortcut
Also upgraded TaskItem's notes editor from plain markdown textarea to
RichTextEditor (same as GridTaskBlock), so inline hyperlinks in notes work
in the someday area too.
v1.82.1
Option A — URL field per task:
- New url String? column in Task schema (migration: 20260403_add_task_url)
- Link icon button in GridTaskBlock action bar; click to open inline URL popup
with input, clear (×) button, and open-in-new-tab shortcut
- Blue link indicator shown inline on the task card when a URL is set
- updateTaskUrl() in WeeklyView persists to DB via PATCH /api/tasks
Option B — Rich text notes with inline hyperlinks:
- GridTaskBlock notes popup upgraded from plain markdown textarea to
RichTextEditor (Tiptap), which already has bold/italic/underline/
bullet/blockquote/link toolbar and full link support
- Notes now save on every change via onChange (no blur required)
v1.82.0
Project icons (faHeart, faBriefcase etc.) were rendering as literal text in
GridTaskBlock because the lookup skipped the legacy "faX" → "x" normalization
that ProjectIcon does. Added the same normalization before allIcons.find().
Someday list tabs now survive provider disconnects and reconnects:
- customTabs (empty tabs) saved to viewSettings in DB instead of localStorage only
- assignListToTab saves a title→tab mapping in viewSettings.somedayTabPrefs
- Profile load merges DB customTabs with localStorage for migration
- Import route reads somedayTabPrefs and auto-applies tab when creating/
relinking a SomedayList by title match
v1.81.20
The SettingsSidebar stores cellDuration in viewSettings (per-view) via
saveViewSetting, so getEffective() returns that value and ignores local
state. The QuickSettings/header/mobile buttons were only calling
setCellDuration (local state) + saveSetting (cookie), which getEffective
overrides. Now all three button groups also call saveViewSetting so the
grid immediately reflects the selected slot size.
v1.81.18
- Removed 45m, restored 60m in CellDuration type and all button groups
- Slot indicator now correctly highlights the active slot in the sidebar
(was broken because 60m users had no matching button when 45 was listed)
v1.81.17
- CellDuration now includes 45 (15 | 20 | 30 | 45 | 60)
- getTimeSlots rewritten to use minutes-based generation so 45-min slots
work correctly (08:00, 08:45, 09:30, 10:15...) — also cleaner for all
other durations
- getSlotHeight(45) = 45px (1px per minute, proportional)
- All three slot button groups (sidebar, desktop header, mobile) now show
[15, 20, 30, 45] instead of [15, 20, 30, 60]
v1.81.16
API routes (goal, weather, calendar/sync, calendar/events):
- All user lookups now use session email instead of session ID so stale
JWTs after DB restore/migration no longer break requests (P2003 FK error)
WeeklyView QuickSettings sidebar:
- effectiveCellDuration now uses local cellDuration state as fallback
instead of stale profile.cellDuration — slot buttons now actually update
the time grid
- Added 20m slot option to all three button groups (sidebar, header, mobile)
- Added :15/:30/:45 sub-hour slots toggle to QuickSettings sidebar
- Slot controls now highlight using effectiveCellDuration (per-view aware)
- Someday and All-day toggles now use effective value, not global profile
v1.81.15
- All-day label column uses flex-start so label is visible in small area
- Weather API returns empty data (200) instead of 502 when forecast range
is out of bounds, suppressing console noise on future/past navigation
- QuickSettings toggles for project icons and checkboxes now use effective
(per-view-aware) value, consistent with other toggles
v1.81.13
Previously showed total allDayEvents.length (all events in the loaded
week). Now counts unique events that appear on at least one currently
visible day column, so in 1-day view it shows that day's count, in
3-day view the count across those 3 days, etc.
v1.81.12
The showTimeGrid toggle is now implicit in the view style selector
(list → grid off, simple/calendar → grid on), making the explicit
Schedule eye toggle obsolete.
v1.81.11
Line 2243 read data.user.profile.viewStyle — data.user.profile is
undefined (the shape is data.user.viewStyle), causing:
TypeError: Cannot read properties of undefined (reading 'viewStyle')
This crash fired every time the SSE sync callback received new data.
v1.81.10
When the active header display is 'current_day' (selected day) AND
viewDays === 1, the in-column weekday header duplicates the information
already shown in the top header bar. Added 'header-current-day-single'
class to the container under that condition, then CSS display:none on
.weekly-day-header. With multiple columns visible the header is shown
normally so each day column is still identifiable.
v1.81.9
1. cellDuration indicator: compare against local `cellDuration` state
(not effectiveCellDuration which reads stale profile.cellDuration
since saveSetting bails early for DEVICE_SETTINGS_KEYS)
2. viewDays on mobile now uses orientation instead of fixed width:
portrait (h > w) → 1 day, landscape → 3 days, for ≤768px.
Applied consistently in useEffect, early profile load, and cookie
override blocks so the responsive value is never overridden by
the saved DB preference on mobile.
3. list-view day header on mobile: changed from sticky to
position:relative so it flows with content and never covers tasks
4. time/day column scroll sync: weekly-days-grid was overflow-y:auto
on mobile, creating an independent scroll container that drifted
from the time column. Changed to overflow-y:visible so only
time-grid-wrapper scrolls. Added touch-action:pan-y to day columns
so iOS handles pan immediately without waiting for JS.
v1.81.7
- FAB menu items reordered so Task is closest to button (top),
Search is furthest (bottom)
- auth.ts: session maxAge=30d + updateAge=1d so the JWT cookie is
written as a persistent cookie (Max-Age header), preventing iOS
PWA from losing the session when the app is closed/backgrounded
v1.81.5
- Mobile quick settings view switcher now correctly toggles showTimeGrid
when switching views (list → off, simple/calendar → on), matching
the desktop switcher behaviour. This removes the unwanted time column
in list view on mobile.
- ProjectIcon: normalise legacy icon names stored as "faHeart" / "faHouse"
(camelCase with "fa" prefix) to "heart" / "house" so the FA icon lookup
succeeds instead of falling through to the text fallback.
v1.81.3
- Mobile header: removed spinner/error from inside the date button,
placed as a fixed-width (24px) sibling slot after the button — date
text position is now stable regardless of sync state
- Selected day: added selectedDay state tracking the last day column
the user clicked; passed to getSelectedDay() so "Selektierter Tag"
in the header reflects the actual cursor position, not just visibleDays[0]
- getSelectedDay() priority: explicit selection → today if visible → first visible day
- Navigating to a new week resets selectedDay so the header stays accurate
v1.81.1
- Rename options to be clearer:
- "Vollständiges Datum" → "Heutiges Datum" (always today's date)
- "Aktueller Tag" → "Selektierter Tag" with custom format input
- "Benutzerdefiniert" → "Woche Benutzerdefiniert" (week-based tokens)
- Add custom format input for "Selektierter Tag" (headerCurrentDayFormat)
with tokens: DDDD, DDD, DD, MMMM, MMM, MM, YYYY, WW
- Add separate header display settings for Mobile Portrait and
Mobile Landscape (mobilePortraitHeaderDisplay, mobileLandscapeHeaderDisplay)
- Move refresh/sync icon to fixed-width slot right of date — date no
longer shifts when the icon appears or the spinner replaces it
- Add 3 new Prisma fields (db push required on deploy)
- All 5 languages updated (EN, DE, FR, ES, IT)
v1.81.0
New "Current Day" option in the header display settings shows the
currently visible day formatted as "Mo., 30. März 2026" (weekday +
full date), using the user's locale.
- Available in all views (simple, calendar, list, kanban)
- In kanban view: defaults to "Current Day" when no setting is chosen
- On mobile portrait: existing behavior preserved (always shows current day)
- On mobile landscape: respects the new setting like other options
- No year separator shown (year is already part of the format)
- Added to all 5 languages (EN, DE, FR, ES, IT)
v1.80.4
In portrait mode the header now always shows the day currently on
screen (getVisibleDays()[0]), so swiping left/right updates the
header to reflect the active day. Formatted as weekday + full date
using the user's locale (e.g. "Mo., 30. März 2026").
v1.80.3
In portrait mode on mobile, the header center now displays the full
date with weekday (e.g. "Mo. 30. März 2026") using the user's language
locale. Falls back to today if today is in the visible week, otherwise
uses the week's reference date. Landscape mobile keeps existing behavior.
v1.80.2
Replace all individual useState reads (viewStyle, timeFormat, language,
cellDuration, showTimeGrid, showNextTask, showSomeday, showAllDay, etc.)
with profile.fieldName reads. Individual useState declarations remain; only
read sites are migrated. No UI logic changed.
- getEffective() calls use profile.fieldName as global fallback
- workingHoursStart/End derived from profile.startHour/endHour
- All JSX reads (className, conditionals, child props) use profile.*
- SettingsSidebar value props use profile.* (setters unchanged)
- Add weekStartDay: 1 to profile defaults
- ProjectsSidebar function body reverted (uses language prop, not profile)
- darkMode excluded: localStorage-only, not an API profile field
v1.80.1
- saveSetting: add setProfile(p => ({...p, [key]: value})) so profile
stays in sync with individual setting changes — prevents auto-save
from overwriting fresh settings with stale profile object values
- Extract SettingsSidebar (4725 lines) from WeeklyView.tsx into its own
file; lazy-load with next/dynamic so the ~4500-line settings panel
is deferred until the user opens settings
- Extract shared constants/helpers to src/lib/:
fontConstants.ts (AVAILABLE_FONTS, FONT_WEIGHTS, isCustomFont)
weeklyViewTranslations.ts (translations object, ~1100 lines)
weeklyViewConstants.ts (WeatherDisplayKey, WEATHER_DISPLAY_DEFAULTS)
- WeeklyView.tsx: 16,209 → 10,364 lines (-36%)
v1.80.0
- tasks/route.ts: add per-unit instance cap for virtual task projection
(daily: 60, weekly: 26, monthly: 12, yearly: 3) to prevent unbounded
generation from high-frequency recurring tasks
- WeeklyView: extract calendarEditabilityMap as a separate useMemo keyed
on connections; replace O(n²) nested find() loop with O(1) Map.get()
lookup in the calendarEvents deduplication useMemo
v1.79.0
- tasks/sync GET: batch Google/Outlook/Synology credential fetches with
Promise.all instead of sequential awaits; move per-list Synology
findMany outside the loop to eliminate N+1
- calendar/sync: replace fire-and-forget background refresh with 800ms
Promise.race so fast refreshes return fresh data without blocking
- Extract icon data to iconRegistry.ts; lazy-load IconPicker via
next/dynamic so picker UI code is deferred until needed
v1.78.0
Three root causes fixed:
1. Scroll enforcement interval (every 50ms for 2s) was the main blocker:
The setInterval forced gridRef.scrollTop on every frame, making iOS think
the page was always scrolling programmatically. User touches during those
2 seconds were overridden. The header appeared 'stuck' because the view
was frozen. Replaced with a single setTimeout scroll (300ms on first load,
50ms on subsequent). Added history.scrollRestoration='manual' so Safari
doesn't fight us with scroll position restoration.
2. touch-action: pan-y was only on .time-grid-wrapper — NOT inherited by CSS.
Slot child elements had touch-action: auto (default), so iOS still waited
for JS before committing scroll on day column touches. Added touch-action:
pan-y directly to .time-slots-container (the actual touched element area).
Time column labels have no JS handlers so iOS scrolled them freely — this
explains why time column worked but day columns didn't.
3. align-items: start on .weekly-days-grid was wrong (added in last commit).
Removed — CSS grid default stretch is correct.
v1.77.4
Root cause: React registers onTouchStart props as non-passive listeners at the
app root. iOS Safari sees any non-passive touchstart on an ancestor and blocks
the scroll gesture on those elements, waiting for JS to finish. This is why
the time column (no handlers) scrolled freely but day columns (slots had
onTouchStart) were frozen.
Changes:
- Removed onTouchStart prop from all time-slot divs — no more React touch
handlers in the time grid, so iOS never blocks scroll
- Added data-slot-blocked attribute to occupied/protected slots
- Moved slot long-press logic into the permanent native touchstart listener
on document (passive: true), reading slot/date from data attributes
- Added touch-action: pan-y on .time-grid-wrapper — explicitly tells iOS
that vertical pan = scroll even if child content has touch handlers
- CSS: align-items: start on .weekly-days-grid so sticky headers in each
day column have a proper scroll context
- Reinforced position:sticky on .time-grid-wrapper .weekly-day-header
v1.77.3
The slot onTouchStart called attachTouchListeners() which did
document.addEventListener() synchronously — DOM manipulation inside a touch
handler makes iOS Safari uncertain about scroll intent and blocks vertical
scroll on day columns (but not the time column which has no handlers).
Fix: attach all touch listeners ONCE at mount (permanent, passive). The slot
onTouchStart only writes to a ref (zero DOM side effects). The non-passive
drag-tracking listener is still added dynamically but only after the 500ms
long-press actually activates, at which point the user has clearly committed
to a drag gesture rather than a scroll.
v1.77.2
- Header center (KW/year/goal/quote) now fades to 20% opacity and blurs
on hover so the controls behind it are readable on smaller screens
- Fix iOS scroll freeze: touchmove listener on grid slots was non-passive,
causing Safari to wait ~20s (iCloud sync duration) before allowing scroll;
now attaches a passive listener initially and only upgrades to non-passive
when the long-press drag actually activates (after 500ms hold)
v1.77.0
The time grid previously had two separate overflow-y:auto scroll containers
(the time-column labels + the day columns grid), synced via JS scrollTop
assignment. On iOS Safari, this caused severe desync and bounce effects:
momentum scroll continued after touch-end, and the programmatic scrollTop
correction fought against native momentum, making the grid stutter/bounce.
Changes:
- time-grid-wrapper is now the SINGLE overflow-y:auto scroll container
(ref=gridRef, onScroll handler moved here from weekly-days-grid)
- time-column and weekly-days-grid have overflow:visible — they scroll
with their parent wrapper without fighting each other
- Removed handleTimeColumnScroll, timeColumnRef, timeGridWrapperRef
(all sync logic now redundant)
- jumpToHour and initial scroll position now set only gridRef.scrollTop
- CSS: weekly-days-grid overflow changed to visible
- CSS: re-enabled position:sticky on .weekly-day-header for mobile —
sticky now works correctly since scroll container is an overflow-y parent
- CSS: hid time-column-header on mobile (saves vertical space)
v1.76.2
Both settings were stored as per-view overrides by the full settings panel
but the quick sidebar toggles read/wrote the global profile value. The
per-view override took precedence in getEffective(), so global changes
from the quick toggle were silently ignored.
- Settings panel now calls saveField() (global profile) for these two
settings instead of saveViewSetting() (per-view override)
- On change, also clears any stale per-view overrides via
saveViewSetting(key, value, false) so the global value always wins
- Quick sidebar eye icons now reflect effectiveShowTaskCheckboxes /
effectiveShowProjectIcons instead of the raw profile values
v1.76.1
Users can now create sync rules to automatically sync events between
any two connected calendar providers (Google, Apple, Outlook, Synology).
- New Prisma models: CalendarSyncRule + CalendarSyncMapping
- Sync engine in src/lib/calendar-cross-sync.ts: fingerprint-based
change detection, anti-loop protection, one-way and two-way support
- API routes: GET/POST rules, PATCH/DELETE rule by id, POST run
- CalendarSyncRulesPanel component with rule list, add/edit form,
toggle enable/disable, Sync Now button with result summary
- New "Sync" tab in settings sidebar (between Connections and Account)
- Sync rules run automatically after each forced calendar cache refresh
v1.76.0
- Skip optimistic UI update for new recurring events (causes duplicate
when force-sync fetches expanded instances with different IDs)
- Mobile sticky day bar: listen on gridRef scroll instead of window
(time-grid scrolls inside weekly-days-grid, not window)
- Remove broken position:sticky from mobile day headers (CSS sticky
doesn't work inside CSS grid scroll containers); use fixed overlay only
- Also propagate isRecurring flag in create response for Google/Outlook
v1.75.8
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The first time-slot label's translateY(-50%) pushed it above the container
top edge. Added padding-top to time-column-slots and time-slots-container,
and disabled the translate on the first label. Reverted 00→0.
v1.75.5
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Apple sync was doing 2 logins per cycle: getAppleCalendars (to get calendar
list for filtering) + getUpcomingEventsBatch (to fetch events). Now uses
stored calendar metadata from DB for filtering, reducing to 1 login total.
Same optimization applied to Synology. Also adds 24:00 end-of-day line
to time grid, and throttles tab-focus sync to max once per 5 minutes.
v1.75.3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Apple/Synology: batch fetch events with single client login instead of N separate connections
- Apple: add client cache (5min TTL) to reuse authenticated DAVClient across operations
- Apple: add retry with backoff for transient ConnectTimeoutError/SSL failures
- Increase cache stale threshold from 2min to 15min
- Increase background sync interval from 2min to 15min
- Throttle tab-focus sync to at most once per 5min
v1.75.2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The scrollable time grid already shows all 24 hours and auto-scrolls
to the preferred start hour on load, making the start/end hour
filter controls redundant. Removed from sidebar, header, and
full settings modal.
v1.75.1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of relying on CSS position:sticky (unreliable on iOS Safari),
adds a fixed overlay bar at the top of the viewport that shows the
current day name as the user scrolls through stacked day columns.
Uses IntersectionObserver to track which day header is near the
viewport top. Appears after scrolling past 80px, with blur backdrop.
v1.75.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Reduced cache stale threshold from 15 minutes to 2 minutes
- Reduced background sync polling from 15 minutes to 2 minutes
- Added visibilitychange listener: syncs calendars and tasks when
the user switches back to the tab (catches external edits in
Apple Calendar, Google Calendar, etc.)
v1.74.7
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Instead of force-refreshing all calendar providers after create/update/delete,
now only the specific connection that owns the modified calendar is refreshed.
This significantly speeds up recurring event operations when multiple
providers are connected.
v1.74.6
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Always show provider icon (Google/Apple/Outlook/Synology) at
bottom-right of event blocks using FontAwesome icons
- Save button shows "Erstelle..." / "Creating..." while saving
instead of just "..." to reduce user confusion during sync
v1.74.3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All create, update, and delete operations now trigger an immediate
forced refresh from providers instead of relying on stale cache.
Previously, deleting recurring events left ghost instances visible
until the next background sync cycle.
v1.74.2
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Show provider icon (Google/Apple/Outlook/Synology) left of calendar color dot
- Hide URL field when Synology calendar is selected (not supported)
- Force refresh after creating recurring events so all instances show immediately
(previously only the first occurrence appeared until next sync cycle)
v1.74.1
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fixed weekly-days-grid applying fixed height in non-time-grid views
(simple/list), which broke the scroll context for sticky headers
- Reordered mobile FAB menu: Aufgabe, Termin, Projekt, Fokus, Suchen
- Changed "Suche" to "Suchen" in FAB menu
v1.73.3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>