Bytecode, not a browser
The device never parses TypeScript. An MBC image carries constants, state, handlers, the UI tree, host-request descriptors and the manifest behind a CRC-32. Bad references and bad versions are rejected before frame one.
Preview 0.1.0 · MBC v28
Micro OS is a portable application layer for small devices. Write a restricted TypeScript app, compile it to versioned bytecode, and run the same binary on an ESP32-S3, a Mac, or in the browser — under a deterministic, capability-gated runtime.
A framework asks the host to run your source. Micro OS asks the host to run a binary contract: a validated MBC image, a static UI tree, reactive state, and a default-deny list of host calls. The host supplies pixels, input and platform services. Nothing else crosses the line.
The core has no platform dependencies. That is why an 800 × 480 panel, a macOS window and a browser tab can share one runtime instead of three rewrites.
Every constraint is load-bearing. No threads, no GC, no dynamic widgets, no ambient I/O — the surface is small enough to validate, test and reason about on a device that fits in your hand.
The device never parses TypeScript. An MBC image carries constants, state, handlers, the UI tree, host-request descriptors and the manifest behind a CRC-32. Bad references and bad versions are rejected before frame one.
Every handler runs under a hard budget. Writes completed before exhaustion are committed, dirty bindings are flushed, and the queue keeps moving.
The tree mounts once. Each binding records exactly which state slots it reads, so a write dirties only its subscribers and emits a patch for an existing node.
The runtime emits LVGL objects, DOM nodes or native SDL-backed widgets through the same narrow port. Host chrome — the 40 px app bar, power state, back gesture — belongs to the host, not the app.
A manifest declares capabilities; the compiler rejects calls outside them and the runtime gates the host boundary again. There is no ambient filesystem, network or clock.
Eight launcher slots. Built-in apps occupy the first ones; compiled .mbc files in /apps are scanned before Home and appended. A built-in id always wins a collision.
The compiler runs on the development host. The runtime runs on the device. Renderers only know how to paint a tree and apply a patch.
A restricted TypeScript subset with state, bindings and a declarative UI.
const count = state(0)SWC parse, allow-list diagnostics, lowering, structural validation, encode.
MTS002 · host call deniedVersioned image with CRC-32, validated again at load time, before execution.
MBC1 · v28 · 6.9 KiBFIFO events, typed stack VM, state store, bindings, render patches.
10,000 instructions / handlerLVGL, SDL3 or DOM. Same tree, same patches, different pixels.
LVGL 9.5 · DOMNo package graph, no bundler config, no runtime imports. One app.ts declares its manifest, mounts a tree and compiles to one artifact. The SDK is a type declaration, not an execution environment.
const count = state(0);
const note = state("micro");
ui.mount(
ui.column([
ui.text("Counter Studio", {
font: "uiSans", size: 18, lineHeight: 24,
}),
ui.text(bind(() => `Count: ${count.value}`), {
size: 32, lineHeight: 40,
}),
ui.button("Add", {
onClick: () => { count.value++; },
}),
ui.button("Reset", {
onClick: () => { count.value = 0; },
}),
ui.input(bind(() => note.value), {
placeholder: "type a note",
onChange: (text) => { note.value = text; },
}),
]),
);app({
id: "counter",
name: "Counter",
icon: "plus",
capabilities: ["device", "net"],
});
// Omit the list and every host call is denied.
// The compiler rejects calls outside the manifest (MTS002).$ npm run build:app
microc apps/counter/app.ts
→ apps/counter/dist/counter.mbc 6.9 KiB
MBC v28 · typed stack machine
capabilities: device, net
✓ validated · CRC-32 okThese captures come from the Web Player: the same compiled MBC, the same Rust runtime, running in a browser tab. The UI is real; the board services are simulated.



momo is a 墨墨-style vocabulary trainer written in the same restricted TypeScript. It keeps a per-word Ebbinghaus schedule, runs a four-step study loop and survives a reboot.
Up to five local users share one install, with namespaced KV and config. On macOS, the sync tab imports the active account's today queue through the native HTTP bridge, then enriches words with definitions and examples.

Short essays on the choices behind the runtime: why bytecode, why a hard budget, and how one UI tree survives three renderers.
A browser engine is the wrong shape for a microcontroller. The case for a versioned, validated runtime contract.
What happens when an app handler runs away, why work is bounded per event, and how failure stays local to one app.
Static trees, reactive bindings and render patches: how one layout survives LVGL, SDL3 and the DOM without a layout engine.
Quickstart
Compile an app, open the native window, or run the Web Player. The first build takes a few minutes; later builds reuse the cache.
$ npm install $ npm run build:app microc → apps/*/dist/*.mbc $ npm run demo Micro OS · native window · Home # or the browser runtime $ npm run dev:web