Exact
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
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.
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.
Contents
Three principles
How it works
Quick start
Contract
Example apps
What works today, and what doesn't yet
Repository map
Working on Exact
Three principles
1. Agent native
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 says
clock +60000
or
clock settle
and reads the result. No timing flakes, no waits.
Tests are scripts of those operations.
An
app.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
"
2. Speed is king
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 in
apps/realworld
).
Edits land in about a tenth of a second.
On a fresh app made by
exact new
, a
saved Contract or TypeScript edit was rebuilt and in the page in 99 ms.
3. Write the web, ship the platform
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
.
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.
How it works
flowchart LR
subgraph author["You write"]
C["app.contract
view · state · actions"]
D["app.ts or a Rust crate
data sources"]
end
C --> K["Contract compiler"] --> P["plan
tables + bytecode"]
P --> W["Web
one ES module, ~20 KB runtime,
the browser's DOM and CSS"]
P --> R["Rust runner
+ layout kernel (Taffy)"]
R --> M["macOS
AppKit + CoreText"]
R --> I["iOS
UIKit + CoreText"]
R --> L["Linux
vello GPU / tiny-skia CPU"]
D -.->|answers, checked against declared shapes| W
D -.-> R
Loading
Contract
(
contract/
) is