Skip to content

Overview

Architecture & design

Status: Draft v0.7 · 2026-11-03 (v0.7: implementation status pages · v0.2: testing design, §12 · v0.3: named Mantou · v0.4: open questions decided, §16 · v0.5: threading spike results, §4) Name: Mantou (馒头), the plain Chinese steamed bun: Mantou provides a plain, unstyled base, and the app supplies the filling (§7.3). Packages: npm mantou-ui (the bare mantou is taken, see Q1) · CLI mantou · Rust crates mantou-* · C ABI prefix mt_ Scope of this draft: desktop (macOS, Windows, Linux). Mobile and web are out of scope for v1.


Mantou lets you write desktop apps in TypeScript and TSX, run them on Bun, and show UI that Mantou draws itself on the GPU. It never shows a webview. It has three parts:

  • A thin native core in Rust, compiled to a C-ABI dynamic library and loaded with bun:ffi. It owns the OS event loop, the windows, the UI tree, layout, text, drawing, scrolling, animations, input methods (IME) and accessibility.
  • A TypeScript runtime that runs the app inside a Bun Worker. It turns compiled TSX and fine-grained signals into a stream of tree ops and sends them to the core in one FFI call per JS turn.
  • A small bootstrap on Bun’s main thread. It starts the Worker and then gives the main thread to the native event loop for the rest of the process’s life.

The main thread never runs app JavaScript and never waits for it. That keeps scrolling, resizing and animation smooth while the app’s JS is busy or paused for garbage collection.

  • Cross-platform desktop apps written entirely in TypeScript/TSX, with full access to npm, Node APIs and Web APIs through Bun.
  • Self-drawn UI with an app-owned look, the same on every platform. One rendering code path, built on wgpu.
  • Smooth by construction: scrolling, resizing, transitions and input handling must not depend on the JS thread.
  • Idle at about 0% CPU. Nothing polls; all work is driven by events.
  • Fine-grained updates: no virtual-DOM diffing; a signal change sends only the ops it causes.
  • Accessible from v1: screen readers on all three desktops through AccessKit.
  • Ships as an app bundle or installer per platform, built from one command.
  • Wrapping OS widgets (NSButton, Win32 controls, GTK widgets). This is not a “native widgets” toolkit.
  • iOS and Android. Bun doesn’t run there; see §14 for what this rules out.
  • Running in a browser.
  • Pixel-identical text to each platform’s own toolkit (see §8.5 for the trade-off).
Mantou
Who owns the process Bun; the main thread is handed to the native loop
Where app code runs Bun Worker (JavaScriptCore, JIT)
UI model Compiled JSX + Solid signals (in JS)
Tree, layout, text Rust: Taffy, parley
Drawing wgpu, instanced quads
Web/Node APIs Provided by Bun
  • Rendering:
    • Frames are drawn only when asked for, so an idle window costs nothing.
    • The scene is a flat list of operations.
    • The renderer draws one instanced quad per operation with a signed-distance-field (SDF) shader for rounded rects, borders and shadows, plus glyph atlases.
    • Layout results are cached and reused when an element hasn’t changed.
  • Threads and the tree:
    • The app UI never waits for JS. One JS turn produces one commit.
    • The core keeps a shadow tree; JS only sends mutations.
    • Text is measured off the main thread and cached by content hash plus width.
    • Removing a node only detaches it; freeing it is a separate step.
    • A node already in place is never re-inserted.
    • During a resize, the app waits a bounded time for the new layout.
    • Layout rounds to device pixels, not whole points.