session and translates those messages into session.* calls. The bridge is a single injected global named mentra.
The entry point is small: create a React root, wrap it in <MentraProvider>, and call mentra.ready().
src/ui/main.tsx
@mentra/miniapp/ui re-exports everything (the mentra global types, the React hooks, <MentraProvider>, <MiniappHeader>), and @mentra/miniapp/react exports the hooks and components directly. There is no @mentra/sdk here and no hosted server: the UI never authenticates, never opens a port, and never owns a session.
The UI layer must not construct a
MiniappSession. That class is the background (JSContext) API. Inside a WebView its CONNECT never gets acked by the host’s UI router, so session.connect() times out with CONNECT_ACK timeout. UI code reaches the glasses only through mentra, and reads host state through the hooks below.The mentra global
The host injects mentra into every UI WebView before your content loads. It carries the typed bus to background plus the two lifecycle signals.
The two sides are asymmetric on purpose.
mentra.send from the UI buffers until ready(), while session.ui.send from the background drops silently when no WebView is open. See Talking between layers for the background side (session.ui.handle, on, send).
mentra.request rejects with an Error whose name is MentraRpcError (the handler threw), MentraRpcTimeoutError (the timeout elapsed), or AbortError (the signal aborted). Branch on err.name, not instanceof: these errors come from the WebView’s bare runtime scope, so instanceof checks are unreliable across the bridge.
Typed channels
Declare every channel once insrc/shared/channels.ts. Both halves import this file, so channel names and payload shapes are checked at compile time on both sides of the bridge. MentraTyped<Channels> types the global; importing the file for its declare global side effect is what wires the types in.
src/shared/channels.ts
mentra.request (and useRpc) infer the request and response types. send/on then reject that channel at compile time, and request rejects the broadcast channels.
React hooks
Each hook is a thin read over host-injected state or thementra bridge. The host injects safe-area and color-scheme values once before content loads, so those hooks read at mount and do not update at runtime (a theme or orientation change forces a reload).
useRpc recreates its AbortController per call: calling it again aborts the previous in-flight request (good for per-keystroke autocomplete), and unmount aborts everything pending. A caller-provided options.signal is merged with the internal one.
useCapabilities returns null by design. It does not construct a MiniappSession (which would time out in a WebView). When a UI page needs the glasses profile, read session.capabilities in a background session.ui.onOpen handler, session.ui.send it on a channel, and pick it up in the UI with mentra.on.Components
<MentraProvider>
The optional root provider. Today it keeps <html class="dark"> in sync with the host’s color scheme so shared theme CSS switches automatically. The toggle runs synchronously during render, so children paint with the right class on first mount and there is no theme flash. Pass syncColorScheme={false} if your app owns the dark class itself (for example when integrating a theming library).
<MiniappHeader>
A drop-in header that aligns with the host’s capsule menu, leaving the top-right clear. It has three slots: left, title, and right. A string title renders as a semantic <h1>; pass a node for custom markup. Set onBack to render a back chevron in the left slot (ignored if left is set). The component layers useCapsuleHeaderStyle underneath, so its UseCapsuleHeaderStyleOptions (leftPadding, rightGap, fallbackHeight, fallbackMarginTop) are accepted as props.
useCapsuleHeaderStyle directly when you want full control over header markup but want the alignment math done for you. For the capsule-menu rect, the safe-area insets, and where you may not place clickable content, see Safe Areas & Layout.
Next steps
Two-layer architecture
The bus model and how background and UI talk.
Safe areas & layout
Keep content clear of the capsule menu.
The session
The background-side
session your UI sends to.Interop & Actions
Let other miniapps call yours.

