Ultimate

Tutorial 1 — your first app

One command scaffolds a monorepo that already runs. No Docker daemon, no database to provision, no .env to fill in.

As of 2026-08-23, with every gate row re-measured As of 2026-09-12. This page documents main, not a published release. Every file count, gate result and command output below was re-measured on this tree: x new → bun install → the command, in a directory outside the checkout. Rows that still describe a published version say so in their own line.

Series: 1 · 2 — first feature · 3 — auth and admin · 4 — jobs and realtime · 5 — deploy free · 6 — growing up

Prerequisites

NeedVersionCheck
Bun≥ 1.4.4bun --version
Dockeronly for tutorial 5docker --version
Postgresnever, locally—

Scaffold

cd $(mktemp -d)
bunx create-ultimate myapp

Unpinned on purpose. Pinning the tutorial to a version pins readers to a scaffold that stops matching this page on the next tag; README.md and llms.txt drop the pin for the same reason. To reproduce a specific release, add @<version> and read that release's tag of this page.

  213 files in /tmp/tmp.XXXXXXXX/myapp
✓ created myapp — next: cd myapp && bun run setup && bun run dev

As of 2026-11-03; --no-example writes 181.

Counts are a derived fact — x new --dry-run --json lists every file, and one added template moves the number.

cd myapp && bun run setup
setup complete — next: bun run dev

bun run setup is bun install, an .env.development.local touch, x db gen "initial", x db migrate, x db seed, then x manifest — so the database is migrated and x.manifest.json exists before the gate asks for either. x new installs nothing, so this is not optional.

x is a workspace dependency, not a global install. bunx x <command> resolves node_modules/.bin/x; the package.json scripts (bun run verify, bun run dev) resolve the same binary. A globally installed x (bun link, bun add -g) run inside the app hands over to that same file, As of 2026-09-05, and says so on stderr: a global copy carries its own @ultimat3/entity with an empty registry, and it wrote a zero-entity manifest under a green exit code before it did. ULTIMATE_KEEP_GLOBAL_CLI=1 is the opt-out — @ultimat3/cli's README has the measurement.

Two shapes, one generator

Counts are derived, never quoted here — x new --dry-run --json | jq '.data.files | length', per shape. Measured on fresh scaffolds of this tree, As of 2026-11-05:

Invocationbun run setup && bun run verify on run one
bunx create-ultimate myappgreen, 20 of 21 steps pass — budgets among them; roadmap skipped (a framework-repo step)
bunx create-ultimate myapp --no-examplegreen, 17 of 21 steps pass — live, job, e2e skipped (no such test yet) and roadmap

--no-example omits the post feature slice and its route. Pick it when an agent is about to write the real feature anyway — tutorial 2 starts there. Pick the default when you want a worked example to read.

The gate, first thing

bun run verify

The app's own gate is x verify, alone. Its budgets step weighs .x/build-stats.json, and on a fresh scaffold no such file exists yet, so the step runs x build --target static itself first (into .x/verify-static, never over .x/static).

The default scaffold, after bun run setup, with no .x/build-stats.json beforehand, As of 2026-11-03 (exercise measured 2026-11-05): the whole gate, in the order it runs:

  ✓ typecheck          3064ms
  ✓ lint               232ms
  ✓ boundaries         844ms
  ✓ filesize           906ms
  ✓ package-shape      402ms
  ✓ errors             951ms
  ✓ unit               2790ms  3 workers (budget 4.0 GB)
  ✓ contract           1233ms  3 workers (budget 4.0 GB)
  ✓ live               840ms  serial
  ✓ job                345ms  1 of 3 workers (budget 4.0 GB)
  ✓ exercise           1952ms
  ✓ e2e                12124ms  serial
  ✓ eval               91ms
  ✓ drift              1486ms
  ✓ contract-diff      78ms
  ✓ budgets            6336ms
  ✓ seo                40ms
  ✓ i18n               63ms
  ✓ policy             75ms
  ✓ manifest           1131ms
  - roadmap            0ms
✓ 20 of 21 steps passed in …ms — 1 skipped: roadmap

