cd /news/developer-tools/exact-a-new-cross-platform-ui-framew… · home › topics › developer-tools › article
[ARTICLE · art-166592] src=github.com ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

Exact – a new cross platform UI framework by the Expo team

The Expo team released Exact, a pre-1.0 cross-platform application runtime that compiles interfaces written in its declarative Contract language into native apps for web, macOS, iOS, and Linux, with no webview or JavaScript bridge in the UI. Exact exposes ten agent operations — tree, screenshot, tap, type, state, layout, logs, clock, prefer, and perf — through a single script, scripts/agent.mjs, and a fresh AI agent given a sample prompt on a clean clone finished a todo app in about 57 minutes with tests passing on web, macOS, and the iOS Simulator, roughly 25 of those minutes spent on first macOS and iOS builds at about 13 minutes each. The web target runs a ~20 KB runtime over the real DOM, while macOS, iOS, and Linux use a Rust runner and Rust layout kernel drawing through AppKit, UIKit, or a GPU painter; the team states Exact has no API stability, backwards compatibility, or migration guide by design.

read29 min views4 publishedNov 9, 2026
Exact – a new cross platform UI framework by the Expo team
Image: Michielbdejong (auto-discovered)

Write an app once. It runs natively on the web, macOS, iOS, and Linux, and an AI agent can build it, run it, see it, and test it on every one of them.

Tip

Try it with a coding agent. On a Mac with Xcode, Rust, Bun, and Chrome installed, give Claude Code this prompt:

Clone https://github.com/expo/exact and follow its README to make a new Exact
app with `exact new`. Run `bun scripts/exact.mjs setup` once first. Make a todo
list where I can add items, check them off, delete them,
and see how many are left. Put the view in Contract and keep the list in `app.ts`.
Write an `app.test.contract`, pass it on web, macOS, and the iOS Simulator with
`scripts/agent.mjs`, then open the app for me on all three.

