Get started
How apps work
A MIRA app is one file with two halves: a glasses app and an optional phone companion.
Two halves
| Glasses app | Phone companion (optional) | |
|---|---|---|
| Runs on | MIRA glasses | The MIRA iPhone app |
| Written in | Rust, compiled to WebAssembly | HTML, CSS and JavaScript |
| Good for | Drawing the display, handling taps and swipes, head tracking | Your app’s phone screen, networking, storage, microphone audio |
| Talks through | Shared/companion.rs | miraCompanion |
The two halves exchange small JSON messages over Bluetooth. Keep networking, accounts and anything secret on the phone, and send the glasses only what they need to draw.
Installed apps live on the glasses. An installed app opens from the glasses sidebar even when your phone isn’t connected, but anything that needs the phone, like companion messages or web data, won’t work until it reconnects.
One file
A finished app is one HTML file under 2 MB. It contains a small manifest with the app’s name, your optional phone screen, and the compiled WebAssembly module encoded as base64. Glasses-only apps use the same file format. See Package and validate for the exact layout.
Lifecycle
Your glasses module exports its memory and four functions. MIRA calls them; none of them return a value.
| Export | When MIRA calls it |
|---|---|
app_init() | Once, when the app opens. Do your setup here. |
app_render(elapsed_ms) | Repeatedly while the app runs, with the milliseconds since the previous call. Update your state and redraw when something changed. |
app_event(event) | When the wearer taps, swipes or holds. See Input. |
app_stop() | Right before the app closes. MIRA then releases everything the app was using. |
#![no_std]
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! { loop {} }
#[no_mangle]
pub extern "C" fn app_init() { /* set up state, draw the first frame */ }
#[no_mangle]
pub extern "C" fn app_render(elapsed_ms: i32) { /* advance timers, redraw if needed */ }
#[no_mangle]
pub extern "C" fn app_event(event: i32) { /* 0 tap, 1 and 2 swipes, ... */ }
#[no_mangle]
pub extern "C" fn app_stop() {}Rules for the glasses module
- Use
#![no_std], a panic handler and fixed-size buffers instead of heap allocation. - The module gets one 64 KiB memory page, which includes an 8 KiB stack. The module itself can be up to 128 KB.
- Keep every callback short. Each one runs under an instruction budget, so spread heavy work across several frames.
- WASI, threads, SIMD, bulk memory, reference types and a start section aren’t available. Put setup in
app_init. - Import only the documented functions from the
miramodule. The API reference lists them all.
The shell around your app
Your app runs inside MIRA’s shell. The shell draws the sidebar, the look-up dashboard, notifications and the shared cursor, and it sees every gesture first:
- Double tap opens the sidebar, unless your app claims double tap for itself.
- Looking up opens the dashboard, unless your app is reading head orientation.
- Notifications and MIRA’s answers can appear on top of your app.
content::context_changed() reports whether the sidebar or an overlay has opened or closed since you last asked. The example app checks it on every frame and redraws when it’s true.
Talking to the phone
There are two ways for the glasses app to reach the phone:
- Your companion. Send and receive JSON messages with your own phone code. See Phone companion.
- MIRA services. Ask the MIRA app for work it already does, such as live transcription, with
ble_request. See Phone requests.
Both are asynchronous. Never block a frame waiting for the phone, and keep local navigation working while a reply is pending.