- is skipped, not passed: roadmap is a framework-repo step. The default scaffold ships its example slice with a live test, a job test and an e2e test, so live, job and e2e are ticks on run one; with --no-example there is no such test yet, and those three are dashes too. contract is not among them — the scaffold ships apps/web/api/health.contract.test.ts, which is what makes it a tick on run one. Nor is exercise: it needs no test file — it runs every action, query and job the app registers, once, against seeded rows — and both shapes write the apps/web/exercise.ts it reads. The summary names every skip, so a gate that is green because a suite does not exist says so on the one line you read. Tutorial 2 turns more of those dashes into ticks.

The list is VERIFY_STEP_NAMES (packages/cli/src/verify-step.ts), whole or not at all: the same steps run here and in the framework repo.

budgets builds before it weighs

budgets took 6.3 s above because it built first. Delete the stats and --verbose prints the line that says so; run it again and it reads the stats it just wrote:

rm .x/build-stats.json
bunx x verify --only budgets --verbose
bunx x verify --only budgets
  ✓ budgets            7320ms
      | built first: build stats were missing — x build --target static --out .x/verify-static (6968ms)
✓ NOT A GATE RUN — all 1 steps passed in …ms
  ✓ budgets            324ms
✓ NOT A GATE RUN — all 1 steps passed in …ms

It builds when .x/build-stats.json is missing, written by an earlier measurement rule, or older than the newest app source file (tests excluded), and never otherwise: run the gate again with nothing edited and the step reads the stats it has. After an edit, the gate pays one static build. A build that fails is the step's verdict, under the build's own code and fix:.

CI asserts the result: scaffold-smoke runs a fresh app's own scripts once, with no waiver and no fix-follow, and budgets must come back green rather than merely not-red — a skip would mean the static build bought nothing (scripts/scaffold-gate.ts).

The invariant block is typed from your columns

invariants is one callback over the whole list, and c is typed from the columns above it:

invariants: (c) => [
  invariant('post_title_not_blank', c.title.trimmed().minLength(1)),
  invariant('post_price_non_negative', c.price.minor.atLeast(0)),
],

c.titel is a compile error that names title, not a runtime surprise. Everything x new and x g write typechecks as generated — no !, no edit before the first x verify.

What still ships broken: Known gaps.

Run it

bunx x dev

--once boots, reports and exits — the shape to paste into a smoke test:

bunx x dev --once --port 3100
{"ts":"2026-11-03T16:48:57.124Z","level":"info","msg":"ultimate web listening on http://localhost:3100"}
{"ts":"2026-11-03T16:48:57.118Z","level":"info","msg":"sync node ready","buildId":"e6af64a2077e5d60","path":"/_x/sync"}
{"ts":"2026-11-03T16:48:57.134Z","level":"info","msg":"jobs.worker.started","workerId":"worker-01a1a250-…","queues":["default"]}
{"ts":"2026-11-03T16:48:57.137Z","level":"info","msg":"jobs.scheduler.started","tasks":1}
{"ts":"2026-11-03T16:48:57.142Z","level":"info","msg":"live feed in-process","detail":"the embedded database has no walsender; repository writes in this process reach subscribers"}
  roles web, sync, worker, scheduler
  panels routes, timeline, live, jobs, db, mail, cache, policy, manifest, services, boundaries
  manifest /tmp/…/myapp/x.manifest.json
  introspect http://localhost:3100/_x
  mcp POST /mcp
✓ dev ready on http://localhost:3100 — /_x mounted (11 panels), db=embedded events=embedded storage=embedded mail=embedded cdn=none live=in-process

An excerpt, As of 2026-11-03: the boot also logs the dev viewer warning, the metrics port and the drain --once ends with.

Four roles in one process, isolation simulated rather than skipped. replicator is opt-in via --role; migrate is not a dev role.

Why no Docker

.env.development ships every service key empty, and empty means embedded:

DATABASE_URL=
NATS_URL=
S3_ENDPOINT=
PORT=3000
ROLE=web
KeyUnset meansSet means
DATABASE_URLPGlite in this process, under .x/pgdatathat Postgres
NATS_URLin-process fanoutthat NATS server
S3_ENDPOINT.x/storage on diskthat S3

Nothing in the framework branches on if (dev) — only the driver behind the seam differs. Same code path, same db(), same enqueue(). Full table: CLI reference § x dev.

What each directory is

