Users can now toggle which meal types (breakfast, lunch, dinner, snack)
are displayed in the meal planner via a new Settings section. Preference
is stored household-wide in sync_config and applied as a filter on the
meals page. Includes preferences API, i18n (DE/EN/IT), and Settings UI.
View mode (list/kanban) is now saved to localStorage and restored on
page load. URL parameter ?view=kanban takes precedence, enabling tablet
kiosk setups to default to Kanban view. Toggle buttons reflect the
active view correctly on initial render.
Closes#17
New tasks default to "none" priority instead of "medium". Tasks with no
priority hide the badge in list and dashboard views, reducing visual noise
for routine items. Includes DB migration v4 and i18n keys (de, en, it).
Closes#15
Three root causes fixed:
1. Double safe-area padding: pwa.css set padding-top/bottom on body
globally, but page containers already account for safe-area-inset
in their height calculations. Removed body vertical padding (kept
only in standalone media query for padding-top).
2. Wrong nav token: all page containers used --nav-height-mobile (56px)
instead of --nav-bottom-height (68px = 56px scroll + 12px dots),
causing 12px of content to render behind the bottom nav.
3. Scroll bleed: fixed-height page containers lacked overflow:hidden,
allowing scroll events to propagate to the body. Added
overscroll-behavior-y:contain on app-content globally.
Fixes#16
Native browser prompt() is unreliable on mobile browsers and PWAs,
often requiring multiple clicks to close. Replace all prompt() calls
with custom promptModal() and selectModal() functions that use the
existing modal system with proper focus management and animations.
Affected pages: shopping (create/rename list), tasks (add subtask),
meals (choose shopping list).
Fixes#12
Show shopping lists with open items directly on the dashboard.
Each list displays a progress bar, the first few unchecked items,
and a "+N more" overflow indicator. Widget only appears when there
are lists with open items.
Backend: new shoppingLists query in /api/v1/dashboard (up to 3 lists,
6 open items each). Frontend: renderShoppingLists() widget following
existing widget pattern. CSS: compact list/progress/item styles.
i18n: shoppingMore key added to de/en/it.
Requested in discussion #9
On mobile, closeModal() relies on the CSS animationend event to call
_doClose(). When the animation does not fire (prefers-reduced-motion,
tab switch, browser quirk), the modal stays stuck and the user cannot
dismiss it. A 300ms fallback timer now guarantees cleanup runs.
Reported in discussion #9
Category group headers in tasks and bar chart labels / transaction meta
in budget were showing raw German database keys instead of going through
CATEGORY_LABELS() i18n mapping.
Closes#11
Increase font-size to 16px on mobile for shopping quick-add inputs,
notes search, and contacts search. Desktop breakpoint restores compact
sizes. Move 9 page-specific stylesheets from index.html to on-demand
loading in router.js, reducing initial CSS payload.
- Rename #page-content to #main-content so skip-to-content link
targets the semantic <main> landmark
- Add sr-only priority labels to dashboard task items for screen
readers (WCAG 1.4.1 color-not-only)
- Replace hardcoded hex in greeting gradient with accent tokens
so dark mode themes the banner correctly
- Replace hardcoded gap: 2px with --space-0h token
- Bump version to 0.7.2
- Extract shared esc() utility (public/utils/html.js) replacing 8
duplicate escHtml() functions across all page modules
- Apply HTML escaping to all user-controlled data in innerHTML
templates: titles, names, locations, descriptions, colors, notes
content, weather data, autocomplete suggestions
- Remove user-scalable=no and maximum-scale=1 from viewport meta
tag, restoring pinch-to-zoom for WCAG 1.4.4 compliance
- Bump version to 0.7.1
The `date` import from validate.js shadowed the `date` field from
req.body, so SQLite received a function reference instead of a string
when updating a budget entry - causing a TypeError.
Fix by aliasing the import to `validateDate` and adding `date` to
the req.body destructuring.
Closes#8
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Fix stored XSS in tasks (titles/subtasks) and settings (member list)
by applying escHtml(). Harden trust proxy to loopback default, add
OAuth state parameter for Google Calendar CSRF protection, sanitize
CSV export against formula injection, invalidate sessions on user
deletion, restrict usernames to alphanumeric chars, and require admin
role for calendar sync triggers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace all — with - in all source files (JS, CSS, HTML, JSON,
Markdown) for consistency and readability.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add complete Italian translation (497 keys) based on PR #7 by
@albanobattistella. Fixed filename from "it. json" to "it.json" and
registered Italian in SUPPORTED_LOCALES and the locale picker component.
Co-Authored-By: albanobattistella <34811668+albanobattistella@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root causes:
1. parseRRule did not strip the "RRULE:" prefix stored by the ICS parser,
causing all recurrence rules from CalDAV sync to silently fail parsing
2. YEARLY frequency (used by birthday events) was not supported
3. expandRecurringEvents filtered instances only by start date, missing
multi-day events that start before the view window but span into it
4. All-day recurring instances got datetime end values instead of date-only
Fixes#5 (follow-up from @tschig)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
All-day events showed on the correct day plus the next day because ICS
DTEND for VALUE=DATE is exclusive (RFC 5545) but was treated as inclusive.
Multi-day events using DURATION instead of DTEND were missing entirely.
Birthday calendars were explicitly filtered out during Apple Calendar sync.
Closes#5
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Apple Calendar sync hardcoded created_by=1 which fails when no user
with ID 1 exists, causing every single event import to fail silently.
Now dynamically resolves the first available user. Also syncs all
calendars instead of only the first one, adds the missing cfgDel helper,
and gracefully skips unreachable calendars.
Fixes#4
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The hex-encoded encryption key (x'...') is not valid as a bare PRAGMA
value in better-sqlite3. Wrapping it in double quotes produces valid
SQLCipher PRAGMA syntax.
Fixes#3
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix SQLCipher PRAGMA key interpolation (hex-encode key to prevent crash on single quotes)
- Enforce min password length (8 chars) on admin user creation
- Add length bounds on username/display_name and login inputs
- Invalidate other sessions on password change
- Multi-stage Docker build (exclude build tools from runtime)
- Exclude docs/ from Docker image
- Consolidate dotenv.config() to single entry point
- Document flat family authorization model in SECURITY.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix [Unreleased] compare link (v0.5.1→v0.5.2), add missing [0.5.2]
compare link, remove phantom social-preview.html from .dockerignore.
Add full repo audit document.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Cookies were sent without Secure flag outside of production (NODE_ENV check).
New logic: secure=true by default; set SESSION_SECURE=false in .env to
allow HTTP explicitly (local dev without reverse proxy). Affects session
cookie, CSRF cookie in login handler, and CSRF middleware.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rate-limit SPA fallback route (missing rate limiting on fs access)
- Add csrfMiddleware to all state-changing auth routes (logout, create
user, change password, delete user) — previously bypassed global CSRF
middleware due to router registration order
- Fix incomplete vCard escaping: escape backslashes before other special
characters to prevent injection via contact fields
- Restrict CI GITHUB_TOKEN to contents: read (least privilege)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uses visualViewport resize event to detect keyboard state (viewport height
< 75% of window height). Sets body.keyboard-visible class; CSS hides
.fab and .page-fab via visibility:hidden on screens < 1024px.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduced --nav-bottom-height token (56px scroll + 12px dots indicator = 68px)
so that toast-container bottom and app-content padding-bottom both account for
the full nav-bottom height including the page-dots indicator.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Admin can now enter CalDAV URL, Apple-ID and app-specific password
directly in Settings; credentials are tested live before saving and
stored in sync_config (take precedence over .env); disconnect clears
DB-stored credentials without server restart. Auto-sync interval
(15 min, configurable via SYNC_INTERVAL_MINUTES) was already in place.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pointer Events-based drag & drop (touch + mouse compatible):
- Ghost element follows pointer; drops on empty slots move the meal,
drops on occupied slots swap both meals via concurrent PUT requests
- prefers-reduced-motion: no ghost animation, interaction still works
- Suppress-click guard prevents accidental edit modal after drag
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
expandRecurringEvents() iterates from the event's original start date,
generating all occurrences within the requested window using the existing
nextOccurrence() service (max 1000 iterations). The SQL query is extended
to also fetch recurring events that started before the window. Event
duration is preserved across instances. Virtual instances carry
is_recurring_instance=1 and are shown with a repeat icon in the agenda
view. /upcoming expands across a 90-day forward window.
Closes BL-01.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds schema migration v3 (recurrence_parent_id column + budget_recurrence_skipped
table). On every GET /api/v1/budget, the server checks all recurring originals
(is_recurring=1, no parent) and creates missing instances for the requested month
using the same day-of-month (clamped to the last day). Deleted instances are
recorded in budget_recurrence_skipped so they are not recreated on the next visit.
Generated instances are shown with a ↩ indicator in the transaction list.
Closes BL-05.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each summary card (Einnahmen, Ausgaben, Saldo) now shows a trend line
comparing the current month to the previous one. The previous month's
summary is fetched in parallel via the existing /budget/summary endpoint,
so there is no extra round-trip latency. Positive deltas render in green
(▲), negative in red (▼), unchanged in neutral grey (—).
Closes BL-02.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- notes.js (Critical): move grid click listener from renderGrid() to
render() — was re-registered on every save/pin/delete, causing
multiple API calls per user action after several interactions
- dashboard.js (Major): introduce AbortController (_fabController) so
the anonymous document click listener from initFab() is cancelled on
each new render() cycle; also remove the redundant initFab() call on
the skeleton render
- layout.css (Major): extend .label selector to include .form-label,
covering usage in notes.js and settings.js without a mass-rename
- test-modal-utils.js (Major): 12 unit tests for wireBlurValidation,
btnSuccess, btnError; registered as test:modal-utils in package.json
- notes.js (Minor): add btnError() shake feedback to save error handler
- calendar.js (Minor): add popup.isConnected guard to closePopup so
the listener self-removes correctly after navigation without a click
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>