Draft
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
📋 PR Review Helper📱 Mobile App Build❌ Build failed (commit 🕶️ ASG Client Build✅ Ready to test! (commit 🔀 Test Locallygh pr checkout 2512 |
- cloud/bun.lock: regenerated to match package.json (was out of date, failing --frozen-lockfile check) - miniapp-developer.tsx + LocalMiniappRuntime.ts: switch from @react-native-async-storage/async-storage (not in deps) to existing MMKV storage utility - miniapp-developer.tsx + miniapp-developer-scanner.tsx: replace useRouter from expo-router with useNavigationHistory (project convention) - Composer.test.ts + MicStateCoordinator.test.ts: rewrite for Jest instead of bun:test (mobile uses jest, not bun's test runner) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Local Miniapp Architecture — Discussion Doc
What this is
We're designing how third-party (and first-party) miniapps run on MentraOS. A miniapp is a small program that drives smart glasses — displays text, subscribes to transcription, responds to button presses, takes photos, etc. Today miniapps run as cloud apps: each miniapp is a Node/Bun server somewhere on the internet, the cloud routes events to it over WebSockets, the cloud routes display commands back down to the phone, the phone routes them to the glasses over BLE.
This doc is about whether we keep that model, replace it, or pick something in between — and what architecture gives us the best outcome given our constraints.
Scope clarification
This doc is about the new local miniapp SDK only. The existing
@mentra/sdk(cloud SDK) is not in scope here — it continues to work, both SDKs coexist during development, and@mentra/sdkwill eventually be deprecated. We're not trying to design "one SDK for everything." If the local SDK ends up sharing transport logic with the cloud SDK as a happy side effect, great. If not, that's fine too.Goals
What we're optimizing for, in priority order:
Reliability. Today's chain is
miniapp-server ⇄ cloud ⇄ phone ⇄ glasses. Four hops, four failure points, four sources of latency. Every extra hop is another place things break and another 50-200ms of delay. We want glasses that respond to events instantly and work when internet is flaky.Developer velocity. Hosting a server is friction — most developers don't want to provision and maintain one. Removing that friction helps adoption.
Modern-device performance. We don't need to run on 2014 devices. Target is iPhone 15 / Pixel 9 class. We do need to not overheat those phones with 3-5 miniapps running.
Requirements
opacity: 0tricks). Headless JS uses ~10-15MB; a backgrounded WebView uses ~25-30MB, drains battery if developers aren't disciplined about disabling animations, and has Apple-review baggage.openURL()to launch a UI), we assume every miniapp has a UI.Apple 4.7
Apple Guideline 4.7 covers "HTML5 and JavaScript mini apps." Apple updated 4.7 in November 2025 and launched a formal Mini Apps Partner Program (15% commission instead of 30% for qualifying mini app hosts). The guideline explicitly permits embedded JS runtimes and JavaScript miniapps, but with conditions:
These are product/legal requirements we owe regardless of runtime architecture. Worth being aware of when designing what we expose to miniapps.
Case study: Pebble (2012-2016)
Pebble solved essentially this problem 12 years ago. Third-party watch apps had two components: a C binary running on the watch, and a JavaScript "companion" running on the phone. The JS handled anything the watch couldn't — fetching weather, geolocation, WebSocket connections, settings storage. The C handled display and user input.
How they ran the JS on the phone:
JSContext+JSVirtualMachine), embedded headlessly in the Pebble mobile app. No WebView.XMLHttpRequest,localStorage,navigator.geolocation,setTimeout,WebSocket,console,Pebble.sendAppMessage()for talking to the watch.Pebble.openURL()opened a WebView pointing at a developer-hosted URL, user filled in a form, URL-scheme callback passed settings back to the phone JS. The config UI died when closed — that was fine, it wasn't driving the watch.Why this is worth studying:
What didn't work for them:
Pebble's architecture is a direct precedent for what we're considering. The engineering approach transfers almost directly.
Options
Option 1 — Current plan: Everything in WebViews, run always
Miniapps are static web bundles (HTML/CSS/JS) that load into WebViews inside the MentraOS app. The WebView stays mounted always (offscreen when backgrounded,
opacity: 0, notdisplay: none). The WebView drives the glasses through a postMessage bridge to the native MentraOS code.Pros:
fetch,WebSocket,IndexedDB, animations, canvasCons:
requestAnimationFrameloop burns phone battery even when the glasses display is offCost to build: mostly done (V1 in progress).
Option 2 — nodejs-mobile: Same SDK on phone and cloud, phone runs embedded Node
Embed the full Node.js runtime on the phone via
nodejs-mobile(community fork of the original Janea project). Each miniapp is still a@mentra/sdkNode app — same code that runs on a cloud server. The difference: instead of the developer hosting it, MentraOS embeds Node in the mobile app and runs the miniapp there.Pros:
fs,crypto, etc.Cons:
Fallback story: trivial. Miniapps ARE cloud apps — move them to a server, done.
Cost to build: 4-8 weeks to integrate nodejs-mobile + build the lifecycle management + design the UI hand-off.
Option 3 — Pebble-style: headless JSC/Hermes + WebView for UI on demand
Embed JavaScriptCore (on iOS, free — it's a system framework) and Hermes (on Android, already shipping in RN) as the miniapp runtime. Miniapps are pure JS — no DOM, no browser APIs, just a curated set of capabilities we explicitly expose:
fetch,WebSocket,setTimeout,localStorage,crypto.subtle,console, plus our glasses-driving APIs.UI, if the miniapp needs one, is a separate concern: launch a WebView on demand (settings, config, richer dashboards). The WebView doesn't need to stay alive in the background — it's only for when the user is actively looking at it.
Pros:
Cons:
expressornode:fs, they're out of luck.fetch,WebSocket, timers, crypto, storage all wired through native bridges. Not hard, but real work. (~2-3 weeks for a complete polyfill layer.)@mentra/sdkoffers. Not zero-change migration but close.Cost to build: 6-10 weeks. Longer than Option 1 because we're building the runtime from scratch. Shorter than Option 2 because we're not dealing with nodejs-mobile complexity.
Option 4 — Cloud-backed with WebView UI
Current architecture (status quo for cloud miniapps). Listed here for completeness. Miniapp runs as a Node server in the cloud. Phone shows a WebView UI when user opens it. WebView and cloud communicate over WebSocket. Cloud drives glasses via its existing cloud→phone→BLE path.
Explicitly not considered because this is what we're trying to get AWAY from. The miniapp-server ⇄ cloud ⇄ phone ⇄ glasses chain is exactly the reliability/latency problem we're solving.
Cross-option comparison
Additional considerations we shouldn't skip
Apple 4.7 risk exists in all options. Any system that loads third-party code into our app is subject to 4.7. The specific requirements (miniapp index, age gate, moderation) are product work we owe regardless of runtime choice.
First-party miniapps matter. We're porting Captions, Translation, Livestreamer, Call, etc. The cost to port differs between options. Option 2 is zero-cost (same SDK). Option 1 requires a rewrite. Option 3 requires a rewrite.
Memory pressure compounds. A user with 5 miniapps open running Option 1 (WebView) burns 100-150MB just on WebView overhead. On Option 3 (JSC), it's 50-75MB. iOS jetsam is aggressive in the background — we should optimize for leanness.
References
agents/local-miniapp-execution-plan.mdagents/local-app-runtime-plan.md