myapp/
├── app.config.ts            the one config file — names env KEYS, never values
├── apps/
│   ├── web/
│   │   ├── site/            static, 0kb JS, SEO-critical. May NOT import app/
│   │   ├── app/             authed, streaming, realtime. Feature slices live here
│   │   ├── api/             actions only — no rendering, no components
│   │   ├── shared/          actor type, tokens, primitives. A leaf both surfaces import
│   │   ├── server.ts        the production entry: ROLE + PORT, nothing else
│   │   └── prerender.ts     the static entry: one HTML file per `render: 'static'` route,
│   │                        plus sitemap.xml and robots.txt
│   ├── admin/               the admin app, gated on `admin:read`
│   ├── desktop/  mobile/    README stubs, no code
├── packages/
│   ├── db/                  schema.ts (the entity export list — the package's public
│   │                        surface, not the generator's input) + migrations/
│   ├── domain/              pure types, no I/O
│   ├── i18n/                catalogs/<locale>.json — one flat file per locale
│   ├── mcp/                 the app's own MCP surface over its actions
│   └── ui/                  app components on semantic tokens
├── docker/                  Dockerfile, .dockerignore, dev + prod compose, helm/
├── guards/                  one rule per file, run by `x verify`'s boundaries step.
│                            Every name in SHIPPED_GUARD_NAMES ships; `x doctor`
│                            lists any missing. Delete one by deleting its file
├── bin/                     setup.ts — `bun run setup`; `dev` and `verify` are x's own
└── CLAUDE.md  AGENTS.md     what an agent reads first: one file, AGENTS.md a symlink

Boundaries are a build error, not a note — but boundaries is a step of x verify, not a top-level command. bunx x boundaries exits X_CLI_UNKNOWN_COMMAND. Run bunx x verify and read the boundaries line. Full model: Project layout.

The database, first run

packages/db/migrations starts empty, and x db gen is its only writer. Two commands, in this order:

bunx x db gen "initial"    # entities → <id>.sql, <id>.snapshot.json, <id>.hash
bunx x db migrate          # applies them, then diffs the live schema against the ledger it wrote

bun run setup runs both — bun install, an .env.development.local touch, x db gen "initial" when the directory holds no .sql, x db migrate, x db seed, then x manifest. x db migrate is @ultimat3/db's own migrator, the one ROLE=migrate runs, so nothing extra has to be installed.

Until the generate has run, x verify is red on its drift step — correct behaviour with a runnable fix, not a defect:

X_DB_DRIFT: schema differs from migrations
  cause: packages/db has a schema but no migration recorded it
  fix:   x db gen "initial"

That is the scaffold with the example slice, which declares one entity. x new --no-example declares none, and zero entities against zero migrations is agreement rather than drift, so its drift step is green from the first run and goes red the moment you write your first entity().

x new hand-wrote a 0000_initial.sql until 2026-08 and that was the defect — a second writer of a directory the generator owns, whose file carried no .snapshot.json, so x db migrate answered X_DB_DRIFT naming x db gen and x db gen answered X_MIGRATION_SNAPSHOT_MISSING naming version control for a file version control never had. Neither fix: now names the command that raises the other (Known gaps).

The routes are clean

x routes on a fresh scaffold reports four file routes and no findings, plus the admin's screens. As of 2026-10, read off the templates x new writes:

pathsurfacerenderhydrateofflinedeclared by
/sitestaticneverprecacheapps/web/site/page.tsx
/offlinesitestaticneverprecacheapps/web/site/offline/page.tsx
/dashboardappssrvisibleruntimeapps/web/app/dashboard/page.tsx
/postsappssrvisibleruntimeapps/web/app/posts/page.tsx

The admin has no page file: apps/admin/app/admin/admin.ts declares defineAdmin(), which mounts /admin and a list, detail and form per entity. Those rows name the call and the permissions that gate them instead of a file — defineAdmin() · admin:read + posts:read — and their --json rows carry mount. --no-example drops /posts. x routes is not a gate step either way, so a finding here never fails x verify — run it after scaffolding and after every x g route.

A read that should not hold up the page goes in a <Hole> on a stream page (Routes and render modes).

The five commands worth memorising

WantCommand
is it shippablebun run verify (add --json)
what is wrong with my environmentbunx x doctor --json
what does this error code meanbunx x errors explain X_FORBIDDEN
what does this app containbunx x manifest --json
what commands existbunx x help — planned ones are marked (planned) and throw X_NOT_IMPLEMENTED

Next

Tutorial 2 — your first feature: one entity, one policy, one action, one route, one test, and the five artifacts a single declaration projects.

Edit this page on GitHubThis page as markdown