A fresh agent given an earlier version of this prompt on a clean clone (Hermes already in the machine cache, Cargo's cache warm) finished in about 57 minutes, and its tests passed on all three platforms. About 25 of those minutes were the first macOS and iOS builds, roughly 13 minutes each; later builds take a minute or two. On a machine that has lacked Hermes, its source build came first and added time. Current setup downloads and verifies the pinned host/iOS bundles once instead; ordinary builds stay offline.

Web iOS macOS
The browser's own DOM and CSS UIKit views, native controls and the system's tab bar AppKit views and native controls

<sub>One app.contract, the same screen on three hosts, captured by the agent driver. Softwear is a demonstration store you can generate from exact-templates: it shows Alternative Apparel's catalog, is not affiliated with that brand, and takes no real orders or payments.</sub>

Exact is a cross-platform application runtime. You describe your interface in Contract, a small declarative language, and you write your data logic in TypeScript or Rust. The Contract compiler turns the interface into a compact plan. On the web, that plan becomes one ES module over a ~20 KB runtime that drives the real DOM. On macOS, iOS, and Linux, a Rust runner executes the same plan, a Rust layout kernel computes every box with CSS rules, and each platform draws with its own tools: AppKit, UIKit, or a GPU painter. There is no webview and no JavaScript bridge in the UI, and no app JavaScript runs before the first pixel.

Note

Exact is pre-1.0 and changes daily. There is no API stability, no backwards compatibility, and no migration guide, by design.

Upgrade note: Snapback mutation replies with ok: false now throw refused(why) and expose failure(slot); the snapback4 package's README has the before/after and inherited-app commands.

Exact assumes much of the code will be written by AI agents. An agent can make an app, run it, look at it, operate it, and prove it works without a person in the loop.

  • Ten operations, the same on every host.tree · screenshot · tap · type · state · layout · logs · clock · prefer · perf drive the web, macOS, iOS (Simulator or a real iPhone), and Linux through one script,scripts/agent.mjs . There are ten on purpose: an agent API grows one reasonable addition at a time until no one can hold it in mind.
  • The clock belongs to the agent. Between two operations nothing moves. Instead of sleeping until an animation finishes, the agent saysclock +60000 orclock settle and reads the result. No timing flakes, no waits.
  • Tests are scripts of those operations. Anapp.test.contract file runs unchanged on any host, with no second evaluator.
  • Every tool speaks to machines.contract build --json gives diagnostics with stable ids and source ranges.contract symbols gives navigation. A development source map leads from any node on screen back to the line that declared it. The driver refuses to drive a build older than its sources and names the rebuild.
  • Contract is small on purpose. A whole app fits in a context window. Every write is declared, every list is keyed, every value is typed. Mistakes are refusals that say what to do, not quiet misbehavior.
bun scripts/agent.mjs web tree "tap change-station" "type station-search Palo" \
  "clock +60000" state "screenshot out.png"

Speed of the app and speed of the loop. Each budget in rules/RULES.md is tracked on every commit, and a regression is a P0 with a name on it.

Budget Target
Cold start to interactive first frame 100 ms p50
Dev restart, edit to present 100 ms p50
App JavaScript executed before first pixel none
Touch one line, rebuild that crate 30 s
The whole blocking check suite 65 s

How the design keeps those numbers:

  • The boot path executes and compiles nothing. TypeScript is compiled to Hermes bytecode at build time and loads after the first pixel. The first frame comes from data baked into the plan.
  • Pay only for what you use. GPU rendering, the Markdown editor, text flow, and browser SQLite are separate artifacts, loaded on demand. Nothing is a feature flag on a core crate.
  • The web gets its own small runtime. On the RealWorld ("Conduit") benchmark, Exact's runtime was up about 300 ms before the React SSR build had hydrated. It used 26 KB of code against React's 81 KB (the RealWorld benchmark inapps/realworld ).
  • Edits land in about a tenth of a second. On a fresh app made byexact new , a saved Contract or TypeScript edit was rebuilt and in the page in 99 ms.

What you write is CSS: the vocabulary, the layout model, the development loop. What you leave unsaid is the platform's: controls, colour, type, motion, gestures, the keyboard and accessibility come out as an app built by hand with UIKit or AppKit would have them, and on the web as a carefully built web app would. What you write wins. The order is author, then platform, then CSS's default, and one test settles a dispute: would someone who knows the platform notice?

  • Layout is CSS's, because a person can't see a layout default, only its result. A bare node is display: block ,box-sizing: content-box ,flex-direction: row ,flex-shrink: 1 , the same as a bare<div> .
  • Names are CSS's, because names are what you write: font-size ,object-fit ,text-overflow ,line-clamp ,light-dark() ,env(safe-area-inset-top) . The compiler refuses an alias and names the web's spelling.
  • Unsaid presentation is the platform's. Text is the system's label colour and size (17 points on an iPhone, 13 on a Mac, the reader's setting in a browser), a button is the system's button, a pushed screen has the system's Back. An app that says less looks more at home.
  • The browser is the parity oracle for layout and for what you wrote. Native layout given the same inputs, Canvas 2D, SVG, gradients, and authored motion are checked against what Chrome draws.
  • The web is also the development loop. You work on the seconds-long web loop, and the native hosts are swept behind you.
  • Any unavoidable deviation in layout is declared, with its reason. For example, Taffy has no position: static .

Writing the web doesn't mean the lowest common denominator. Where a platform can do more (real HTML in an SVG foreignObject on the web, MapKit through a native module on Apple), Exact lets it.

flowchart LR
  subgraph author["You write"]
    C["app.contract<br/>view · state · actions"]
    D["app.ts or a Rust crate<br/>data sources"]
  end
  C --> K["Contract compiler"] --> P["plan<br/>tables + bytecode"]
  P --> W["Web<br/>one ES module, ~20 KB runtime,<br/>the browser's DOM and CSS"]
  P --> R["Rust runner<br/>+ layout kernel (Taffy)"]
  R --> M["macOS<br/>AppKit + CoreText"]
  R --> I["iOS<br/>UIKit + CoreText"]
  R --> L["Linux<br/>vello GPU / tiny-skia CPU"]
  D -.->|answers, checked against declared shapes| W
  D -.-> R
  • Contract (contract/ ) is compiled in four passes: syntax, types, analysis, and lowering. At build time the plan isbaked : constant data is evaluated into it, so the first frame needs nothing else.
  • The runner (runner/ ) is the plan's virtual machine. It handles keyed component instances, events, timers on a seekable clock, and the data seam.
  • The kernel (kernel/ ) is a columnar node arena with CSS layout through a patchedTaffy . The host measures text: CoreText on Apple, cosmic-text on Linux. On the web, the browser lays out the same CSS itself, so the web build carries no layout engine.
  • Hosts (host/ ) present the tree. The web host writes DOM nodes, and the browser runs CSS transitions. The Apple host is a static library with a C ABI under AppKit and UIKit presenters. The Linux host paints the tree itself, onto DRM/KMS with evdev input, or into an offscreen buffer.
  • Data sources sit below one seam. TypeScript runs on Hermes on native hosts and in the browser's own engine on the web. Rust runs natively, and can be replaced live (as a shared library, or as Wasm on iOS and the web). Every answer is checked against the shape Contract declared. Grants control network, files, SQLite, and secrets.
  • Optional pieces are separate artifacts: a wgpu GPU module (Metal on Apple, WebGPU in the browser), native modules (a hyphenated tag backed by a platform widget, like MapKit), and agame engine add-on.

These steps were run on macOS on Apple Silicon. Linux works for the web loop and the Linux host.

Rust throughrustup . The pinned stable toolchain inrust-toolchain.toml installs itself the first time Cargo runs. #

Bun 1.4.2 , the versionpackage.json pins. Node and npm are not needed. #

Google Chrome. The agent drives the web through headless Chrome. SetCHROME to use another Chromium. #

Xcode , for the macOS and iOS hosts. #

Hermes , only for TypeScript apps on native hosts. Exact consumes Ibex's pinned, attested260318099.0.4 release bundle; ordinary Cargo builds are offline and never download or compile an engine. Install it once for each target before building (for this Mac, useaarch64-apple-darwin ):

cargo run --manifest-path vendor/ibex/crates/hermes-lean-sys-installer/Cargo.toml -- --target <triple>

The installer verifies the bundle and publishes it under ~/.cargo/hermes-lean-sys/ . The v4 set supports macOS, Linux, Windows, iOS devices and the universal iOS Simulator, plus tvOS devices and arm64 tvOS Simulators. Linux and Windows select Ibex's Englishintl tier; Apple keeps Hermes's OS-backed Intl.HERMES_LEAN_SYS_DIR is only an explicit development override, not normal setup.

To install the pinned Bun beside any existing installation: curl -fsSL https://bun.sh/install | BUN_INSTALL=~/.bun-1.4.2 bash -s bun-v1.4.2. Use ~/.bun-1.4.2/bin/bun for the commands below if it is not on your PATH. bun scripts/exact.mjs setup installs the declared stable and web nightly Rust toolchains, their components/targets (the nightly's clippy lints a game's web bake), matching wasm-bindgen, pinned Binaryen and Bun dependencies, installs the pinned Hermes host bundle and the iOS/tvOS bundles this Mac builds, and fetches the crates of exact2's lock and the game SDK's (game/app/shells.lock), since every bake resolves offline. It keeps Binaryen in ~/.cache/exact/binaryen; builds find it automatically. setup --check invokes Ibex's installer in its offline check mode for that same target set. The resolver authenticates the canonical receipts, archives, compiler and host/target HBC pairing without installing anything; its exit status and diagnostic are the check's result.

git clone https://github.com/expo/exact.git
cd exact
bun scripts/exact.mjs setup
bun host/web/dev.mjs            # Caltrain, at http://127.0.0.1:8765/

The first run compiles the toolchain, which takes a few minutes. After that, open apps/caltrain/app.contract, change some text, and save. The page rebuilds and reloads in about a tenth of a second. Add --lan to open the same page from a phone on your network. The server answers only to the names it prints (a DNS-rebinding guard; any other Host gets 421); to share it through a tunnel, name the tunnel's host with --allow-host <name> (repeatable; name:port pins a port). An allowed name reaches the page and its reload stream, never the local installer's token.

bun host/apple/build.mjs --run              # macOS
bun host/apple/build.mjs --ios --run        # an iOS Simulator (--device --run for a connected iPhone)
cargo build --profile host-dev -p caltrain-linux   # Linux: a DRM/KMS console, or headless anywhere (--release to ship)

A first native build takes ten to fifteen minutes on a laptop; later builds reuse it and take a minute or two. Watch its output rather than waiting blind. iOS commands use an iPhone simulator that's already booted, or boot the newest iPhone Pro. To choose one, set EXACT_SIM to its name or UDID (or pass --sim to build.mjs), and keep the same setting for agent.mjs ios.

On Linux, a development build can serve its screen with EXACT_VNC=1 and EXACT_VNC_PASSWORD set to 1–8 bytes. It listens on 127.0.0.1:5900; an explicit EXACT_VNC=host:port selects another address. Production bakes ignore both variables.

bun host/web/build.mjs caltrain-web         # the build the agent's web host serves
bun scripts/agent.mjs web tree "tap change-station" "type station-search Palo" state "screenshot out.png"
bun scripts/agent.mjs web --test apps/caltrain/app.test.contract

Swap web for macos, ios, or linux once that host is built. Caltrain's three tests take about two seconds on the web host. Each operation is one shell argument, so quote it: "type new-todo Buy milk" types Buy milk (everything after the target is the text). "screenshot film.png over 600 every 50" films motion as a contact sheet; use an .apng name to get an animation.

bun scripts/exact.mjs new ../hello          # or run `bun link` once, then `exact new ../hello`
cd ../hello
bun exact.mjs contract types app.contract -o app.contract.d.ts   # first run builds the compiler
bun exact.mjs web                           # the dev loop, at http://127.0.0.1:8765/
bun exact.mjs test web tests/*.test.contract # the app's *.test.contract, or the files named
bun exact.mjs mac --run                     # this Mac
bun exact.mjs ios --run                     # an iOS Simulator

exact new creates a standalone app: app.contract (the view), app.ts (its data), app.json (the manifest: name, bundle id, hosts, deploy policy), and small web/ and apple/ host crates. Its AGENTS.md (and CLAUDE.md) points a coding agent at docs/start-here.md, the one guide it must read, and at bun exact.mjs help for the commands, including bun exact.mjs contract … for the compiler and contract vocab for the card of what an app writes (--all: every tag and property Contract accepts). Before any of it, run bun scripts/exact.mjs setup once; setup --check names everything this machine is missing without changing it. It has its own Cargo workspace, which uses your exact2 checkout by path. To drive it from exact2, point EXACT_APP_DIR at it:

EXACT_APP_DIR=../hello bun host/web/build.mjs hello-web
EXACT_APP_DIR=../hello bun scripts/agent.mjs web --app hello tree "screenshot hello.png"

Look at every screenshot, not just the test result: a test passes on a layout that spills off the screen. bun exact.mjs agent <host> "screenshot out.png" shows the app on each host in its own session; for a copy you launched yourself, use xcrun simctl io <device> screenshot out.png on iOS.

bun scripts/exact.mjs also runs apps from this repository as real Mac apps: exact run markdown README.md, or exact install markdown to put mdview on your PATH. exact list shows what's here.

An app's AGENTS.md asks its agent to record friction in one line, bun exact.mjs feedback note "<what happened>". A failed or slow drive, build or verify, and a passing test or verify, end with one line naming it, at most once a day per project. A note stays in the app's .exact/ with exact2's commit, the last command's verb, exit code and duration, and the OS; never any command output, or the app's code or files. It goes to Expo (https://exact-diaries.ccheever-0f6.workers.dev) only when the project's answer allows: always, or a yes to ask, the default, after bun exact.mjs feedback shows exactly what would go. bun exact.mjs feedback never stops it for a project; EXACT_FEEDBACK=0 silences the nudges and refuses every send on the machine. docs/diary.md has the details.

An agent building an app starts at start-here.md, the only required reading, then looks up the recipe for each question. For more, read the complete guide for humans, or look things up in the guide for agents or the grammar and vocabulary reference.

Contract describes what an app shows and how its state changes. It doesn't fetch, read files, or run arbitrary code. That's what data sources are for. Here is a complete todo app: a Contract file, a TypeScript file, and a test. This exact app was built for web, macOS, and the iOS Simulator, and its test passed on all three.

To try it in an app created with exact new, replace app.contract and app.test.contract with the examples below. In app.ts, keep the generated export const appId = … line and replace the rest with the TypeScript below.

// app.contract: the view, its state, and what each action changes. It names no colour,
// font or control size: each host draws its own heading, field, button, checkbox and list.
shape Todo
  id: string
  title: string
  done: bool

shape Change
  count: number

component Todos
  state draft = ""
  mutation changed as shape Change refreshes todos
  resource todos = todos() as shape Todo[]
  derive left = todos.filter(t => not t.done).length

  action edit(value: string)
    draft = value
  action add
    if draft.trim() != ""
      send changed = addTodo(draft.trim())
      draft = ""
  action toggle(id: string, done: bool)
    send changed = setDone(id, done)
  action remove(id: string)
    send changed = removeTodo(id)

  view
    list appearance="auto" height="100%"
      section
        header
          text `${left} left` role="heading" testId="count"
        row
          input value=draft input=edit submit=add placeholder="What needs doing?" aria-label="New todo" testId="new-todo" flex=1 min-width=0
          button press=add buttonStyle="filled" testId="add"
            text "Add"
      section
        for t in todos key=t.id
          row testId=`todo-${t.id}`
            input type="checkbox" checked=t.done change=toggle(t.id) aria-label=t.title testId=`toggle-${t.id}`
            text t.title flex=1
            button press=remove(t.id) destructive=true testId=`remove-${t.id}`
              text "Delete"
python
// app.ts: keeps the list, and answers what app.contract asks for
import type { Answer, Sources } from './app.contract.d.ts';

export const grants = '';  // e.g. 'net.fetch https://api.example.com'

type Todo = { id: string; title: string; done: boolean };
let todos: Todo[] = [];
let next = 1;

const sources: Sources = {
  todos: () => todos,
  addTodo: ([title]) => {
    todos = [...todos, { id: String(next++), title, done: false }];
    return { count: todos.length };
  },
  setDone: ([id, done]) => {
    todos = todos.map((t) => (t.id === id ? { ...t, done } : t));
    return { count: todos.length };
  },
  removeTodo: ([id]) => {
    todos = todos.filter((t) => t.id !== id);
    return { count: todos.length };
  },
};

export const answer: Answer = (source, args, store, storage, native) =>
  sources[source](args, store, storage, native);
// app.test.contract: runs on any host with `scripts/agent.mjs <host> --test`
test "add, finish and delete"
  expect text "count" == "0 left"
  type "new-todo" "Buy milk"
  tap "add"
  type "new-todo" "Walk the dog"
  type "new-todo" key "Enter"
  expect text "count" == "2 left"
  tap "toggle-1"
  expect text "count" == "1 left"
  tap "remove-1"
  expect tree missing "todo-1"
  expect tree has "todo-2"
Web macOS iOS Simulator

The view never edits the list itself. An action send s a mutation to a source, app.ts changes its data and answers, and refreshes todos asks for the list again. Data lives in one place, and every change to it is a named, typed call an agent can see. This list lives in memory. To keep it across launches, give app.ts a grant like sqlite.open app:/data/todos.db and use storage.sqlite, as Fieldnotes does.

Concept What it is
shape A closed record type. Fields are number ,string ,bool , another shape,option<T> , orT[] .
component A unit of UI. The first one in a file is the root. Components take props , caninject what an ancestorprovide s, and can fill aslot withchildren .
state A value the component owns, its type inferred or declared ( state picked: option<Item> = none ). Under afor , child state belongs to that keyed row.
derive A value computed from others, recomputed when they change.
resource Data from a source: resource x = source(args) as shape T . When the arguments change, the source is asked again.
mutation /send A change made through a source: send x = source(args) .refreshes r asks resourcer again afterward,pending(x) andfailed(x) show progress, andfailure(x) says why a resource failed.
action The only place state changes. What it writes is inferred from its body, and let binds a local inside it.
task Work on a schedule: every(1000, tick) ,after(ms, a) ,every(frame, a) .
view Indented elements, if … else ,for … key=… (a key is required),match over options, and calls to other components.
style /class= A named set of CSS properties. The node's own attributes win, and there is no cascade.
fn ,xs.map /xs.filter /xs.join Pure, single-expression helpers. Recursion is refused.
Built-in functions The web's string and array methods by their names, called on the value ( xs.length ,s.trim() ,s.includes(t) ,xs.at(-1) ), and functions (first(xs) ,formatDate ,formatNumber ); all are listed with their types understdlib inplan/tables/format.json .
routes A router: a location and a retained stack per tab, moved with open ,push ,replace , andback , and read withtop .
font Declares a font family and binds it to files at build time.
use … from "./x.contract" Imports components, shapes, styles, and fn s from another Contract file.
testId A stable name for the agent, tests, and accessibility tools.
  • No JavaScript in the view, and no I/O. Expressions have no effects. Effects happen only in actions, as assignments,send ,refresh , or commands likefocus(…) andshare(…) . Data crosses one seam: a source answers, and the runner checks the answer.
  • No loops in actions, no await. A list is a view's keyed for , a computation is aderive or afn (an action may name a value withlet ), and anything slower lives in a source. A list that changes changes where its data lives, through a mutation, as in the example above. That's what lets the plan be baked, diffed, inspected, and executed the same way on four hosts.
  • No escape hatch; access hatches. Nothing steps outside the plan. Where an app needs a platform widget, it uses anative module : a hyphenated tag likeexact-map , backed by Swift, Kotlin or Rust, laid out by the kernel like any other box. Where it needs the platform object Exact itself built (aUIView , a navigation controller, a DOM element), it marks the nodehatch="word" and its native code is handed that object: anaccess hatch , the panel a machine is built with so its insides can be reached. It configures what Exact made and changes Contract state only by acting on authored nodes, as a person would.

These limits serve the principles. An agent can't wire up a data race it can't write. A plan with no JavaScript in it starts fast. A view written in CSS's own words lays out the same on every host and looks at home on each.

$ cargo run -q -p contract -- build app.contract
app.contract:4:5 [type-assign] `count` is `number`, cannot assign `string`
app.contract:7:23 [lower-unknown-attr] `text` has no attribute `size`; `size` is spelled `font-size` here, the web's name

One run reports up to twenty independent errors, not just the first. Other subcommands: --json for tools, symbols (definitions and references as JSON), fmt (source-preserving), types (generates app.contract.d.ts for TypeScript sources), and rust (shapes for a Rust data crate). The full language is specified in docs/contract-grammar.md.

Each app lives in apps/<name>/. Run one on the web with bun host/web/dev.mjs --app <name>, on macOS with bun host/apple/build.mjs <name>-apple --run, and on iOS by adding --ios. All of these screenshots come from the web host, taken by scripts/agent.mjs.

To learn from, start with Shelf, the recipe app. It names no colour and no font size, and scripts/no-tells.mjs holds it to that, so it shows what an app that leaves presentation to the platform looks like on each host. Then read these four for what each teaches. They were written before Exact left presentation to the platform and paint their own colours and sizes, so take their structure, not their style:

App What it teaches
Caltrain A Rust data crate, authored tests, routes, an optional GPU module
Weatherlight TypeScript with fetch and grants
Fieldnotes Storage: SQLite, files, backup and restore
Recorder The microphone through @exact/media-capture and@exact/recording

| CaltrainThe app that defines v1: nearby stations, live departure boards, search, theming, a Canvas 2D line map, and an aurora GPU sky. Rust data. Every host. | WeatherlightWeather from Open-Meteo under a GPU-animated sky that follows the hour you pick. TypeScript data and a wgpu module. | SparkA swipe deck: throw a card with your finger, and it flies off with your release velocity. Pan gestures and springs on every host. | ExposeA pretend phone OS (lock screen, home, Messages with an AI assistant, Reader) set in custom fonts. TypeScript. The assistant needs an OpenRouter key. | | CalendarMonth pages, draggable sheets, events dragged between days, wallpaper themes, SQLite. It lives outside apps/ , so setEXACT_APP_DIR to run it. | SparklineA market list of animated SVG charts that draw in and pulse. | ** Photo Editor**Rotate, pan, and crop, through a native module. | ** Video Player**A bundled clip that shrinks out of the way when the keyboard opens. |

| Exact LiveA creative-production workspace: a photo grid you reorder by dragging, a crew chat that can take a 10,000-message burst, and a Markdown runbook. Rust data. | Still (Interaction Gallery)Drag to reorder, pan and zoom photos, a resizable sheet with a nested list, and 100 to 25,000 records. | | ReflowSeven typography studies after Cheng Lou's Pretext demos: text flowing around moving balls, a dragon, masonry, and a magazine spread. Line heights come from font metrics read at build time. | TextflowText around shapes with CSS's shape-outside andwrap-flow : draggable orbs, a dancer, an editorial layout. One Rust walker shared by every host. | | MarkdownA Markdown reader that opens files from Finder or the command line ( mdview README.md ). Its siblingLLP (llpview ) reads a folder of numbered design documents. | ** Conduit** (RealWorld)The RealWorld Medium clone against its hosted API. It's the benchmark that compared Exact's web build with React 19. Web only. |

| SVG Gallery : SVG as the browser renders it, one page per feature, held to Chrome's pixels on every host. | | Canvas Gallery : the HTML Canvas 2D API drawn by the browser, Core Graphics, or tiny-skia, and compared to Chrome. |

App What it shows Notes
Fieldnotes A local notebook with pins, search, SQLite storage, and JSON backup and restore TypeScript + Rust
Messages A port of Expo's chat demo, talking to models through OpenRouter Needs bun apps/messages/service.ts and an OpenRouter key
Type Tour Phone-OS screens as a type specimen for the Expose fonts No JavaScript at all
Map Demo Native modules: MapKit on Apple and OpenStreetMap on the web
Carousel A horizontal virtualized list of 25,000 cards
Motion Gallery Animated GIF and WebP images and CSS keyframes, compared across hosts
Update Lab Live replacement of Contract, TypeScript, and Rust in a running app Hand-testing lab
Messages Stress ,Completion Storm ,Markdown Stress 100k-message histories, 128 parallel requests, and 4 MiB documents, under load Opt-in workloads
Native Fixture ,Auth Fixture The native-module interface; OAuth with PAR, DPoP, and PKCE against a local server Test fixtures
Beacons, Tennis, … Games on the optional engine add-on : Rust gameplay, Contract menus bun game/dev.mjs beacons

The v1 bar: one real application, not a demo, runs from a single Contract source on web, macOS, iOS, and Linux, within the time budgets in rules/RULES.md. Caltrain is that application, and it runs on all four. Everything else here was admitted because a real app needed it.

  • Four hosts. Web, macOS (AppKit, no SwiftUI), iOS (UIKit, on the Simulator and on devices), and Linux (GPU or CPU painting, DRM/KMS or headless). Existing Swift apps can embed Exact sessions through ExactKit.

  • Layout and text. CSS block, flex, and grid;position: static and containing blocks;aspect-ratio ; declared fonts; text around shapes.

  • Paint. Gradients, shadows,backdrop-filter , glass materials, complete SVG, Canvas 2D on every host, and a wgpu GPU canvas loaded after first pixel.

  • Motion. CSS transitions, springs,@keyframes , follow-and-release gestures (pan, swipe, pinch) with release velocity, layout transitions, exit animations, and scroll- or drag-linked timelines.

  • Lists. Virtualized lists, vertical or horizontal, one level deep. The rows of a verticallist are its platform's: UIKit's separators and highlight, a table on the Mac, one line between rows on the web, with nothing drawn by the app.

  • Controls and media. Inputs,select , a WYSIWYG Markdown editor on the web, macOS, and iOS, images including animated ones, video, pickers for an app's declared file types, and share.

  • Data. TypeScript or Rust sources, optionally on a worker. Grant-checkedfetch and streams, files and SQLite (native and in the browser), Keychain secrets, and OAuth sign-in. A router keeps a stack per tab.

  • Native modules. Platform widgets as hyphenated tags.

  • The web, server side. Pages pre-rendered at build time or per request by a Rust renderer, then adopted in place instead of hydrated.

  • Delivery.scripts/deploy.mjs publishes the web root and signed update bundles, which installed apps fetch after first pixel. Apple apps check every fifteen minutes while active under every activation policy;deploy.check.interval overrides the interval in seconds. Checks stage a candidate;deploy.activate controls when it applies. TypeScript and Rust logic can be replaced live in development. Every web build gets an install page, andexact release signs and notarizes a Mac app.

  • Agents and tests. The ten operations on every host, including a physical iPhone; Contract tests; source maps; filmed screenshots.

  • Windows. Apps default to ink on a DirectX 11 composition swap chain, or CPU when the manifest declaresgame .host.windows.painter: "ink" or"cpu" selects either painter, andEXACT_PAINTER overrides both. Skirmish is a standalone game built with Exact's optional game engine; the CPU default preserves its module-owned window presentation.Windows Desk demonstrates native controls over Contract and the kernel with an app-local Win32 presenter; general Windows control parity and delivery remain unfinished.

  • Android. Early. An app builds for Android and runs on an emulator or a phone:bun exact.mjs android builds it,bun exact.mjs test android runs its tests, andbun exact.mjs agent android … drives it. It needs the Android SDK, the Rust targetaarch64-linux-android , and the Hermes Android bundle (bun scripts/hermes-android.mjs build ) until an Ibex release ships one. Delivery and store packaging don't exist yet.

  • No JSX or React tier. Nothing runs JavaScript above the data seam. The door stays open, but no one is building it.

  • TypeScript can't import npm packages' code yet.app.ts imports its own local files; from a package it imports only types (import type ), which the build checks and never runs.

  • Code updates in production. Signed delivery covers plans and assets. Signed delivery of TypeScript or Rust modules isn't implemented, and app-store rules limit it anyway.

  • Linux editing. The web and Apple hosts edit; Linux reads. Linux also has no word segmenter, so flowed Thai, Lao, Khmer, and Myanmar text breaks only at spaces.

  • Out on purpose for v1. Camera access, a gesture arena, general layout animation, grid or masonry virtualization, a general webview beyondiframe , and a devtools UI.

  • No packaged CLI. You work from a source checkout.exact new apps refer to it by path.

Path What's there
kernel/ exact-kernel : the node arena, wire frames, transactions, Taffy layout, and the export.kernel/tables/schema.json declares every node type, property, and opcode.
contract/ The Contract compiler ( syntax →types →analyze →lower ), its CLI, and the corpus
plan/ , runner/ The plan format, generated from one JSON authority, and the VM that runs it
motion/ CSS transitions, springs, and the seekable clock for non-web hosts
host/ web/ ,web-js/ (the JS target),apple/ (Rust core, ExactKit Swift package, AppKit and UIKit),linux/ ,render/ (server pages), plus update adapters and rasterizers
js/ , logic/ , data/ The TypeScript executor (Hermes, and the browser's engine on the web), Rust logic modules, and the data seam
gpu/ , canvas/ , textflow/ , markdown/ Capabilities loaded on demand: the wgpu canvas, Canvas 2D, text around shapes, and the Markdown editor
route/ , update/ , bake/ , filesystem/ The router, the signed update store, delivery baking, and app-owned files
apps/ , examples/ , game/ The apps above, the Calendar example, and the optional game engine
scripts/ exact.mjs ,agent.mjs ,smoke.mjs ,metrics.mjs ,deploy.mjs , and the checks
docs/ Guides for app authors: start here, the Contract grammar, recipes, and pitfalls
rules/ RULES.md , how work on Exact happens
vendor/taffy/ Taffy, plus Exact's patches

Start with rules/RULES.md, one page on how work happens here. Agents should also read AGENTS.md. In short:

Six blocking checks, 65 seconds in total. Anything slower runs asynchronously, per commit.

cargo build --all-targets --keep-going
cargo test --tests --no-fail-fast
cargo clippy --all-targets --keep-going -- -D warnings && cargo fmt --all -- --check
bun scripts/caps.mjs     # budgets: 1,500 lines per source file, document caps
bun scripts/boot.mjs     # counts the module graph before first pixel
cargo check --workspace --all-targets --keep-going   # every crate compiles

The first three build and test Cargo's default-members . The last compiles every other crate too, so a host or an app outside them cannot stop compiling unseen. It needs the pinned Bun (package.json ) first onPATH : app crates' build scripts run it. #

Verify by running, not by reading. Build the app a change touches and drive it:bun scripts/smoke.mjs <web|macos|ios|linux> drives the whole app on that host. #

Delete; don't deprecate. Before 1.0 there are no compatibility shims. Generated files are built, never committed. #

Agents remove apparatus freely and add none (checks, scripts, registries, design documents) without a person saying so. #

A spec gets written only when it has an implementer and a date.

Detailed operational notes cover toolchains, the Contract CLI's JSON formats, source maps, TypeScript and Rust data modules, storage, live replacement, delivery trust, the install page, and the stress harnesses. They're in docs/reference.md.

The workspace is MIT-licensed (Cargo.toml).

Exact’s Snapback4 adapter uses published 0.4.33 with the matching immutable Rust source revision 3653cae823145170df3e720f5e321bd1b5b4d79d. See the snapback4 package's README for wiring and device rebuilds.

── more in #developer-tools 4 stories · sorted by recency
── more on @expo 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/exact-a-new-cross-pl…] indexed:0 read:29min 2026-11-09 · —