intertwingly

It’s just data

Choose Your Own Adventure

Yesterday's per-resource specialization PR took 24 minutes of commit time. The handoff document estimating it called for 3-5 days. The asymmetry between decision time and implementation time is the experience this post is about.

Borrowed from Prisma

An architectural decision Roundhouse just made, forced by Spinel and validated by Prisma: specialize the closed axis, defer the open one. The Rails-blog real-blog → Spinel error count dropped from 22 to 6 in one PR. The principle is older than this work — but it generalizes cleanly to a multi-target transpiler.

Two Compilers, One Cadence

Four days ago I argued the Spinel subset was the same intermediate language Roundhouse needed. The bet then was that the maturity gaps would close on both sides. Four days in: Matz called Roundhouse "a great forcing function," seven issues have been filed, six are closed, and the channel runs at roughly one fix per day.

Untyped, on Purpose

Most type systems force a single bet about where to set the dial between programmer annotations and compiler inference. Today Roundhouse declined to set it. Ty::Untyped landed as a first-class type — the IR's load-bearing primitive for deferring per-target, per-code-category, per-stage strictness decisions until there's evidence about what to decide.

Round Trip

Yesterday I closed with the bet that "within a few iterations the round trip works end to end." Iteration one closed faster than that rhetoric implied. The demo is two make commands and a working blog on localhost:3000 — Rails source on the way in, transpiled Spinel-subset Ruby on the way out, real-time Turbo broadcasts and SQLite persistence in between.

Two Compilers, One Subset

An update on Two Compilers, One Moment. Two days ago I described overlapping inputs and non-overlapping value. The work since suggests the overlap is sharper than that — the IR roundhouse needs internally is, structurally, the Spinel subset. Discovered, not designed.

Two Compilers, One Moment

Two compilers, started a month apart. The first by Ruby's creator. The second by someone with no knowledge of the first. The interesting question isn't which one is right — it's what changed in the surrounding landscape that made now the moment.

Static Types for Dynamic Targets

Ruby's + means addition between integers, concatenation between strings, concatenation between arrays, and a runtime raise between an integer and a string. To produce correct output for one operator in six target languages, a compiler has to know the operand types at each site — in Rust where types are mandatory, and equally in Elixir, Python, and TypeScript where they're not. Roundhouse's bet rides on that observation. Today two pieces of load-bearing infrastructure landed: RBS ingestion on both sides of the compiler, and a diagnostic pipeline that surfaces what the typed IR can't resolve.

Rails Was Already Typed

Rails apps come pre-typed. Schema declares column types, conventions thread them across associations and controllers, and standard inference fills in the rest. None of it requires annotations — the information is already in the source. Roundhouse reads it, types every expression, and emits to seven targets. As of today, the TypeScript and Rust targets produce the same DOM as Rails on the standard blog scaffold.

Introducing Roundhouse

Roundhouse reads Rails applications and produces standalone projects in other target languages — the deployment target becomes a compiler flag rather than a runtime choice. It started two days ago. Here's what it is, what it can do today, and why the Rails subset it targets is larger than it sounds.

Nine Days

The internet says Claude Code is nerfed. That has not been my experience. A Rails blog app, transpiled to six compiled languages with real-time WebSocket broadcasting, downloadable binaries, 126+ tests. Nine days.

Rust on Rails

Railcar can now transpile a Rails application to Rust. The same blog app generates a working Axum server with rusqlite, Turbo Streams — 21 tests passing, zero compiler warnings, the sixth target language.

Go on Rails

Railcar can now transpile a Rails application to Go. The same blog app generates a working net/http server with view functions, modernc.org/sqlite, and Turbo Streams — 21 tests passing, the fifth target language.

Elixir on Rails

Railcar can now transpile a Rails application to Elixir. The same blog app generates a working Plug + Bandit server with EEx templates, Exqlite, and Turbo Streams — 21 tests passing, the first functional language target.

TypeScript on Rails

Railcar can now transpile a Rails application to TypeScript. The same blog app generates a working Express server with EJS templates, better-sqlite3, Turbo Streams — 21 tests passing, the third target in a single day.

Python on Rails

Railcar can now transpile a Rails application to Python. The same blog app that compiles to Crystal also generates a working Python web application with aiohttp, SQLite, Tailwind CSS, and Turbo Streams — 21 tests passing, full CRUD with nested resources.

Crystal on Rails

Compiling arbitrary Ruby is hard. Generating type signatures for arbitrary Ruby is hard. But Rails isn't arbitrary Ruby — it's a DSL with known semantics. Railcar exploits that to do three things: transpile Rails apps to Crystal, provide a Rails-compatible Crystal framework, and generate RBS type signatures.

Rails on the BEAM

Same blog. Same Turbo Streams. But now a runtime crash doesn't crash the server, broadcasts span nodes without Redis, and you have a path to Phoenix.

Calling JavaScript from Ruby

Ruby can call C. Ruby can call Python. But calling JavaScript has always meant eval strings or manual bundling. Boax embeds the Boa JS engine in Ruby via Rust, letting you call JS libraries with the same proxy-object pattern Ruby developers already know.

The Transpiler That Reads Your Whole App

Most transpilers work file by file. But Rails encodes meaning across files — controller names imply view paths, associations imply async behavior, group_by in a controller implies Map operations in a view. Juntos reads the whole application and carries that knowledge forward.