diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index e1ebc69..9bacd80 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -1,6 +1,7 @@ import { Navigate, Outlet, Route, Routes } from "react-router-dom"; import { AdminAuthProvider } from "./features/admin/AdminAuthContext"; import { RequireAdmin } from "./features/admin/RequireAdmin"; +import { UpdateBanner } from "./features/app-update/UpdateBanner"; import { RedirectIfAuthenticated } from "./features/auth/RedirectIfAuthenticated"; import { RequireAuth } from "./features/auth/RequireAuth"; import { AdminLayout } from "./layouts/AdminLayout"; @@ -71,111 +72,120 @@ import { ShoppingListPage } from "./pages/shopping-list/ShoppingListPage"; * never nested under `RequireAuth`. `/admin/login` is the only * unauthenticated admin route; everything else sits under `RequireAdmin` + * `AdminLayout`. + * + * {@link UpdateBanner} is mounted here, outside ``, rather than in + * `AppLayout` — the native app-update check (issue #66) must run on every + * app start, session or not, and `AppLayout` only mounts once a visitor is + * past `RequireAuth`. It's a `position: fixed` strip and renders nothing on + * the web, so mounting it app-wide costs the routed pages no layout. */ export function App() { return ( - - - - - } - > - } /> - {/* Same component for both — a master-detail layout, not a + <> + + + + + + } + > + } /> + {/* Same component for both — a master-detail layout, not a navigation to a separate page: the tab bar + table stay mounted, only RecipesPage's detail panel changes with `:id` (see RecipesPage.tsx). */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - } /> - - - - } - > - } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + - - + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + } /> + + + } > - } /> - } /> - } /> - } /> - } /> + } /> + + + + } + > + } /> + } /> + } /> + } /> + } /> + - - } /> - + } /> + + ); } diff --git a/apps/web/src/features/app-update/update-banner.scss b/apps/web/src/features/app-update/update-banner.scss index 3264c51..2e0e4ff 100644 --- a/apps/web/src/features/app-update/update-banner.scss +++ b/apps/web/src/features/app-update/update-banner.scss @@ -1,18 +1,28 @@ -// Styles for UpdateBanner — a discreet, dismissible strip above the routed -// page content (see AppLayout.tsx). Uses --color-warning the same way -// source-item-preview__hint does elsewhere in the app: "worth noticing", +// Styles for UpdateBanner — a discreet, dismissible strip pinned to the top +// of the screen. Mounted app-wide in App.tsx (outside ``) so the +// native update check runs session or not, hence `position: fixed` rather +// than the old in-flow placement inside `.app-content`: this way it never +// enters any routed page's layout / height math (`.app-layout` is +// `height: 100vh; overflow: hidden`), it just overlays. Native-only — +// renders nothing on the web (see UpdateBanner.tsx). Uses --color-warning +// the same way source-item-preview__hint does elsewhere: "worth noticing", // not an error. .update-banner { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 100; display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: var(--space-sm); - margin-bottom: var(--space-md); padding: var(--space-sm) var(--space-md); + // Clears the Android status bar when the WebView draws under it. + padding-top: calc(var(--space-sm) + env(safe-area-inset-top, 0px)); background: color-mix(in srgb, var(--color-warning) 12%, var(--color-surface)); - border: 1px solid var(--color-warning); - border-radius: var(--radius-base); + border-bottom: 1px solid var(--color-warning); font-size: var(--font-size-sm); &__text { diff --git a/apps/web/src/layouts/AppLayout.tsx b/apps/web/src/layouts/AppLayout.tsx index d0ad9e9..7ad72db 100644 --- a/apps/web/src/layouts/AppLayout.tsx +++ b/apps/web/src/layouts/AppLayout.tsx @@ -2,7 +2,6 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { NavLink, Outlet, useLocation, useNavigate } from "react-router-dom"; import { BottomSheet } from "../components/ui/BottomSheet"; -import { UpdateBanner } from "../features/app-update/UpdateBanner"; import { useAuth } from "../features/auth/AuthContext"; import { useIsMobileViewport } from "../lib/useIsMobileViewport"; import "./AppLayout.scss"; @@ -148,9 +147,9 @@ export function AppLayout() { )}
- {/* Renders nothing on the web build (native-only check) or once no - newer version is known — see UpdateBanner's own doc comment. */} - + {/* `UpdateBanner` used to sit here, but the native update check + (issue #66) must run session or not — it's mounted app-wide in + `App.tsx` now, as a `position: fixed` strip. */}