Roundhouse

User guide · Targets

Thirteen names are accepted by --target. Ten are server targets that emit a complete HTTP + Action Cable application; the rest are variations on those. This page says what each needs and how far each is tested — which is the honest measure of how much to trust it.

The server targets

TargetEmitsPrerequisites to build and run
rustCargo crate (axum, rusqlite)Rust 1.85+, SQLite library
goGo moduleGo 1.24+
typescriptNode packageNode.js 24+
crystalshardCrystal 1.10+, SQLite library
elixirMix projectElixir 1.15+
kotlinGradle build (JVM)JDK 17+, Gradle 8+
swiftSwift packageSwift 6+; on Linux libsqlite3-dev
pythonPython project (uv)Python 3.11+, uv
csharp.NET solution.NET SDK 10+
rubyRuby tree — the framework runtime in Ruby, no RailsRuby 3.4+, bundler, SQLite; Node for the asset build

All ten serve on :3000, speak Action Cable at /cable, use SQLite at storage/development.sqlite3, and are seeded by sqlite3 storage/development.sqlite3 < db/seed.sql. Each ships the app's model and controller tests and a Playwright e2e/ suite; the sqlite3 CLI and Node.js 24+ are needed for the latter.

The variations

TargetWhat it is
jrubyThe ruby emit with prebuilt assets and JRuby run/test commands. JRuby 10+ (JDK 21+).
spinelThe ruby shape packaged as a spin project for ahead-of-time compilation to a native binary. Needs the Spinel compiler; spinel.md.
typescript-workerThe typescript emit bundled to run in a browser SharedWorker, with SQLite compiled to WebAssembly, for the in-browser demos. Node.js 24+ to bundle; no server.

How far each is tested

The lanes below run across targets in scheduled full validation, or when requested with ci:full. Ordinary PRs use a Ruby floor plus targeted additions for the files they change. After Full goes red, maintainers can narrow fix rounds with focus labels (ci:swift, ci:extras, …) — see CI coverage. Pushes to canonical main run that Ruby floor plus the full Spinel suite; extra-language SDKs wait for the three-hour schedule. Maintainers can request full PR coverage with ci:full. The lanes use the blog fixture (fixtures/real-blog: articles, comments, nested routes, validations, Turbo Streams over Action Cable, Tailwind) unless another app is named. A target's row in RELEASES.md records where these stood at the snapshot.

LaneWhat it provesTargets
compareThe emitted server renders the same DOM as live Rails, node for node, on every page of the fixture; JSON endpoints value for value. verifying.md.rust, go, typescript, crystal, elixir, kotlin, swift, python, csharp, ruby, jruby, spinel
smokeThe emitted README's own commands — build, setup, test, e2e in a real browser — run verbatim from the published archive.rust, go, typescript, crystal, elixir, kotlin, swift, python, csharp, ruby, jruby, spinel
framework testsThe framework runtime's own test suite (runtime/ruby/test/) passes when transpiled to the target — the runtime is itself transpiled, so this is the target's Active Record, Action Controller and Action View under test.crystal, kotlin, swift, typescript, ruby, spinel; rust on a subset
toolchainUnit tests emitted for a smaller fixture (tiny-blog) compile and pass — the absent-feature shape the full blog can't exercise.crystal, elixir, python, typescript, csharp, spinel (go, rust, kotlin, swift are covered by smoke instead and keep their harness for the dev loop)
CampfireBasecamp's Campfire — attachments, rich text, web push, bots, search, boosts, multiple rooms and users — compared against live Rails page by page, its own test suite run against the transpile, and its Action Cable broadcasts checked frame for frame.ruby, spinel

The last row is the important asymmetry. The nine compiled and JVM targets are proven on the blog; the Ruby shape — on CRuby, and compiled by Spinel — is proven on Campfire, a real product with a real surface, and is the lane the framework runtime is developed against first. When a Rails feature lands in roundhouse it lands there, and reaches the other targets as their emitters and runtimes catch up. rails-coverage.md is the feature-level view of that gap.

The conformance bar

The conformance bar is a passing compare result against the fixture, not the assumption that every target was checked on an ordinary PR. A page that renders one node differently — an attribute missing, a whitespace text node — is red. That is deliberately a higher bar than "the tests pass": it means the emitted app is the same app, as a browser or a scraper would see it, not a reimplementation that happens to agree on the tested paths.

The cost of the bar is that a target can drop off it. A target whose compare lane goes red on main and stays red is not on the list for the next snapshot, and its row in RELEASES.md says so.

This page is docs/guide/targets.md in the repository; edits are welcome there.