# {{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 six `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.
## Develop it
```sh
frame dev
```
One command to a running node that STAYS attached: edit anything under
`component/src/` (or `component/gleam.toml`) and the loop rebuilds the
component and hot-swaps it against the running node — the page keeps
serving, the messaging server keeps its connections, and your stored
entities survive. Every swap is proven live (a mailbox round-trip and a
durable entity round-trip) before it is announced as
`RUNNING CURRENT generation N`; a compile error prints the compiler's own
diagnostics and the node keeps serving the last good code
(`RELOAD FAILED — RUNNING LAST GOOD`) until your next edit fixes it.
What a reload keeps and what it replaces: the component's identity, its
registration, and its durable state survive every swap; the component's
PROCESSES do not — actor memory starts fresh each generation (the host
source spells out the full table). The edit-quiet window that groups your
keystrokes into one rebuild is `--quiet-ms` (default 100).
`frame dev` never restarts a dead node on its own: if the node fails, the
loop says so, exits nonzero, and you rerun the one command after fixing
the cause.
## 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:6010 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