Every small device platform eventually asks the same question: can we just run JavaScript? The answer is usually yes, followed by a list of exceptions. Micro OS takes the other road. Apps compile ahead of time to MBC, a versioned bytecode, and the device never sees a parser, a JIT, or a garbage collector.
This is not nostalgia for virtual machines. It is a response to three constraints that a general-purpose scripting engine handles badly.
The shape of the problem
The reference device is an ESP32-S3 with 8 MiB of flash and 8 MiB of PSRAM driving an 800 × 480 panel. That is enough for a real application, but it is not enough to treat the runtime as free. A JavaScript engine brings a parser, an interpreter, hidden classes, a GC and a growing standard library. Each one is a source of both flash cost and timing variance.
The browser host has the opposite profile: plenty of memory, but the app still has to behave identically to the device. If one host runs your code with a JIT and another interprets it, “the same app” becomes a claim about source compatibility, not behavior.
Micro OS wants a stronger claim: the same bytes execute with the same semantics on every host.
What replaces the engine
The compiler is a development-host tool. It parses a deliberately restricted TypeScript subset with SWC, rejects anything outside the allow-list, lowers the program to a typed stack machine, validates it, and encodes an MBC image. The image starts with MBC1, a format version, a payload length and a CRC-32, followed by constants, state declarations, bytecode functions, the static UI specification, host-request descriptors and the manifest.
The decoder rejects, before execution:
- any magic other than
MBC1; - any version other than the current one (28 at the time of writing);
- truncated payloads and CRC mismatches;
- invalid constant, function, state or UI references;
- jumps outside a function, and excessive stack depth.
That last point matters. A corrupt or stale app should fail at the boundary, with a diagnostic, not halfway through painting a screen.
Because there is no ambient I/O, every external effect is a host call. A call is checked against the app’s declared capabilities before the host implementation runs. There are no timers, threads, promises or filesystem paths in the language. The runtime owns the event queue; the app owns one handler at a time.
Determinism is a feature, not a limitation
A stack machine with a fixed instruction budget is boring in the best way. Handlers execute in a defined order. State writes are coalesced. Bindings re-evaluate only when a dependency changes. A given sequence of events produces a given sequence of render patches.
That property is what makes three very different renderers practical. The LVGL host, the SDL3 host and the DOM host do not need to agree on layout quirks or event timing. They receive the same tree and the same patches, and they implement a narrow rendering trait.
It also makes testing honest. The workspace can compile Counter, load the MBC, enqueue two clicks and assert Count: 2 without a display server. The same assertion runs through the real native rose and through Chromium.
What we gave up
The restricted language is real. There is no JSX, no classes, no general functions, no async/await, no exceptions, no arbitrary objects or arrays, and no runtime widget creation. The UI tree is built once by ui.mount; state changes patch existing nodes. If you need a dynamic list of arbitrary length, this slice is not ready for you.
That is a deliberate trade. The alternative is to ship a general language and then spend the next year building the sandbox that makes it safe. Micro OS starts from the sandbox and adds language surface only when a real app needs it.
MBC is also not signed. The CRC detects corruption, not intent. Capabilities are enforced at the call boundary, but the manifest is unauthenticated and there is no installer UI that reviews grants. This is a correctness boundary today; signing and install-time consent are future work. We would rather say that plainly than imply a security guarantee the code does not make.
Why not Lua or WebAssembly?
Both are reasonable. Lua is small and embeddable, but it is dynamically typed and carries a language runtime plus bindings; deterministic budgets and static UI validation would still need to be built around it. WebAssembly has a strong sandbox and mature toolchains, but it standardizes computation, not the app contract. You still need a UI model, a capability model, a manifest, and a story for the 800 × 480 panel.
MBC is narrower on purpose. It is the contract Micro OS actually needs: a small, validated, versioned unit that can be inspected before it runs and rendered by any host that implements the port.
The result is not a general-purpose OS. It is an application layer with sharp edges — and the edges are the point.