diff --git a/CHANGELOG.md b/CHANGELOG.md index d1a70fb..95ac2e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.8.2] - 2026-04-04 + +### Fixed +- Fix UI overlap and scroll bleed on iOS PWA - remove double safe-area padding from body that caused content to shift under status bar (#16) +- Fix page containers using wrong nav height token (56px instead of 68px including dot indicator), causing content to render behind bottom nav on all pages +- Add `overflow: hidden` to all fixed-height page containers (shopping, meals, notes, budget, contacts) to prevent scroll bleed +- Add `overscroll-behavior-y: contain` to app-content to prevent rubber-banding scroll propagation +- Fix FAB position on all pages to account for full bottom nav height including dot indicator +- Bump service worker cache version to v23 + ## [0.8.1] - 2026-04-04 ### Fixed diff --git a/package.json b/package.json index d860142..dd6944b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.8.1", + "version": "0.8.2", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module", diff --git a/public/styles/budget.css b/public/styles/budget.css index f1146c3..21e489c 100644 --- a/public/styles/budget.css +++ b/public/styles/budget.css @@ -15,9 +15,10 @@ .budget-page { display: flex; flex-direction: column; - height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom)); + height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom)); max-width: var(--content-max-width); margin: 0 auto; + overflow: hidden; } @media (min-width: 1024px) { diff --git a/public/styles/calendar.css b/public/styles/calendar.css index f1d541d..bea0a61 100644 --- a/public/styles/calendar.css +++ b/public/styles/calendar.css @@ -15,7 +15,7 @@ .calendar-page { display: flex; flex-direction: column; - height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom)); + height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom)); max-width: var(--content-max-width); margin: 0 auto; overflow: hidden; diff --git a/public/styles/contacts.css b/public/styles/contacts.css index c4796f2..ac284f1 100644 --- a/public/styles/contacts.css +++ b/public/styles/contacts.css @@ -15,9 +15,10 @@ .contacts-page { display: flex; flex-direction: column; - height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom)); + height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom)); max-width: var(--content-max-width); margin: 0 auto; + overflow: hidden; } @media (min-width: 1024px) { diff --git a/public/styles/dashboard.css b/public/styles/dashboard.css index 820c1e5..c33d20f 100644 --- a/public/styles/dashboard.css +++ b/public/styles/dashboard.css @@ -16,7 +16,7 @@ * -------------------------------------------------------- */ .dashboard { padding: var(--space-4); - padding-bottom: calc(var(--nav-height-mobile) + var(--safe-area-inset-bottom) + var(--space-16)); + padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom) + var(--space-16)); max-width: var(--content-max-width); margin: 0 auto; } @@ -883,7 +883,7 @@ * -------------------------------------------------------- */ .fab-container { position: fixed; - bottom: calc(var(--nav-height-mobile) + 24px + var(--safe-area-inset-bottom)); + bottom: calc(var(--nav-bottom-height) + 24px + var(--safe-area-inset-bottom)); right: var(--space-4); z-index: calc(var(--z-nav) - 1); display: flex; diff --git a/public/styles/layout.css b/public/styles/layout.css index b000ccb..da81754 100644 --- a/public/styles/layout.css +++ b/public/styles/layout.css @@ -97,6 +97,7 @@ flex: 1; padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom)); overflow-y: auto; + overscroll-behavior-y: contain; } /* Sidebar auf Mobile verstecken */ @@ -217,7 +218,7 @@ * -------------------------------------------------------- */ .page-fab { position: fixed; - bottom: calc(var(--nav-height-mobile) + 24px + var(--safe-area-inset-bottom)); + bottom: calc(var(--nav-bottom-height) + 24px + var(--safe-area-inset-bottom)); right: var(--space-4); width: 52px; height: 52px; @@ -865,7 +866,7 @@ /* FAB (Floating Action Button) */ .fab { position: fixed; - bottom: calc(var(--nav-height-mobile) + var(--safe-area-inset-bottom) + var(--space-4)); + bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom) + var(--space-4)); right: var(--space-4); width: 52px; height: 52px; diff --git a/public/styles/meals.css b/public/styles/meals.css index 93503ce..d9fc5c5 100644 --- a/public/styles/meals.css +++ b/public/styles/meals.css @@ -15,9 +15,10 @@ .meals-page { display: flex; flex-direction: column; - height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom)); + height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom)); max-width: var(--content-max-width); margin: 0 auto; + overflow: hidden; } @media (min-width: 1024px) { diff --git a/public/styles/notes.css b/public/styles/notes.css index 0eb393c..af57443 100644 --- a/public/styles/notes.css +++ b/public/styles/notes.css @@ -15,9 +15,10 @@ .notes-page { display: flex; flex-direction: column; - height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom)); + height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom)); max-width: var(--content-max-width); margin: 0 auto; + overflow: hidden; } @media (min-width: 1024px) { diff --git a/public/styles/pwa.css b/public/styles/pwa.css index 32ec09d..64f2025 100644 --- a/public/styles/pwa.css +++ b/public/styles/pwa.css @@ -15,10 +15,11 @@ html, body { -webkit-tap-highlight-color: transparent; } -/* ── Safe Area Insets (Notch, Dynamic Island, Gesture Bar) ── */ +/* ── Safe Area Insets (Notch, Dynamic Island, Gesture Bar) ── + * Nur horizontale Safe Areas auf body - vertikale werden von + * Standalone-Modus (padding-top) und Nav/Seiten (padding-bottom) gehandhabt. + * Kein body padding-top/bottom hier, sonst doppelt mit Seiten-Berechnungen. */ body { - padding-top: env(safe-area-inset-top); - padding-bottom: env(safe-area-inset-bottom); padding-left: env(safe-area-inset-left); padding-right: env(safe-area-inset-right); } @@ -61,4 +62,9 @@ nav, body { padding-top: env(safe-area-inset-top); } + + /* Kein Scroll-Bleed - Content bleibt in seinem Container */ + .app-content { + overscroll-behavior-y: contain; + } } diff --git a/public/styles/shopping.css b/public/styles/shopping.css index 3fa499a..a587bef 100644 --- a/public/styles/shopping.css +++ b/public/styles/shopping.css @@ -15,9 +15,10 @@ .shopping-page { display: flex; flex-direction: column; - height: calc(100dvh - var(--nav-height-mobile) - var(--safe-area-inset-bottom)); + height: calc(100dvh - var(--nav-bottom-height) - var(--safe-area-inset-bottom)); max-width: var(--content-max-width); margin: 0 auto; + overflow: hidden; } @media (min-width: 1024px) { diff --git a/public/styles/tasks.css b/public/styles/tasks.css index 48c3464..8683639 100644 --- a/public/styles/tasks.css +++ b/public/styles/tasks.css @@ -14,7 +14,7 @@ * -------------------------------------------------------- */ .tasks-page { padding: var(--space-4); - padding-bottom: calc(var(--nav-height-mobile) + var(--safe-area-inset-bottom) + var(--space-16)); + padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom) + var(--space-16)); max-width: var(--content-max-width); margin: 0 auto; } diff --git a/public/sw.js b/public/sw.js index f00ca31..67b618b 100644 --- a/public/sw.js +++ b/public/sw.js @@ -12,9 +12,9 @@ * API: Immer Netzwerk (kein Caching von Nutzerdaten) */ -const SHELL_CACHE = 'oikos-shell-v22'; -const PAGES_CACHE = 'oikos-pages-v22'; -const ASSETS_CACHE = 'oikos-assets-v22'; +const SHELL_CACHE = 'oikos-shell-v23'; +const PAGES_CACHE = 'oikos-pages-v23'; +const ASSETS_CACHE = 'oikos-assets-v23'; const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE]; // App-Shell: sofort benötigt für ersten Render