# {{NAME}}
A generated Frame application: a Rust composition host, a Gleam component
compiled to BEAM bytecode, and a browser page — one binary, one config, one
command. The whole workflow is five `frame` verbs.
## Prerequisites
`frame doctor` walks all of these and prints an install link for anything
missing:
- Rust 1.89+
- Gleam 1.17.0+ and Erlang/OTP (checked before every component build; a
missing toolchain fails loudly with an install link)
- Node 20+ (typechecking and the browser proof; not needed to run the app —
see below)
- An installed Chrome, only for `frame test`'s real-browser proof — not
required to build or run the application itself
## Run it
```sh
frame run
```
That is the whole first session: it builds whatever changed, boots the full
stack, prints the URL, and tears everything down cleanly on Ctrl-C. A fresh
application runs before `npm` has ever been invoked — the page ships
compiled, and its one runtime dependency is vendored at `page/dist/vendor/`
and resolved by the import map in `page/dist/index.html`, so nothing from
`node_modules` is ever served.
There is no bundler and no dev server: the page compiles with plain `tsc`
(`page/src` into `page/dist` as native ES modules), and `frame run`
recompiles it only when sources changed. The first time you edit the page
you will need its type toolchain once: `frame check` and `frame test`
install it for you (`npm install` inside `page/`, announced, network), or
run `npm --prefix page install` yourself.
Open the URL the host prints. The page fetches `/frame/config.json` to learn
the running host's messaging endpoint and credential, fetches
`/frame/app/status.json` for everything that already happened in this process
before the page connected (the component's lifecycle so far and any
announced facts), then joins the messaging server directly as a participant
for what happens next. It shows: your component's real lifecycle
(registered, starting, running), the entity your host just stored, and a
receipt for any message you send from the page.
## Prove it
```sh
frame test
```
One verdict: the Gleam component tests, the host's Rust suite (including the
full-stack boot proof against the real served page), and a real, installed
Chrome driven through the live application. Narrow with `--component`,
`--host`, or `--browser` when iterating; the default always runs everything —
a proof that skips the browser is not the proof.
```sh
frame check
```
Seconds, no build artifacts: `tsc --noEmit` over the page, `gleam check`
over the component, `cargo check` over the host, and validation of
`frame.toml` itself.
`frame build` builds without booting, and `frame doctor` re-walks the
prerequisite tools any time.
## What's here
- `host/` — the Rust composition host. It declares the component's identity
in code (no manifest file format is invented), reconciles durable state
before starting anything, and hands its component and stated runtime
policy to Frame's embedding seam, which boots the component, the embedded
messaging server, and the page server together. Stopping the host
preserves storage — nothing here deletes an entity.
- `component/` — the Gleam project, compiled to BEAM bytecode and embedded
into the host binary at build time.
- `page/` — the browser page: fetches its config, fetches the boot-history
snapshot, then joins the messaging server directly (no fallback feed — a
bad config, an unreachable snapshot, or a dead connection is a loud, typed,
in-page error), and renders the host's real events as they happen.
`page/dist` is the servable root: the scaffold-authored `index.html`,
`styles.css`, and `vendor/` (the messaging SDK's own single-file,
self-contained browser build) live there, and the compiled `page/src`
modules sit beside them — shipped pre-compiled, recompiled by `frame run`
or `frame build` when you edit `page/src`. Its whole dark theme is
`page/dist/styles.css` — system fonts only, nothing fetched from outside,
so the page looks the same offline.
- `frame.toml` — the one file describing the whole stack. It pins no ports:
the page server prefers http://127.0.0.1:4190 and walks forward to a free
port (the chosen URL is printed on boot), and the embedded messaging bus
binds OS-assigned ports advertised to the page through `/frame/config.json`,
so two copies run side by side with no edits. To pin a port, state
`[frame].bind` and a full `[bus]` section — a stated port is then law and a
collision fails loudly instead of moving.
## How the page finds the running host
- `GET /frame/config.json` — the messaging endpoint, credential, and channel
the page should use, served on the same HTTP surface as the page.
- `GET /frame/app/status.json` — a snapshot of this process's whole history
so far: every component lifecycle transition and every announced fact, in
order. The page fetches this once, before it ever subscribes to the live
channel, so it never races the host's own events at boot.
- The live channel named by `frame.toml`'s `[frame].channel` — where the page
keeps watching after the snapshot, matching each live event against what
the snapshot already showed so nothing is double-counted.
## Learn more
- Building applications on Frame: https://github.com/ablative-io/frame/blob/main/docs/guides/BUILDING-APPS.md
- Frame vocabulary: https://github.com/ablative-io/frame/blob/main/docs/NOMENCLATURE.md