Platforms · three hosts

Three hosts.
One binary.

The same compiled app, the same runtime, three different machines. Hosts own the window, the event loop and the pixels; the app contract stays identical.

macOSWebAssemblyESP32-S3800 × 480

Pick a screen

Each host implements the same narrow port. They differ in platform services, not in app semantics.

01 / HOSTSupported
Bare metal

ESP32-S3

A real handheld: 8 MiB flash, 8 MiB PSRAM, an 800 × 480 RGB panel and GT911 touch. LVGL renders the same UI tree the browser does.

  • ESP-IDF + FreeRTOS
  • LVGL 9.5
  • TF card apps
02 / HOSTSupported
Desktop

macOS

SDL3 owns the window and event loop; the shared Rust runtime executes MBC and LVGL paints it. Apple Silicon is the tested target.

  • SDL3 + LVGL
  • Native HTTP + dictionary
  • Double-clickable bundle
03 / HOSTSupported
Browser

Web

The same runtime compiled to WebAssembly, rendering DOM instead of LVGL. No JavaScript app runtime and no embedded JS engine.

  • Wasm + DOM
  • Desktop + mobile
  • Device simulator
Windows / Linux native is source-level groundwork; iOS / Android are out of scopeSee the matrix

The reference device

An ESP32-S3 Touch LCD 7: enough flash, PSRAM and pixels to run a real app contract.

SoCESP32-S3 · 8 MiB flash · 8 MiB PSRAM
Display800 × 480 RGB565 · 16 MHz
TouchGT911 capacitive controller
ExpanderCH422G · reset / backlight / SD chip select
StorageTF card over SPI · MOSI 11 · MISO 13 · SCLK 12 · CS EXIO4
InputBOOT button on GPIO0 · edge-swipe back gesture
ConsoleUART0 · 115200 baud
Partitionsfactory app 0x10000 / 0x3D0000 · micro_app 0x3F0000 / 0x3F0000
KernelESP-IDF 5.5 + FreeRTOS (owned by the platform)

The board is a host, not a target abstraction. The runtime does not know it is on a microcontroller: it receives a UI tree and a render patch, and the LVGL adapter paints it.

A missing TF card is a warning, not a boot failure. Without a card, storage.available() is false,/apps is ignored, and the built-in apps still launch.

The wall clock is SNTP-backed through micro_clock; before the first sync, clock.now() falls back to uptime.

Boot chain

Ten steps from power to a running shell. No JS engine, no parser, no package manager.

  1. Verify the board

    8 MiB flash / 8 MiB PSRAM class check; abort on mismatch.

  2. Bring up the panel

    RGB LCD, GT911 touch and CH422G expander; enable the backlight.

  3. Mount the card

    A missing TF card warns; it does not stop boot. storage.available() reports it.

  4. Smoke / health screen

    Draw, mark the board profile healthy, clear for the shell.

  5. Start platform services

    BOOT button, Wi-Fi STA, KV, SNTP clock, OS reducer.

  6. Dispatch boot events

    BOOT_SAMPLED … SETUP_SKIPPED; first-run setup is skipped in this slice.

  7. Scan built-in apps

    Raw micro_app partition, up to 8 manifests beside the shell.

  8. Scan the SD card

    Top-level /apps/*.mbc, malformed images skipped, built-in ids win collisions.

  9. Load the shell

    Destroy any previous runtime, load shell.mbc into PSRAM, create its Runtime.

  10. Start the tick

    Install the host app bar; create the 30 ms LVGL timer that drives the runtime.

Support matrix

What is tested, what is groundwork, and what is deliberately out of scope.

TargetStatusNotes
macOS native (SDL/LVGL)SupportedApple Silicon is the tested target.
Web browser (Wasm/DOM)SupportedDesktop and mobile, with a responsive simulator.
ESP32-S3 reference boardSupported800 × 480 RGB panel, 8 MiB flash + 8 MiB PSRAM.
Windows / Linux nativeGroundworkPortable SDL layer exists; no packaged launcher, not verified on real desktops.
Native iOS / AndroidOut of scopeNo host or packaging layer. Mobile browser access works through the Web Player.
One honest caveat.
The browser simulator executes the shared runtime and the OS reducer, but it does not emulate ESP-IDF, RGB timing, GT911 electrical I2C, PSRAM allocation, the Wi-Fi radio or physical hardware success.