- Add _pendingLoginRedirect flag in router.js to handle auth:expired
events that fire during active navigation (isNavigating=true).
Previously the navigate('/login') call was silently dropped, leaving
the user stuck on the dashboard with an error toast.
- Bump SHELL_CACHE v33→v34 and PAGES_CACHE v28→v29 to force cache
invalidation on iOS devices still running the pre-CSRF-fix code.
- Add err.status to dashboard error log for easier diagnostics.
iOS Safari in PWA standalone mode unreliably handles cookies, causing
CSRF token desync between client and server after app resume. Previous
fixes (response body token in /auth/me and /auth/login) still left a
window where the token could go stale.
Now the server sends X-CSRF-Token response header on every API response
(via csrfMiddleware), including 403 error responses. The client reads
this header from every response, enabling instant self-healing: a 403
extracts the correct token from the error response itself and retries
without needing an extra /auth/me round-trip.
SW cache bumped to v33 to ensure existing iOS PWA installs pick up the
new client code.
When adding ingredients in the meal editor, each ingredient now has a
category dropdown. Categories are stored on the ingredient and applied
automatically when transferring to the shopping list, so items appear
pre-grouped by category without manual re-sorting.
iOS reserves the home indicator area outside the CSS viewport, leaving
a black strip below the bottom navigation. A fixed body::after pseudo-
element now fills this zone with the surface color. Also added explicit
background-color on body for consistent rendering.
iOS Safari (especially PWA/standalone mode) unreliably exposes cookies
via document.cookie, causing CSRF token mismatch on state-changing
requests. The CSRF token is now included in /auth/login and /auth/me
response bodies and stored in-memory on the client. Cookie remains as
fallback. Retry mechanism also improved to read token from response
body and handle expired sessions.
- FAB focus ring: double-ring pattern replacing invisible #fff outline
- forced-colors media query for Windows High Contrast Mode
- New tokens: --color-accent-secondary, --content-max-width-narrow, --cal-hour-height
- Apple sync logo uses semantic tokens for correct dark mode inversion
- Sidebar logo gradient references token instead of hardcoded #7C5CFC
Users can now show/hide widgets and reorder them via a settings button
in the greeting header. Configuration is persisted server-side in
sync_config (dashboard_widgets key) and shared across all family members.
- Greeting widget gets a settings icon button opening a customize modal
- Modal lists all widgets (tasks, calendar, shopping, meals, notes,
weather) with toggle switches and up/down reorder buttons
- Reset to default layout available in the modal
- GET /preferences now returns dashboard_widgets; PUT accepts it
- All 10 locales updated with new i18n keys
Dashboard widgets, task cards, note items, meal slots, form inputs,
toolbars, and FAB actions now use semi-transparent glass backgrounds
with backdrop-filter blur. Each surface gets a subtle module accent
color tint via color-mix gradient overlay. App background uses a
radial accent gradient for ambient vibrancy.
New tokens: --glass-bg-card, --glass-bg-input, --glass-bg-toolbar,
--glass-tint-strength with full dark mode and accessibility overrides.
In iOS WebKit standalone (home screen) mode, position:fixed elements
move with the page when the body itself becomes scrollable - unlike
regular Safari where fixed elements stay pinned. The root cause was
body having min-height: 100dvh without overflow: hidden, which allowed
body scroll to occur when content overflowed.
Fix: html and body are now overflow: hidden with fixed height (100% / 100dvh)
so all scrolling is confined to .app-content. Service worker bumped to
shell-v30 to force re-download of reset.css on installed PWAs.
- All @supports checks extended to include or (-webkit-backdrop-filter: blur(1px))
so Safari < 18 (which only recognizes the -webkit- prefix) no longer skips
the entire @supports block and receives no glass styles at all
- Non-blur glass styles (background-color, border, box-shadow) moved outside
@supports blocks - always active on all browsers regardless of blur support
- Capsule buttons, specular highlights, glass borders and shadows now visible
on all devices, blur effects added on top where supported
- dashboard.css: move .widget glass styles (shadow, border, ::before highlight)
directly here since module CSS loads after glass.css and would override it
- tasks.css: move .filter-chip--active glass state and fix .priority-badge
border-radius to use --radius-glass-chip (capsule) instead of --radius-xs
- glass.css: remove dead .sticky-header rule (class not used in HTML) and
remove duplicate .widget rules now handled by dashboard.css
- Apple CalDAV: ICS events with TZID parameter are now converted to UTC
using the Intl API instead of being stored as floating local time,
fixing wrong start times for events synced from iOS Calendar
- i18n: fallback language for unsupported browser locales changed from
German to English
Six tabs (General, Meals, Budget, Shopping, Calendar, Account) replace
the flat single-page layout. Active tab persists via sessionStorage.
Calendar tab auto-activates on OAuth redirect. Tab bar is sticky.
All labels translated in de/en/es/it/sv.
- Added user-scalable=no, maximum-scale=1 to viewport meta tag to prevent
pinch-to-zoom in standalone PWA mode
- Added overflow: hidden to html, body so any minimal content overflow
cannot make the body scrollable (belt-and-suspenders alongside
overscroll-behavior: none)
- Service worker cache bumped to v28/v27
Root cause 1 (scroll bleed): padding-top was applied to body in standalone
mode. Since .app-shell has height: 100dvh, body-padding shifted the shell
beyond the viewport bottom - enabling body-level scrolling.
Fix: moved padding-top from body to .app-shell in the standalone media query.
Root cause 2 (content overflow): fixed-height page containers
(Calendar, Shopping, Meals, Notes, Budget, Contacts) calculated height as
100dvh - nav-bottom - safe-area-inset-bottom, but never subtracted the top
safe area. This caused each page to overflow .app-content by exactly
env(safe-area-inset-top) pixels in standalone mode.
Fix: added --safe-area-inset-top token and subtracted it in all 6 height
calculations.
Service worker cache bumped to v27/v26.
- Add cursor:pointer to .modal-overlay so iOS Safari fires click events
on the backdrop (iOS ignores clicks on non-interactive divs without it)
- Add touchend fallback listener on overlay for belt-and-suspenders iOS support
- Enlarge close button from target-sm (32px) to target-md (40px) to meet
Apple touch-target guidelines; remove now-redundant ::before expansion
- Swipe-to-close now only activates from the top handle zone (< 48px) or
when the panel is scrolled to top, preventing accidental dismissal while
scrolling form content downward
- Calendar toolbar now wraps view buttons to a second row on viewports
< 580px so nav controls and label stay readable on all iOS devices
- Tasks toolbar title no longer bleeds over action buttons; uses
min-width:min-content so flex-wrap kicks in before overflow occurs
- Shopping list-header name gets flex:1/overflow:hidden so it truncates
cleanly instead of colliding with the clear-checked / delete buttons
- New optional recipe_url field in the meal modal (below Notes)
- Link icon appears on meal cards when a URL is set, opens in new tab
- DB migration v6: ALTER TABLE meals ADD COLUMN recipe_url TEXT
- API: recipe_url supported in POST /meals and PUT /meals/:id
- i18n: new keys recipeUrlLabel, recipeUrlPlaceholder, openRecipe (de, en, sv, it)
- New DB table shopping_categories (migration v5) seeds 9 default
categories with Lucide icons and sort_order
- Backend CRUD routes: GET/POST/PUT/DELETE /shopping/categories
plus PATCH /shopping/categories/reorder
- Category validation now uses DB instead of hardcoded constant;
items of deleted category are moved to the next available one
- Frontend shopping page loads categories from API, dropdown and
grouping reflect custom order dynamically
- Settings -> Shopping section: list categories with up/down buttons,
click-to-rename, delete with confirmation; add new categories inline
- i18n keys added in de/en/sv/it
Adds a small button on each kanban card that cycles the task status
(open → in_progress → done → open) without requiring drag-and-drop.
Useful for touch devices and kiosk browsers (e.g. Fully Kiosk Browser)
where drag-and-drop is unavailable. All four locales updated.
- Shopping category dropdown options now use CATEGORY_LABELS() for
translated display text instead of raw German internal keys
- rrule-ui.js now imports t() from /i18n.js; all hardcoded German
strings (freq options, weekday labels, form labels, unit labels)
replaced with i18n keys under the new 'rrule' namespace
- Added 'rrule' section to de.json and en.json with 22 new keys
Fixes#21
- tasks.js: add maybeShowSwipeHint (long loop, max 3x) - matches shopping.js pattern
- tasks.js: vibrate(15) on task status toggle
- oikos-locale-picker: show disabled/loading state for both reload and setLocale paths
- dashboard: show success toast after weather refresh (all 4 locales)
- dashboard: add semi-transparent FAB backdrop to signal open mode
global.window assignment doesn't expose 'window' as an identifier in ESM modules
in Node.js 22. matchMedia() is a global in browsers - no window. prefix needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Locale-Picker: disable select + fade before location.reload() (system mode)
gives visual feedback before the page jumps
- Extract _validateField() helper from wireBlurValidation to avoid duplication
- Add validateAll(formContainer): validates all required fields on demand,
marks inline errors, focuses first invalid field
- tasks.js: call validateAll() at submit to catch untouched required fields
Swedish translation contributed by @olsson82 (PR #19), integrated with
minor corrections (dayShortSunday: Sun → Sön, amountLabel aligned with
v0.11.2, new v0.11.2 currency keys added).
Italian was already supported server-side but is now explicitly listed
in the locale picker alongside German, English, and Swedish.
Add household-wide currency preference for the budget section.
Users can select from 13 currencies (EUR, USD, GBP, SEK, NOK, DKK,
CHF, PLN, CZK, HUF, JPY, AUD, CAD) in Settings → Budget.
- preferences API (GET/PUT) now includes currency field
- budget page loads currency from preferences on render
- formatAmount() uses locale-aware Intl.NumberFormat with chosen currency
- settings page gains a Budget section with a currency select
- all three locales (de, en, it) updated with new i18n keys
min-height: 100dvh lets the shell grow unbounded, so app-content never
overflows and touch scroll has nothing to scroll. height: 100dvh constrains
the shell to exactly the viewport, making overflow-y: auto on app-content work.
- inputmode=decimal on budget amount input for correct decimal keyboard
- inputmode=numeric on rrule interval input for numeric keyboard
- autocomplete attributes on contacts form (name, tel, email, street-address)
- Replace hardcoded px values with design tokens (--space-0h, --space-px,
--space-1, --target-sm/md/lg) across layout, calendar, dashboard CSS
- Fix rgba(255,255,255,0.35) spinner border to use var(--color-glass-hover)
- Fix modal close icon from off-grid 18px to 16px
- Fix touch scrolling on mobile: add min-height: 0 and
-webkit-overflow-scrolling: touch to .app-content
- Add --color-text-on-accent token to tokens.css
- Migrate all hardcoded color:#fff/#ffffff to var(--color-text-on-accent) across all module stylesheets
- Fix toggle thumb background (#fff -> var(--color-surface)) for dark mode
- Migrate hardcoded transition durations to token vars (--transition-fast/base/slow)
- Replace off-grid spacing (3px, 5px) with space tokens (--space-0h, --space-1)
- Replace below-minimum font-size 9px with var(--text-xs) in calendar, dashboard, notes
- Replace hardcoded 2.5rem with var(--text-4xl) in weather widget
- Replace hardcoded box-shadow with var(--shadow-sm) in toggle thumb
- Replace 0.85em and #666 with type/color tokens in print styles