Summary
When BB is added to the iOS/iPadOS home screen and launched as a standalone PWA, the whole app shell renders shorter than the viewport: dead space along the bottom, and content pushed up so the top runs under the status bar. It is not a cosmetic offset — the shell never fills the screen in either orientation.
The same build is completely fine in a normal Safari tab. It only reproduces when launched from the home-screen icon.
Environment
- BB 0.33.0, self-hosted, reached over Tailscale Serve (HTTPS,
atum-vps.<tailnet>.ts.net)
- Reproduces on both iPhone and iPad, current iOS/iPadOS
- Reproduces against unmodified upstream UI —
git diff origin/main <our branch> -- apps/app packages/shared-ui is empty on our fork, so no local styling is involved
- Long-standing; not a recent regression
Steps to reproduce
- Open BB in Safari on an iPhone or iPad.
- Share → Add to Home Screen.
- Launch from the home-screen icon (standalone mode only engages from the icon, not from a Safari tab).
Expected: shell fills the screen, content clears the status bar and home indicator.
Actual: shell is shorter than the screen. Empty band at the bottom; content shifted up and colliding with the status bar at the top.
The useful signal
The bottom gap is visibly wider in portrait than in landscape.
Portrait reserves more room for the home indicator than landscape does, so a gap whose height tracks orientation points at env(safe-area-inset-bottom) being accounted for twice — once by the standalone viewport itself and again by padding inside the layout.
Consistent with that: in a Safari tab the insets are effectively zero, there is nothing to double-reserve, and the layout is correct. That is why this only ever shows up in standalone.
Rotating the device does not correct it, so this is not a stale height captured at mount — it is wrong at every layout pass, by an amount that tracks the inset.
Where I think it comes from
Two independently-computed viewport heights are nested inside an ancestor that clips rather than scrolls:
apps/app/src/components/ui/sidebar.tsx:1175 — SidebarInset <main> sets min-h-svh
apps/app/src/components/layout/AppLayout.tsx:1082 — its only child independently sets h-[100dvh] plus all four env(safe-area-inset-*) paddings
apps/app/src/app.css:10-24 — html.bb-app-shell-root / body.bb-app-shell are locked to height: 100%; overflow: hidden, so any excess is clipped and unreachable rather than scrollable
apps/app/index.html sets viewport-fit=cover with apple-mobile-web-app-status-bar-style: black-translucent, so content is deliberately painted under the status bar and the inset padding is the only thing pushing it back. That makes the shell's height arithmetic load-bearing in standalone in a way it is not in a browser tab.
One of those two heights looks redundant. I suspect the child should fill its parent rather than re-assert a full-viewport height, but I could not determine which one is actually wrong without measurements from the device.
What I could not verify
I have not confirmed which of the two heights is miscomputing, because env(safe-area-inset-*) cannot be emulated in Chrome DevTools or any headless runner — the only surface that reproduces it is a physical device in standalone mode. I did not want to guess at a fix and submit it untested, which is why this is an issue rather than a PR.
Happy to run a diagnostic build on real hardware and report 100svh / 100lvh / 100dvh / innerHeight / visualViewport.height and the four resolved inset values in both orientations, if that would help pin it down. Also glad to turn this into a PR if you can say which of the two heights is meant to be authoritative.
Summary
When BB is added to the iOS/iPadOS home screen and launched as a standalone PWA, the whole app shell renders shorter than the viewport: dead space along the bottom, and content pushed up so the top runs under the status bar. It is not a cosmetic offset — the shell never fills the screen in either orientation.
The same build is completely fine in a normal Safari tab. It only reproduces when launched from the home-screen icon.
Environment
atum-vps.<tailnet>.ts.net)git diff origin/main <our branch> -- apps/app packages/shared-uiis empty on our fork, so no local styling is involvedSteps to reproduce
Expected: shell fills the screen, content clears the status bar and home indicator.
Actual: shell is shorter than the screen. Empty band at the bottom; content shifted up and colliding with the status bar at the top.
The useful signal
The bottom gap is visibly wider in portrait than in landscape.
Portrait reserves more room for the home indicator than landscape does, so a gap whose height tracks orientation points at
env(safe-area-inset-bottom)being accounted for twice — once by the standalone viewport itself and again by padding inside the layout.Consistent with that: in a Safari tab the insets are effectively zero, there is nothing to double-reserve, and the layout is correct. That is why this only ever shows up in standalone.
Rotating the device does not correct it, so this is not a stale height captured at mount — it is wrong at every layout pass, by an amount that tracks the inset.
Where I think it comes from
Two independently-computed viewport heights are nested inside an ancestor that clips rather than scrolls:
apps/app/src/components/ui/sidebar.tsx:1175—SidebarInset<main>setsmin-h-svhapps/app/src/components/layout/AppLayout.tsx:1082— its only child independently setsh-[100dvh]plus all fourenv(safe-area-inset-*)paddingsapps/app/src/app.css:10-24—html.bb-app-shell-root/body.bb-app-shellare locked toheight: 100%; overflow: hidden, so any excess is clipped and unreachable rather than scrollableapps/app/index.htmlsetsviewport-fit=coverwithapple-mobile-web-app-status-bar-style: black-translucent, so content is deliberately painted under the status bar and the inset padding is the only thing pushing it back. That makes the shell's height arithmetic load-bearing in standalone in a way it is not in a browser tab.One of those two heights looks redundant. I suspect the child should fill its parent rather than re-assert a full-viewport height, but I could not determine which one is actually wrong without measurements from the device.
What I could not verify
I have not confirmed which of the two heights is miscomputing, because
env(safe-area-inset-*)cannot be emulated in Chrome DevTools or any headless runner — the only surface that reproduces it is a physical device in standalone mode. I did not want to guess at a fix and submit it untested, which is why this is an issue rather than a PR.Happy to run a diagnostic build on real hardware and report
100svh/100lvh/100dvh/innerHeight/visualViewport.heightand the four resolved inset values in both orientations, if that would help pin it down. Also glad to turn this into a PR if you can say which of the two heights is meant to be authoritative.