ShellPort is the seam between framework-free UI logic and the native shell.
Quick Start
1
Read the insets synchronously
2
React to the keyboard
keyboardHeightPx is also a synchronous snapshot, so a warm resume with the keyboard already up lays out correctly on mount.3
readKeyboardHeight is the single source of truth
One exported function computes the keyboard height, with the clamp and the zoom guard in one place.
4
createWebShell seeds the snapshot at construction
keyboardHeightPx is seeded, not declared = 0.What The Shell Provides
These are the capabilities a phone needs but a desktop window does not.The Two Adapters
Only
adapters/src/tauri may import @tauri-apps/*, and inside it only bridge.ts touches them. tools/depgraph.mjs enforces this, so a React Native port is one directory rather than an audit.
How It Works
The seed at construction and the live handler read through the same function, so the clamp-at-0 and the pinch-zoom guard cannot drift between the first frame and every frame after it.
The
keyboardHeightPx snapshot is seeded at construction, so a component mounting during a warm resume, or with a hardware or floating keyboard already up, lays out correctly on its first frame.
Secure External Links
openExternal must reject any scheme the allowlist rejects. In a webview, openExternal("javascript:...") is script execution in the app’s own origin — and the URL routinely comes from a model or a tool result.
The native counterpart
On desktop and web-only builds,readKeyboardHeight(view) is the entire source of the keyboard height. On iOS and Android the keyboard height, safe-area insets, lifecycle, and back-press come from the Native Shell instead — four Tauri events the webview subscribes to by string.
The two are complementary, not alternatives. The web shell’s keyboardHeightPx seed still runs at construction so the first frame lays out correctly; the native keyboard-height event feeds every update after it.
Executable Specification
Four conformance tests inadapters/src/conformance/contracts.test.ts pin the snapshot and the guard:
Common Patterns
The live handler and the seed share one function, so a hide is never swallowed.Best Practices
Seed the snapshot, do not start at 0
Seed the snapshot, do not start at 0
A property declared
= 0 and only updated by an event reproduces the exact bug it was added to fix: one frame at the wrong height, then a jump. Seed from readKeyboardHeight(view) at construction.Guard pinch-zoom in every shell you write
Guard pinch-zoom in every shell you write
scale > 1 separates zoom from a keyboard. A shell that omits the guard reports a phantom keyboard the moment the user zooms — most visibly at construction, on a page opened already zoomed.Read the height through one function
Read the height through one function
Keep the clamp and the guard in a single
readKeyboardHeight. Duplicating the subtraction in the seed and the handler lets the two drift apart.Store back handlers in an array
Store back handlers in an array
The most recently registered handler gets first refusal. A
Set has no defined order and ships a modal that closes the wrong screen.Never let insets become NaN
Never let insets become NaN
parseFloat("") is NaN, and calc(100vh - NaNpx) silently blanks the screen. Every inset path coerces unparseable input to 0.Flush on background
Flush on background
iOS can kill a suspended app with no further callback, so anything unflushed when
onLifecycleChanged reports background is lost.Related
Mobile Architecture
How the shell is injected at boot.
Capabilities & Gaps
The keyboard snapshot as a closed gap.
Native Shell
The Tauri events that feed the shell on iOS and Android.
Storage & Secrets
Where chats and API keys live.
The Two Seams
How the UI-shell seam is enforced.

