app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit d806a4de6c617600d8a82f8a3c38fe3a731a6c69
parent a43b354ac92b930a627fe134914ea248e0340d6d
Author: triesap <triesap@radroots.dev>
Date:   Thu, 22 Jan 2026 16:25:44 +0000

app: harden native app shell

- add app shell ids for core layout anchors

- mark scrollable mains for overscroll control

- apply native scroll/touch base rules

- align splash, setup, and home semantics

Diffstat:
Mapp/app.css | 23+++++++++++++++++++++++
Mapp/index.html | 2+-
Mapp/src/app.rs | 27++++++++++++++++-----------
3 files changed, 40 insertions(+), 12 deletions(-)

diff --git a/app/app.css b/app/app.css @@ -19,12 +19,17 @@ font-family: var(--font-sans); background: var(--bg-app); color: var(--text-primary); + height: 100%; + overscroll-behavior: none; + touch-action: manipulation; } body { min-height: 100dvh; margin: 0; background: var(--bg-app); + height: 100%; + overscroll-behavior: none; } * { @@ -45,7 +50,25 @@ opacity: 0.5; pointer-events: none; } +} +@layer base { + #app-root { + min-height: 100dvh; + height: 100%; + } + + #app-shell { + min-height: 100dvh; + display: flex; + flex-direction: column; + } + + [data-app-scroll] { + overflow-y: auto; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; + } } @layer components { diff --git a/app/index.html b/app/index.html @@ -16,5 +16,5 @@ data-wasm-opt-params="--enable-bulk-memory-opt" /> </head> - <body></body> + <body id="app-root"></body> </html> diff --git a/app/src/app.rs b/app/src/app.rs @@ -178,7 +178,10 @@ fn app_health_check_delay_ms() -> u32 { #[component] fn SplashPage() -> impl IntoView { view! { - <main style="min-height:100vh;background:white;display:flex;align-items:center;justify-content:center;"> + <main + id="app-splash" + style="min-height:100dvh;background:white;display:flex;align-items:center;justify-content:center;" + > </main> } } @@ -260,10 +263,10 @@ fn SetupPage() -> impl IntoView { }); }; view! { - <main class="min-h-[100dvh] w-full px-6 pt-10 pb-16"> + <main id="app-setup" data-app-scroll class="min-h-[100dvh] w-full px-6 pt-10 pb-16"> {move || match setup_step.get() { RadrootsAppSetupStep::Intro => view! { - <section class="flex flex-col w-full gap-6"> + <section id="app-setup-intro" class="flex flex-col w-full gap-6"> <header class="flex flex-col gap-3"> <p class="font-sans text-sm uppercase tracking-[0.14em] text-ly0-gl-label"> "Radroots" @@ -289,7 +292,7 @@ fn SetupPage() -> impl IntoView { </section> }.into_any(), RadrootsAppSetupStep::KeyChoice => view! { - <section class="flex flex-col w-full gap-4"> + <section id="app-setup-key-choice" class="flex flex-col w-full gap-4"> <header class="flex flex-col gap-2"> <p class="font-sans text-sm uppercase tracking-[0.14em] text-ly0-gl-label"> "Setup" @@ -405,7 +408,7 @@ fn HomePage() -> impl IntoView { } }; view! { - <main> + <main id="app-home" data-app-scroll> <div>"app"</div> <div style="margin-top: 8px; display: flex; align-items: center; gap: 8px;"> <span @@ -767,11 +770,12 @@ fn AppShell() -> impl IntoView { } fallback=|| view! { <SplashPage /> } > - <Show - when=move || setup_required.get() == Some(false) - fallback=|| view! { <SetupPage /> } - > - <nav style="display:flex;gap:12px;margin-bottom:12px;"> + <Show + when=move || setup_required.get() == Some(false) + fallback=|| view! { <SetupPage /> } + > + <div id="app-shell"> + <nav id="app-nav" aria-label="Primary" style="display:flex;gap:12px;margin-bottom:12px;"> <A href="/" exact=true>"home"</A> <A href="/logs">"logs"</A> <A href="/ui">"ui"</A> @@ -785,8 +789,9 @@ fn AppShell() -> impl IntoView { <Route path=path!("settings") view=RadrootsAppSettingsPage /> <Route path=path!("setup") view=SetupPage /> </Routes> - </Show> + </div> </Show> + </Show> } }