{"slug": "exact-a-new-cross-platform-ui-framework-by-the-expo-team", "title": "Exact – a new cross platform UI framework by the Expo team", "summary": "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.", "body_md": "**Write an app once. It runs natively on the web, macOS, iOS, and Linux, and an AI\nagent can build it, run it, see it, and test it on every one of them.**\n\nTip\n\n**Try it with a coding agent.** On a Mac with Xcode, Rust, Bun, and Chrome installed,\ngive Claude Code this prompt:\n\n```\nClone https://github.com/expo/exact and follow its README to make a new Exact\napp with `exact new`. Run `bun scripts/exact.mjs setup` once first. Make a todo\nlist where I can add items, check them off, delete them,\nand see how many are left. Put the view in Contract and keep the list in `app.ts`.\nWrite an `app.test.contract`, pass it on web, macOS, and the iOS Simulator with\n`scripts/agent.mjs`, then open the app for me on all three.\n```\n\nA 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.\n\n| Web | iOS | macOS | \n|---|---|---|\n| The browser's own DOM and CSS | UIKit views, native controls and the system's tab bar | AppKit views and native controls | \n\n<sub>One `app.contract`, the same screen on three hosts, captured by the agent driver. [Softwear](https://github.com/expo/exact-templates/tree/main/softwear) is a demonstration store you can generate from [exact-templates](https://github.com/expo/exact-templates): it shows Alternative Apparel's catalog, is not affiliated with that brand, and takes no real orders or payments.</sub>\n\nExact is a cross-platform application runtime. You describe your interface in\n**Contract**, a small declarative language, and you write your data logic in\n**TypeScript or Rust**. The Contract compiler turns the interface into a compact\n*plan*. On the web, that plan becomes one ES module over a ~20 KB runtime that drives\nthe real DOM. On macOS, iOS, and Linux, a Rust runner executes the same plan, a Rust\nlayout kernel computes every box with CSS rules, and each platform draws with its own\ntools: AppKit, UIKit, or a GPU painter. There is no webview and no JavaScript bridge\nin the UI, and no app JavaScript runs before the first pixel.\n\nNote\n\nExact is pre-1.0 and changes daily. There is no API stability, no backwards compatibility, and no migration guide, by design.\n\nUpgrade note: Snapback mutation replies with `ok: false` now throw\n`refused(why)` and expose `failure(slot)`; the\n[snapback4 package's README](https://www.npmjs.com/package/snapback4)\nhas the before/after and inherited-app commands.\n\nExact 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.\n\n- **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\niPhone), and Linux through one script,`scripts/agent.mjs` . There are ten on purpose:\nan agent API grows one reasonable addition at a time until no one can hold it in mind.\n- **The clock belongs to the agent.** Between two operations nothing moves. Instead of\nsleeping until an animation finishes, the agent says`clock +60000` or`clock settle` and reads the result. No timing flakes, no waits.\n- **Tests are scripts of those operations.** An`app.test.contract` file runs unchanged\non any host, with no second evaluator.\n- **Every tool speaks to machines.**`contract build --json` gives diagnostics with\nstable ids and source ranges.`contract symbols` gives navigation. A development\nsource map leads from any node on screen back to the line that declared it.\nThe driver refuses to drive a build older than its sources and names the rebuild.\n- **Contract is small on purpose.** A whole app fits in a context window. Every write\nis declared, every list is keyed, every value is typed. Mistakes are refusals that\nsay what to do, not quiet misbehavior.\n\n```\nbun scripts/agent.mjs web tree \"tap change-station\" \"type station-search Palo\" \\\n  \"clock +60000\" state \"screenshot out.png\"\n```\n\nSpeed of the app and speed of the loop. Each budget in `rules/RULES.md` is tracked on\nevery commit, and a regression is a P0 with a name on it.\n\n| Budget | Target | \n|---|---|\n| Cold start to interactive first frame | 100 ms p50 | \n| Dev restart, edit to present | 100 ms p50 | \n| App JavaScript executed before first pixel | none | \n| Touch one line, rebuild that crate | 30 s | \n| The whole blocking check suite | 65 s | \n\nHow the design keeps those numbers:\n\n- **The boot path executes and compiles nothing.** TypeScript is compiled to Hermes\nbytecode at build time and loads after the first pixel. The first frame comes from\ndata baked into the plan.\n- **Pay only for what you use.** GPU rendering, the Markdown editor, text flow, and\nbrowser SQLite are separate artifacts, loaded on demand. Nothing is a feature flag\non a core crate.\n- **The web gets its own small runtime.** On the RealWorld (\"Conduit\") benchmark,\nExact's runtime was up about 300 ms before the React SSR build had hydrated. It used\n26 KB of code against React's 81 KB (the RealWorld benchmark in`apps/realworld` ).\n- **Edits land in about a tenth of a second.** On a fresh app made by`exact new` , a\nsaved Contract or TypeScript edit was rebuilt and in the page in 99 ms.\n\nWhat 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?\n\n- Layout is CSS's, because a person can't see a layout default, only its result. A bare\nnode is `display: block` ,`box-sizing: content-box` ,`flex-direction: row` ,`flex-shrink: 1` , the same as a bare`<div>` .\n- 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\nrefuses an alias and names the web's spelling.\n- Unsaid presentation is the platform's. Text is the system's label colour and size (17\npoints on an iPhone, 13 on a Mac, the reader's setting in a browser), a `button` is the\nsystem's button, a pushed screen has the system's Back. An app that says less looks\nmore at home.\n- 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.\n- The web is also the development loop. You work on the seconds-long web loop, and the native hosts are swept behind you.\n- Any unavoidable deviation in layout is declared, with its reason. For example, Taffy\nhas no `position: static` .\n\nWriting the web doesn't mean the lowest common denominator. Where a platform can do\nmore (real HTML in an SVG `foreignObject` on the web, MapKit through a native module on\nApple), Exact lets it.\n\n```\nflowchart LR\n  subgraph author[\"You write\"]\n    C[\"app.contract<br/>view · state · actions\"]\n    D[\"app.ts or a Rust crate<br/>data sources\"]\n  end\n  C --> K[\"Contract compiler\"] --> P[\"plan<br/>tables + bytecode\"]\n  P --> W[\"Web<br/>one ES module, ~20 KB runtime,<br/>the browser's DOM and CSS\"]\n  P --> R[\"Rust runner<br/>+ layout kernel (Taffy)\"]\n  R --> M[\"macOS<br/>AppKit + CoreText\"]\n  R --> I[\"iOS<br/>UIKit + CoreText\"]\n  R --> L[\"Linux<br/>vello GPU / tiny-skia CPU\"]\n  D -.->|answers, checked against declared shapes| W\n  D -.-> R\n```\n\n- **Contract** (`contract/` ) is compiled in four passes: syntax, types, analysis, and\nlowering. At build time the plan is*baked* : constant data is evaluated into it, so\nthe first frame needs nothing else.\n- **The runner** (`runner/` ) is the plan's virtual machine. It handles keyed component\ninstances, events, timers on a seekable clock, and the data seam.\n- **The kernel** (`kernel/` ) is a columnar node arena with CSS layout through a patched[Taffy](https://github.com/expo/exact/blob/main/vendor/taffy/EXACT-PATCHES.md) . The host measures text: CoreText on Apple,\ncosmic-text on Linux. On the web, the browser lays out the same CSS itself, so the web\nbuild carries no layout engine.\n- **Hosts** (`host/` ) present the tree. The web host writes DOM nodes, and the browser\nruns CSS transitions. The Apple host is a static library with a C ABI under AppKit and\nUIKit presenters. The Linux host paints the tree itself, onto DRM/KMS with evdev\ninput, or into an offscreen buffer.\n- **Data sources** sit below one seam. TypeScript runs on Hermes on native hosts and in\nthe browser's own engine on the web. Rust runs natively, and can be replaced live\n(as a shared library, or as Wasm on iOS and the web). Every answer is checked against\nthe shape Contract declared. Grants control network, files, SQLite, and secrets.\n- **Optional pieces** are separate artifacts: a wgpu GPU module (Metal on Apple, WebGPU\nin the browser), native modules (a hyphenated tag backed by a platform widget, like\nMapKit), and a[game engine](https://github.com/expo/exact/blob/main/game/README.md) add-on.\n\nThese steps were run on macOS on Apple Silicon. Linux works for the web loop and the Linux host.\n\n- \n**Rust** through[rustup](https://rustup.rs) . The pinned stable toolchain in`rust-toolchain.toml` installs itself the first time Cargo runs.\n- \n**[Bun](https://bun.sh) 1.4.2** , the version`package.json` pins. Node and npm are\nnot needed.\n- \n**Google Chrome.** The agent drives the web through headless Chrome. Set`CHROME` to\nuse another Chromium.\n- \n**Xcode** , for the macOS and iOS hosts.\n- \n**Hermes** , only for TypeScript apps on native hosts. Exact consumes Ibex's\npinned, attested`260318099.0.4` release bundle; ordinary Cargo builds are\noffline and never download or compile an engine. Install it once for each\ntarget before building (for this Mac, use`aarch64-apple-darwin` ):\n\n```\ncargo run --manifest-path vendor/ibex/crates/hermes-lean-sys-installer/Cargo.toml -- --target <triple>\n```\n\n The installer verifies the bundle and publishes it under `~/.cargo/hermes-lean-sys/` . The v4 set supports macOS, Linux, Windows, iOS\ndevices and the universal iOS Simulator, plus tvOS devices and arm64 tvOS\nSimulators. Linux and Windows select Ibex's English`intl` tier; Apple keeps\nHermes's OS-backed Intl.`HERMES_LEAN_SYS_DIR` is only an explicit development\noverride, not normal setup.\n\nTo install the pinned Bun beside any existing installation:\n`curl -fsSL https://bun.sh/install | BUN_INSTALL=~/.bun-1.4.2 bash -s bun-v1.4.2`.\nUse `~/.bun-1.4.2/bin/bun` for the commands below if it is not on your PATH.\n`bun scripts/exact.mjs setup` installs the declared stable and web nightly Rust\ntoolchains, their components/targets (the nightly's clippy lints a game's web bake),\nmatching wasm-bindgen, pinned Binaryen and Bun dependencies, installs the pinned\nHermes host bundle and the iOS/tvOS bundles this Mac builds, and fetches the crates\nof exact2's lock and the game SDK's (`game/app/shells.lock`), since every bake\nresolves offline. It keeps Binaryen in `~/.cache/exact/binaryen`; builds find it\nautomatically. `setup --check` invokes Ibex's installer in its offline check mode for\nthat same target set. The resolver authenticates the canonical receipts, archives,\ncompiler and host/target HBC pairing without installing anything; its exit status and\ndiagnostic are the check's result.\n\n```\ngit clone https://github.com/expo/exact.git\ncd exact\nbun scripts/exact.mjs setup\nbun host/web/dev.mjs            # Caltrain, at http://127.0.0.1:8765/\n```\n\nThe first run compiles the toolchain, which takes a few minutes. After that, open\n[`apps/caltrain/app.contract`](https://github.com/expo/exact/blob/main/apps/caltrain/app.contract), change some text, and save.\nThe page rebuilds and reloads in about a tenth of a second. Add `--lan` to open the\nsame page from a phone on your network. The server answers only to the names it\nprints (a DNS-rebinding guard; any other `Host` gets 421); to share it through a\ntunnel, name the tunnel's host with `--allow-host <name>`\n(repeatable; `name:port` pins a port). An allowed name reaches the page and its\nreload stream, never the local installer's token.\n\n```\nbun host/apple/build.mjs --run              # macOS\nbun host/apple/build.mjs --ios --run        # an iOS Simulator (--device --run for a connected iPhone)\ncargo build --profile host-dev -p caltrain-linux   # Linux: a DRM/KMS console, or headless anywhere (--release to ship)\n```\n\nA first native build takes ten to fifteen minutes on a laptop; later builds reuse it\nand take a minute or two. Watch its output rather than waiting blind. iOS commands use an\niPhone simulator that's already booted, or boot the newest iPhone Pro. To choose one,\nset `EXACT_SIM` to its name or UDID (or pass `--sim` to `build.mjs`), and keep the same\nsetting for `agent.mjs ios`.\n\nOn Linux, a development build can serve its screen with `EXACT_VNC=1` and\n`EXACT_VNC_PASSWORD` set to 1–8 bytes. It listens on `127.0.0.1:5900`;\nan explicit `EXACT_VNC=host:port` selects another address. Production bakes\nignore both variables.\n\n```\nbun host/web/build.mjs caltrain-web         # the build the agent's web host serves\nbun scripts/agent.mjs web tree \"tap change-station\" \"type station-search Palo\" state \"screenshot out.png\"\nbun scripts/agent.mjs web --test apps/caltrain/app.test.contract\n```\n\nSwap `web` for `macos`, `ios`, or `linux` once that host is built. Caltrain's three\ntests take about two seconds on the web host. Each operation is one shell argument, so\nquote it: `\"type new-todo Buy milk\"` types `Buy milk` (everything after the target is\nthe text). `\"screenshot film.png over 600 every 50\"`\nfilms motion as a contact sheet; use an `.apng` name to get an animation.\n\n```\nbun scripts/exact.mjs new ../hello          # or run `bun link` once, then `exact new ../hello`\ncd ../hello\nbun exact.mjs contract types app.contract -o app.contract.d.ts   # first run builds the compiler\nbun exact.mjs web                           # the dev loop, at http://127.0.0.1:8765/\nbun exact.mjs test web tests/*.test.contract # the app's *.test.contract, or the files named\nbun exact.mjs mac --run                     # this Mac\nbun exact.mjs ios --run                     # an iOS Simulator\n```\n\n`exact new` creates a standalone app: `app.contract` (the view), `app.ts` (its data),\n`app.json` (the manifest: name, bundle id, hosts, deploy policy), and small `web/` and\n`apple/` host crates. Its `AGENTS.md` (and `CLAUDE.md`) points a coding agent at\n[`docs/start-here.md`](https://github.com/expo/exact/blob/main/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\nand `contract vocab` for the card of what an app writes (`--all`: every tag and property Contract accepts). Before any of\nit, run `bun scripts/exact.mjs setup` once; `setup --check` names everything this machine\nis missing without changing it. It has its own Cargo workspace, which uses your exact2 checkout\nby path. To drive it from exact2, point `EXACT_APP_DIR` at it:\n\n```\nEXACT_APP_DIR=../hello bun host/web/build.mjs hello-web\nEXACT_APP_DIR=../hello bun scripts/agent.mjs web --app hello tree \"screenshot hello.png\"\n```\n\nLook at every screenshot, not just the test result: a test passes on a layout that\nspills off the screen. `bun exact.mjs agent <host> \"screenshot out.png\"` shows the app\non each host in its own session; for a copy you launched yourself, use\n`xcrun simctl io <device> screenshot out.png` on iOS.\n\n`bun scripts/exact.mjs` also runs apps from this repository as real Mac apps:\n`exact run markdown README.md`, or `exact install markdown` to put `mdview` on your\n`PATH`. `exact list` shows what's here.\n\nAn 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\n`test` or `verify`, end with one line naming it, at most once a day per project. A note stays in the app's\n`.exact/` with exact2's commit, the last command's verb, exit code and duration, and the\nOS; never any command output, or the app's code or files. It goes to Expo (`https://exact-diaries.ccheever-0f6.workers.dev`) only when\nthe project's answer allows: `always`, or a yes to `ask`, the default, after\n`bun exact.mjs feedback` shows exactly what would go. `bun exact.mjs feedback never`\nstops it for a project; `EXACT_FEEDBACK=0` silences the nudges and refuses every send\non the machine. [docs/diary.md](https://github.com/expo/exact/blob/main/docs/diary.md) has the details.\n\nAn agent building an app starts at [start-here.md](https://github.com/expo/exact/blob/main/docs/start-here.md), the only\nrequired reading, then looks up the [recipe](https://github.com/expo/exact/blob/main/docs/recipes) for each question. For more, read the complete [guide for humans](https://github.com/expo/exact/blob/main/docs/contract-for-humans.md),\nor look things up in the [guide for agents](https://github.com/expo/exact/blob/main/docs/contract-for-agents.md) or the\n[grammar and vocabulary reference](https://github.com/expo/exact/blob/main/docs/contract-grammar.md).\n\nContract 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.\n\nTo try it in an app created with `exact new`, replace `app.contract` and\n`app.test.contract` with the examples below. In `app.ts`, keep the generated\n`export const appId = …` line and replace the rest with the TypeScript below.\n\n```\n// app.contract: the view, its state, and what each action changes. It names no colour,\n// font or control size: each host draws its own heading, field, button, checkbox and list.\nshape Todo\n  id: string\n  title: string\n  done: bool\n\nshape Change\n  count: number\n\ncomponent Todos\n  state draft = \"\"\n  mutation changed as shape Change refreshes todos\n  resource todos = todos() as shape Todo[]\n  derive left = todos.filter(t => not t.done).length\n\n  action edit(value: string)\n    draft = value\n  action add\n    if draft.trim() != \"\"\n      send changed = addTodo(draft.trim())\n      draft = \"\"\n  action toggle(id: string, done: bool)\n    send changed = setDone(id, done)\n  action remove(id: string)\n    send changed = removeTodo(id)\n\n  view\n    list appearance=\"auto\" height=\"100%\"\n      section\n        header\n          text `${left} left` role=\"heading\" testId=\"count\"\n        row\n          input value=draft input=edit submit=add placeholder=\"What needs doing?\" aria-label=\"New todo\" testId=\"new-todo\" flex=1 min-width=0\n          button press=add buttonStyle=\"filled\" testId=\"add\"\n            text \"Add\"\n      section\n        for t in todos key=t.id\n          row testId=`todo-${t.id}`\n            input type=\"checkbox\" checked=t.done change=toggle(t.id) aria-label=t.title testId=`toggle-${t.id}`\n            text t.title flex=1\n            button press=remove(t.id) destructive=true testId=`remove-${t.id}`\n              text \"Delete\"\npython\n// app.ts: keeps the list, and answers what app.contract asks for\nimport type { Answer, Sources } from './app.contract.d.ts';\n\nexport const grants = '';  // e.g. 'net.fetch https://api.example.com'\n\ntype Todo = { id: string; title: string; done: boolean };\nlet todos: Todo[] = [];\nlet next = 1;\n\nconst sources: Sources = {\n  todos: () => todos,\n  addTodo: ([title]) => {\n    todos = [...todos, { id: String(next++), title, done: false }];\n    return { count: todos.length };\n  },\n  setDone: ([id, done]) => {\n    todos = todos.map((t) => (t.id === id ? { ...t, done } : t));\n    return { count: todos.length };\n  },\n  removeTodo: ([id]) => {\n    todos = todos.filter((t) => t.id !== id);\n    return { count: todos.length };\n  },\n};\n\nexport const answer: Answer = (source, args, store, storage, native) =>\n  sources[source](args, store, storage, native);\n// app.test.contract: runs on any host with `scripts/agent.mjs <host> --test`\ntest \"add, finish and delete\"\n  expect text \"count\" == \"0 left\"\n  type \"new-todo\" \"Buy milk\"\n  tap \"add\"\n  type \"new-todo\" \"Walk the dog\"\n  type \"new-todo\" key \"Enter\"\n  expect text \"count\" == \"2 left\"\n  tap \"toggle-1\"\n  expect text \"count\" == \"1 left\"\n  tap \"remove-1\"\n  expect tree missing \"todo-1\"\n  expect tree has \"todo-2\"\n```\n\n| Web | macOS | iOS Simulator | \n|---|---|---|\n\nThe view never edits the list itself. An action `send` s a *mutation* to a source,\n`app.ts` changes its data and answers, and `refreshes todos` asks for the list again.\nData lives in one place, and every change to it is a named, typed call an agent can see.\nThis list lives in memory. To keep it across launches, give `app.ts` a grant like\n`sqlite.open app:/data/todos.db` and use `storage.sqlite`, as\n[Fieldnotes](https://github.com/expo/exact/blob/main/apps/fieldnotes) does.\n\n| Concept | What it is | \n|---|---|\n| `shape` | A closed record type. Fields are `number` ,`string` ,`bool` , another shape,`option<T>` , or`T[]` . | \n| `component` | A unit of UI. The first one in a file is the root. Components take `props` , can`inject` what an ancestor`provide` s, and can fill a`slot` with`children` . | \n| `state` | A value the component owns, its type inferred or declared ( `state picked: option<Item> = none` ). Under a`for` , child state belongs to that keyed row. | \n| `derive` | A value computed from others, recomputed when they change. | \n| `resource` | Data from a source: `resource x = source(args) as shape T` . When the arguments change, the source is asked again. | \n| `mutation` /`send` | A change made through a source: `send x = source(args)` .`refreshes r` asks resource`r` again afterward,`pending(x)` and`failed(x)` show progress, and`failure(x)` says why a resource failed. | \n| `action` | The only place state changes. What it writes is inferred from its body, and `let` binds a local inside it. | \n| `task` | Work on a schedule: `every(1000, tick)` ,`after(ms, a)` ,`every(frame, a)` . | \n| `view` | Indented elements, `if … else` ,`for … key=…` (a key is required),`match` over options, and calls to other components. | \n| `style` /`class=` | A named set of CSS properties. The node's own attributes win, and there is no cascade. | \n| `fn` ,`xs.map` /`xs.filter` /`xs.join` | Pure, single-expression helpers. Recursion is refused. | \n| 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 under`stdlib` in[`plan/tables/format.json`](https://github.com/expo/exact/blob/main/plan/tables/format.json) . | \n| `routes` | A router: a location and a retained stack per tab, moved with `open` ,`push` ,`replace` , and`back` , and read with`top` . | \n| `font` | Declares a font family and binds it to files at build time. | \n| `use … from \"./x.contract\"` | Imports components, shapes, styles, and `fn` s from another Contract file. | \n| `testId` | A stable name for the agent, tests, and accessibility tools. | \n\n- **No JavaScript in the view, and no I/O.** Expressions have no effects. Effects\nhappen only in actions, as assignments,`send` ,`refresh` , or commands like`focus(…)` and`share(…)` . Data crosses one seam: a source answers, and the runner\nchecks the answer.\n- **No loops in actions, no `await`.** A list is a view's keyed` for` , a computation is a`derive` or a`fn` (an action may name a value with`let` ), and anything slower lives in a\nsource. A list that changes changes where its data lives, through a mutation, as\nin the example above. That's what lets the plan be baked, diffed, inspected, and\nexecuted the same way on four hosts.\n- **No escape hatch; access hatches.** Nothing steps outside the plan. Where an app\nneeds a platform widget, it uses a*native module* : a hyphenated tag like`exact-map` , backed by Swift, Kotlin or Rust, laid out by the kernel like any other box.\nWhere it needs the platform object Exact itself built (a`UIView` , a navigation\ncontroller, a DOM element), it marks the node`hatch=\"word\"` and its native code is\nhanded that object: an*access hatch* , the panel a machine is built with so its\ninsides can be reached. It configures what Exact made and changes Contract state\nonly by acting on authored nodes, as a person would.\n\nThese 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.\n\n``` bash\n$ cargo run -q -p contract -- build app.contract\napp.contract:4:5 [type-assign] `count` is `number`, cannot assign `string`\napp.contract:7:23 [lower-unknown-attr] `text` has no attribute `size`; `size` is spelled `font-size` here, the web's name\n```\n\nOne run reports up to twenty independent errors, not just the first. Other subcommands:\n`--json` for tools, `symbols` (definitions and references as JSON), `fmt`\n(source-preserving), `types` (generates `app.contract.d.ts` for TypeScript sources),\nand `rust` (shapes for a Rust data crate). The full language is specified in\n[`docs/contract-grammar.md`](https://github.com/expo/exact/blob/main/docs/contract-grammar.md).\n\nEach app lives in [`apps/<name>/`](https://github.com/expo/exact/blob/main/apps). Run one on the web with\n`bun host/web/dev.mjs --app <name>`, on macOS with\n`bun host/apple/build.mjs <name>-apple --run`, and on iOS by adding `--ios`. All of\nthese screenshots come from the web host, taken by `scripts/agent.mjs`.\n\nTo learn from, start with [Shelf](https://github.com/expo/exact/blob/main/apps/shelf), the recipe app. It names no colour and no\nfont size, and `scripts/no-tells.mjs` holds it to that, so it shows what an app that\nleaves presentation to the platform looks like on each host. Then read these four for\nwhat each teaches. They were written before Exact left presentation to the platform\nand paint their own colours and sizes, so take their structure, not their style:\n\n| App | What it teaches | \n|---|---|\n| [Caltrain](https://github.com/expo/exact/blob/main/apps/caltrain) | A Rust data crate, authored tests, routes, an optional GPU module | \n| [Weatherlight](https://github.com/expo/exact/blob/main/apps/weatherlight) | TypeScript with `fetch` and grants | \n| [Fieldnotes](https://github.com/expo/exact/blob/main/apps/fieldnotes) | Storage: SQLite, files, backup and restore | \n| [Recorder](https://github.com/expo/exact/blob/main/apps/recorder) | The microphone through `@exact/media-capture` and`@exact/recording` | \n\n| [**Caltrain**](https://github.com/expo/exact/blob/main/apps/caltrain)The 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. | [**Weatherlight**](https://github.com/expo/exact/blob/main/apps/weatherlight)Weather from Open-Meteo under a GPU-animated sky that follows the hour you pick. TypeScript data and a wgpu module. | [**Spark**](https://github.com/expo/exact/blob/main/apps/spark)A swipe deck: throw a card with your finger, and it flies off with your release velocity. Pan gestures and springs on every host. | [**Expose**](https://github.com/expo/exact/blob/main/apps/expose)A pretend phone OS (lock screen, home, Messages with an AI assistant, Reader) set in custom fonts. TypeScript. The assistant needs an OpenRouter key. | \n| [**Calendar**](https://github.com/expo/exact/blob/main/examples/calendar)Month pages, draggable sheets, events dragged between days, wallpaper themes, SQLite. It lives outside `apps/` , so set`EXACT_APP_DIR` to run it. | [**Sparkline**](https://github.com/expo/exact/blob/main/apps/sparkline)A market list of animated SVG charts that draw in and pulse. | [** Photo Editor**](https://github.com/expo/exact/blob/main/apps/photo-editor)Rotate, pan, and crop, through a native module. | [** Video Player**](https://github.com/expo/exact/blob/main/apps/video-player)A bundled clip that shrinks out of the way when the keyboard opens. | \n\n| [**Exact Live**](https://github.com/expo/exact/blob/main/apps/exact-live)A 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)](https://github.com/expo/exact/blob/main/apps/interaction-gallery)Drag to reorder, pan and zoom photos, a resizable sheet with a nested list, and 100 to 25,000 records. | \n| [**Reflow**](https://github.com/expo/exact/blob/main/apps/reflow)Seven 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. | [**Textflow**](https://github.com/expo/exact/blob/main/apps/textflow)Text around shapes with CSS's `shape-outside` and`wrap-flow` : draggable orbs, a dancer, an editorial layout. One Rust walker shared by every host. | \n| [**Markdown**](https://github.com/expo/exact/blob/main/apps/markdown)A Markdown reader that opens files from Finder or the command line ( `mdview README.md` ). Its sibling[**LLP**](https://github.com/expo/exact/blob/main/apps/llp) (`llpview` ) reads a folder of numbered design documents. | [** Conduit** (RealWorld)](https://github.com/expo/exact/blob/main/apps/realworld)The RealWorld Medium clone against its hosted API. It's the benchmark that compared Exact's web build with React 19. Web only. | \n\n| [**SVG Gallery**](https://github.com/expo/exact/blob/main/apps/svg-gallery) : SVG as the browser renders it, one page per feature, held to Chrome's pixels on every host. | \n| [**Canvas Gallery**](https://github.com/expo/exact/blob/main/apps/canvas-gallery) : the HTML Canvas 2D API drawn by the browser, Core Graphics, or tiny-skia, and compared to Chrome. | \n\n| App | What it shows | Notes | \n|---|---|---|\n| [Fieldnotes](https://github.com/expo/exact/blob/main/apps/fieldnotes) | A local notebook with pins, search, SQLite storage, and JSON backup and restore | TypeScript + Rust | \n| [Messages](https://github.com/expo/exact/blob/main/apps/messages) | A port of Expo's chat demo, talking to models through OpenRouter | Needs `bun apps/messages/service.ts` and an OpenRouter key | \n| [Type Tour](https://github.com/expo/exact/blob/main/apps/typetour) | Phone-OS screens as a type specimen for the Expose fonts | No JavaScript at all | \n| [Map Demo](https://github.com/expo/exact/blob/main/apps/map-demo) | Native modules: MapKit on Apple and OpenStreetMap on the web |  | \n| [Carousel](https://github.com/expo/exact/blob/main/apps/carousel) | A horizontal virtualized list of 25,000 cards |  | \n| [Motion Gallery](https://github.com/expo/exact/blob/main/apps/motion-gallery) | Animated GIF and WebP images and CSS keyframes, compared across hosts |  | \n| [Update Lab](https://github.com/expo/exact/blob/main/apps/update-lab) | Live replacement of Contract, TypeScript, and Rust in a running app | Hand-testing lab | \n| [Messages Stress](https://github.com/expo/exact/blob/main/apps/messages-stress) ,[Completion Storm](https://github.com/expo/exact/blob/main/apps/completion-storm) ,[Markdown Stress](https://github.com/expo/exact/blob/main/apps/markdown-stress) | 100k-message histories, 128 parallel requests, and 4 MiB documents, under load | Opt-in workloads | \n| [Native Fixture](https://github.com/expo/exact/blob/main/apps/native-fixture) ,[Auth Fixture](https://github.com/expo/exact/blob/main/apps/auth-fixture) | The native-module interface; OAuth with PAR, DPoP, and PKCE against a local server | Test fixtures | \n| [Beacons, Tennis, …](https://github.com/expo/exact/blob/main/game/games) | Games on the optional [engine add-on](https://github.com/expo/exact/blob/main/game/README.md) : Rust gameplay, Contract menus | `bun game/dev.mjs beacons` | \n\nThe v1 bar: *one real application, not a demo, runs from a\nsingle Contract source on web, macOS, iOS, and Linux, within the time budgets in\n`rules/RULES.md`.* Caltrain is that application, and it runs on all four. Everything\nelse here was admitted because a real app needed it.\n\n- **Four hosts.** Web, macOS (AppKit, no SwiftUI), iOS (UIKit, on the Simulator and\non devices), and Linux (GPU or CPU painting, DRM/KMS or headless). Existing Swift apps\ncan embed Exact sessions through ExactKit.\n- **Layout and text.** CSS block, flex, and grid;`position: static` and containing\nblocks;`aspect-ratio` ; declared fonts; text around shapes.\n- **Paint.** Gradients, shadows,`backdrop-filter` , glass materials, complete SVG,\nCanvas 2D on every host, and a wgpu GPU canvas loaded after first pixel.\n- **Motion.** CSS transitions, springs,`@keyframes` , follow-and-release gestures (pan,\nswipe, pinch) with release velocity, layout transitions, exit animations, and\nscroll- or drag-linked timelines.\n- **Lists.** Virtualized lists, vertical or horizontal, one level deep. The rows of a\nvertical`list` are its platform's: UIKit's separators and highlight, a table on the Mac, one\nline between rows on the web, with nothing drawn by the app.\n- **Controls and media.** Inputs,`select` , a WYSIWYG Markdown editor on the web, macOS,\nand iOS, images including animated ones, video, pickers for an app's declared file\ntypes, and share.\n- **Data.** TypeScript or Rust sources, optionally on a worker. Grant-checked`fetch` and\nstreams, files and SQLite (native and in the browser), Keychain secrets, and OAuth\nsign-in. A router keeps a stack per tab.\n- **Native modules.** Platform widgets as hyphenated tags.\n- **The web, server side.** Pages pre-rendered at build time or per request by a Rust\nrenderer, then adopted in place instead of hydrated.\n- **Delivery.**`scripts/deploy.mjs` publishes the web root and signed update bundles,\nwhich installed apps fetch after first pixel. Apple apps check every fifteen\nminutes 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\nreplaced live in development. Every web build gets an install page, and`exact release` signs and notarizes a Mac app.\n- **Agents and tests.** The ten operations on every host, including a physical\niPhone; Contract tests; source maps; filmed screenshots.\n\n- **Windows.** Apps default to ink on a DirectX 11 composition swap chain,\nor CPU when the manifest declares`game` .`host.windows.painter: \"ink\"` or`\"cpu\"` selects either painter, and`EXACT_PAINTER` overrides both.\nSkirmish is a standalone game built with Exact's optional game engine;\nthe CPU default preserves its module-owned window presentation.[Windows Desk](https://github.com/expo/exact/blob/main/apps/windows-desk/README.md) demonstrates\nnative controls over Contract and the kernel with an app-local Win32 presenter;\ngeneral Windows control parity and delivery remain unfinished.\n- **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, and`bun exact.mjs agent android …` drives it. It needs the Android SDK, the Rust target`aarch64-linux-android` , and the Hermes Android bundle\n(`bun scripts/hermes-android.mjs build` ) until an Ibex release ships one. Delivery and\nstore packaging don't exist yet.\n- **No JSX or React tier.** Nothing runs JavaScript above the data seam. The door stays\nopen, but no one is building it.\n- **TypeScript can't import npm packages' code yet.**`app.ts` imports its own local\nfiles; from a package it imports only types (`import type` ), which the build checks\nand never runs.\n- **Code updates in production.** Signed delivery covers plans and assets. Signed\ndelivery of TypeScript or Rust modules isn't implemented, and app-store rules limit\nit anyway.\n- **Linux editing.** The web and Apple hosts edit; Linux reads. Linux also has no\nword segmenter, so flowed Thai, Lao, Khmer, and Myanmar text breaks only at spaces.\n- **Out on purpose for v1.** Camera access, a gesture arena, general layout animation,\ngrid or masonry virtualization, a general webview beyond`iframe` , and a devtools UI.\n- **No packaged CLI.** You work from a source checkout.`exact new` apps refer to it by\npath.\n\n| Path | What's there | \n|---|---|\n| [`kernel/`](https://github.com/expo/exact/blob/main/kernel) | `exact-kernel` : the node arena, wire frames, transactions, Taffy layout, and the export.[`kernel/tables/schema.json`](https://github.com/expo/exact/blob/main/kernel/tables/schema.json) declares every node type, property, and opcode. | \n| [`contract/`](https://github.com/expo/exact/blob/main/contract) | The Contract compiler ( `syntax` →`types` →`analyze` →`lower` ), its CLI, and the corpus | \n| [`plan/`](https://github.com/expo/exact/blob/main/plan) ,[` runner/`](https://github.com/expo/exact/blob/main/runner) | The plan format, generated from one JSON authority, and the VM that runs it | \n| [`motion/`](https://github.com/expo/exact/blob/main/motion) | CSS transitions, springs, and the seekable clock for non-web hosts | \n| [`host/`](https://github.com/expo/exact/blob/main/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 | \n| [`js/`](https://github.com/expo/exact/blob/main/js) ,[` logic/`](https://github.com/expo/exact/blob/main/logic) ,[` data/`](https://github.com/expo/exact/blob/main/data) | The TypeScript executor (Hermes, and the browser's engine on the web), Rust logic modules, and the data seam | \n| [`gpu/`](https://github.com/expo/exact/blob/main/gpu) ,[` canvas/`](https://github.com/expo/exact/blob/main/canvas) ,[` textflow/`](https://github.com/expo/exact/blob/main/textflow) ,[` markdown/`](https://github.com/expo/exact/blob/main/markdown) | Capabilities loaded on demand: the wgpu canvas, Canvas 2D, text around shapes, and the Markdown editor | \n| [`route/`](https://github.com/expo/exact/blob/main/route) ,[` update/`](https://github.com/expo/exact/blob/main/update) ,[` bake/`](https://github.com/expo/exact/blob/main/bake) ,[` filesystem/`](https://github.com/expo/exact/blob/main/filesystem) | The router, the signed update store, delivery baking, and app-owned files | \n| [`apps/`](https://github.com/expo/exact/blob/main/apps) ,[` examples/`](https://github.com/expo/exact/blob/main/examples) ,[` game/`](https://github.com/expo/exact/blob/main/game) | The apps above, the Calendar example, and the optional game engine | \n| [`scripts/`](https://github.com/expo/exact/blob/main/scripts) | `exact.mjs` ,`agent.mjs` ,`smoke.mjs` ,`metrics.mjs` ,`deploy.mjs` , and the checks | \n| [`docs/`](https://github.com/expo/exact/blob/main/docs) | Guides for app authors: start here, the Contract grammar, recipes, and pitfalls | \n| [`rules/`](https://github.com/expo/exact/blob/main/rules) | [` RULES.md`](https://github.com/expo/exact/blob/main/rules/RULES.md) , how work on Exact happens | \n| [`vendor/taffy/`](https://github.com/expo/exact/blob/main/vendor/taffy) | Taffy, plus Exact's patches | \n\nStart with [`rules/RULES.md`](https://github.com/expo/exact/blob/main/rules/RULES.md), one page on how work happens here. Agents\nshould also read [`AGENTS.md`](https://github.com/expo/exact/blob/main/AGENTS.md). In short:\n\n- \n**Six blocking checks, 65 seconds in total.** Anything slower runs asynchronously,\nper commit.\n\n```\ncargo build --all-targets --keep-going\ncargo test --tests --no-fail-fast\ncargo clippy --all-targets --keep-going -- -D warnings && cargo fmt --all -- --check\nbun scripts/caps.mjs     # budgets: 1,500 lines per source file, document caps\nbun scripts/boot.mjs     # counts the module graph before first pixel\ncargo check --workspace --all-targets --keep-going   # every crate compiles\n```\n\n The first three build and test Cargo's `default-members` . The last compiles every\nother crate too, so a host or an app outside them cannot stop compiling unseen. It\nneeds the pinned Bun (`package.json` ) first on`PATH` : app crates' build scripts run it.\n- \n**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.\n- \n**Delete; don't deprecate.** Before 1.0 there are no compatibility shims. Generated\nfiles are built, never committed.\n- \n**Agents remove apparatus freely and add none** (checks, scripts, registries, design\ndocuments) without a person saying so.\n- \n**A spec gets written only when it has an implementer and a date.**\n\nDetailed operational notes cover toolchains, the Contract CLI's JSON formats, source\nmaps, TypeScript and Rust data modules, storage, live replacement, delivery trust, the\ninstall page, and the stress harnesses. They're in\n[`docs/reference.md`](https://github.com/expo/exact/blob/main/docs/reference.md).\n\nThe workspace is MIT-licensed (`Cargo.toml`).\n\nExact’s Snapback4 adapter uses published **0.4.33** with the matching immutable\nRust source revision `3653cae823145170df3e720f5e321bd1b5b4d79d`.\nSee [the snapback4 package's README](https://www.npmjs.com/package/snapback4) for wiring and device rebuilds.", "url": "https://wpnews.pro/news/exact-a-new-cross-platform-ui-framework-by-the-expo-team", "canonical_source": "https://github.com/expo/exact", "published_at": "2026-11-09 22:59:13+00:00", "updated_at": "2026-11-09 23:23:18.658005+00:00", "lang": "en", "topics": ["developer-tools", "ai-agents", "ai-tools"], "entities": ["Expo", "Exact", "Contract", "Claude Code", "Hermes", "Softwear", "Alternative Apparel"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/exact-a-new-cross-platform-ui-framework-by-the-expo-team", "markdown": "https://wpnews.pro/news/exact-a-new-cross-platform-ui-framework-by-the-expo-team.md", "text": "https://wpnews.pro/news/exact-a-new-cross-platform-ui-framework-by-the-expo-team.txt", "jsonld": "https://wpnews.pro/news/exact-a-new-cross-platform-ui-framework-by-the-expo-team.jsonld"}}