fix(pwa): fix UI overlap, scroll bleed and wrong nav height on iOS
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
This commit is contained in:
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
## [0.8.1] - 2026-04-04
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "oikos",
|
"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.",
|
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
|
||||||
"main": "server/index.js",
|
"main": "server/index.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -15,9 +15,10 @@
|
|||||||
.budget-page {
|
.budget-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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);
|
max-width: var(--content-max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
.calendar-page {
|
.calendar-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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);
|
max-width: var(--content-max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -15,9 +15,10 @@
|
|||||||
.contacts-page {
|
.contacts-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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);
|
max-width: var(--content-max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
* -------------------------------------------------------- */
|
* -------------------------------------------------------- */
|
||||||
.dashboard {
|
.dashboard {
|
||||||
padding: var(--space-4);
|
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);
|
max-width: var(--content-max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
@@ -883,7 +883,7 @@
|
|||||||
* -------------------------------------------------------- */
|
* -------------------------------------------------------- */
|
||||||
.fab-container {
|
.fab-container {
|
||||||
position: fixed;
|
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);
|
right: var(--space-4);
|
||||||
z-index: calc(var(--z-nav) - 1);
|
z-index: calc(var(--z-nav) - 1);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -97,6 +97,7 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom));
|
padding-bottom: calc(var(--nav-bottom-height) + var(--safe-area-inset-bottom));
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
overscroll-behavior-y: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Sidebar auf Mobile verstecken */
|
/* Sidebar auf Mobile verstecken */
|
||||||
@@ -217,7 +218,7 @@
|
|||||||
* -------------------------------------------------------- */
|
* -------------------------------------------------------- */
|
||||||
.page-fab {
|
.page-fab {
|
||||||
position: fixed;
|
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);
|
right: var(--space-4);
|
||||||
width: 52px;
|
width: 52px;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
@@ -865,7 +866,7 @@
|
|||||||
/* FAB (Floating Action Button) */
|
/* FAB (Floating Action Button) */
|
||||||
.fab {
|
.fab {
|
||||||
position: fixed;
|
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);
|
right: var(--space-4);
|
||||||
width: 52px;
|
width: 52px;
|
||||||
height: 52px;
|
height: 52px;
|
||||||
|
|||||||
@@ -15,9 +15,10 @@
|
|||||||
.meals-page {
|
.meals-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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);
|
max-width: var(--content-max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
|
|||||||
@@ -15,9 +15,10 @@
|
|||||||
.notes-page {
|
.notes-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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);
|
max-width: var(--content-max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ html, body {
|
|||||||
-webkit-tap-highlight-color: transparent;
|
-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 {
|
body {
|
||||||
padding-top: env(safe-area-inset-top);
|
|
||||||
padding-bottom: env(safe-area-inset-bottom);
|
|
||||||
padding-left: env(safe-area-inset-left);
|
padding-left: env(safe-area-inset-left);
|
||||||
padding-right: env(safe-area-inset-right);
|
padding-right: env(safe-area-inset-right);
|
||||||
}
|
}
|
||||||
@@ -61,4 +62,9 @@ nav,
|
|||||||
body {
|
body {
|
||||||
padding-top: env(safe-area-inset-top);
|
padding-top: env(safe-area-inset-top);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Kein Scroll-Bleed - Content bleibt in seinem Container */
|
||||||
|
.app-content {
|
||||||
|
overscroll-behavior-y: contain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,10 @@
|
|||||||
.shopping-page {
|
.shopping-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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);
|
max-width: var(--content-max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
* -------------------------------------------------------- */
|
* -------------------------------------------------------- */
|
||||||
.tasks-page {
|
.tasks-page {
|
||||||
padding: var(--space-4);
|
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);
|
max-width: var(--content-max-width);
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -12,9 +12,9 @@
|
|||||||
* API: Immer Netzwerk (kein Caching von Nutzerdaten)
|
* API: Immer Netzwerk (kein Caching von Nutzerdaten)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const SHELL_CACHE = 'oikos-shell-v22';
|
const SHELL_CACHE = 'oikos-shell-v23';
|
||||||
const PAGES_CACHE = 'oikos-pages-v22';
|
const PAGES_CACHE = 'oikos-pages-v23';
|
||||||
const ASSETS_CACHE = 'oikos-assets-v22';
|
const ASSETS_CACHE = 'oikos-assets-v23';
|
||||||
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE];
|
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE];
|
||||||
|
|
||||||
// App-Shell: sofort benötigt für ersten Render
|
// App-Shell: sofort benötigt für ersten Render
|
||||||
|
|||||||
Reference in New Issue
Block a user