Skip to main content

11 posts tagged with "architecture"

View All Tags

Why We Chose Rust for Version 3

· 12 min read
Dave Rapin
Dave Rapin
Founder @ Curling IO
About this post

This is a technical architecture post from the development of Curling IO v3. It is written for software engineers and goes deeper into Gleam, Rust, SQLite, client-side UI architecture, and build tooling than our usual product posts.

For Curling IO v3, we knew we wanted to move away from the Ruby on Rails stack we'd used for v2. We wanted a statically typed language with stronger compile-time guarantees and fewer footguns. We first explored Gleam because it ticked those boxes and gave us access to the BEAM. Lustre offered a typed UI framework for both server and client. An initial implementation let us see how that combination would fit our architecture. Our early foundation posts describe what we were learning and building.

That work helped settle the product requirements, the changes we wanted from Version 2, and the UI design. It also exposed tradeoffs in deployment and client interactions that led us to ultimately choose Rust and TypeScript as the best fit for our architecture before going further with Gleam and Lustre. SQLite and the product design carried forward. We’ve since ported and expanded our SQL tooling and built a small TypeScript runtime called Hypertea for client interactions.

The move improved deployment, gave us useful compile-time automation, and suited the way we investigate and fix issues. Unfortunately, it also made our builds much heavier. There are things about Gleam and Lustre that we still prefer.

Human in the Loop with Contracts

· 14 min read
Dave Rapin
Dave Rapin
Founder @ Curling IO
About this post

This is a technical implementation note about the AI assistant architecture in Curling IO v3. It is written for software engineers and others designing agent systems, and goes deeper into Rust, persistence, authorization, and failure handling than our usual product posts.

The usual human-in-the-loop AI agent pattern goes something like this: the model requests a tool call, the agent runtime pauses, a human approves the call, and the runtime resumes so the tool can execute.

That is a reasonable general-purpose design. It is also stricter than simply letting an agent call every tool it can see. For Curling IO, we wanted to expose the smallest possible surface to the model and put an application-owned guardrail around every path to a write. That led us to a stricter question:

If the application already has the exact call details, why hand control back to the agent at all?

By the time we ask a club manager to approve an operation, Curling IO has parsed the model's request, resolved every default, checked the current application state, produced a fixed preview, and stored the exact arguments. The model has nothing useful left to contribute to execution, so we do not let it execute the operation or resume it merely to carry out the approval.

The agent proposes. The application turns that proposal into a contract. The human approves the contract. Rust executes it.

Optimizing Curling Draw Schedules

· 26 min read
Dave Rapin
Dave Rapin
Founder @ Curling IO

This post is part of our Curling IO v3 sneak peek series, where we explore some of the new features available in the upcoming version.

Curling IO v3 includes a new draw scheduling screen for event games. The schedule is a grid of draws and club resources, with unassigned games kept in a queue beside it. You can drag and drop games, lock specific placements, and use Allocate and Optimize around those locks.

Unlike a separate schedule template generator, this editor works with the event's actual teams, stages, games, resources, and draw times. Saving the schedule updates the event directly.

You can try most of the scheduling interface now at CurlingSchedules.com. It uses generic teams and browser-local saves instead of an event's actual games, but the grid, drag-and-drop editing, locks, catalog schedules, fairness inspection, and optimization are available today.

Live Admin With Gleam and Lustre

· 12 min read
Dave Rapin
Dave Rapin
Founder @ Curling IO
Update: October 1, 2026

This post reflects our earlier Gleam exploration for Curling IO v3. We ultimately chose Rust and TypeScript. Why We Chose Rust for Version 3 explains that decision and what we kept from the exploration.

About this post

This is a technical architecture post from the development of Curling IO v3. It is written for software engineers and goes deeper into Gleam, Lustre, the BEAM, WebSockets, and server-rendered interfaces than our usual product posts.

Curling IO's admin panel should feel instant when a club manager is working through a season setup. Toggle a setting, save a discount, move between product sections: the page should respond without a full reload.

Version 2 works, but every form submission reloads the page. Version 3's admin is a single Lustre server component running on the BEAM. One WebSocket connection, one long-lived Erlang process per session. Every interaction goes over that WebSocket and comes back as a DOM patch. The page never reloads, and there's no client-side JavaScript framework.

AI Agents Love Gleam

· 12 min read
Dave Rapin
Dave Rapin
Founder @ Curling IO
Update: October 1, 2026

This post reflects our earlier Gleam exploration for Curling IO v3. We ultimately chose Rust and TypeScript. Why We Chose Rust for Version 3 explains that decision and what we kept from the exploration.

Fair warning: this post contains some opinions that are going to be controversial and may not age well. Here be dragons.

AI coding agents like Claude Code, OpenAI Codex, and Google Gemini can write code, run it, read the errors, and try again. That loop is the whole game. The faster and more informative that loop is, the more useful the agent becomes. After building Curling IO Version 3 in Gleam alongside AI coding agents, I'm convinced Gleam is the best language for this workflow. Agents don't write better Gleam - there's less training data. But Gleam's compiler lets agents self-correct without waiting for a human.

Parallel Tests for Free

· 6 min read
Dave Rapin
Dave Rapin
Founder @ Curling IO
Update: October 1, 2026

This post reflects our earlier Gleam exploration for Curling IO v3. We ultimately chose Rust and TypeScript. Why We Chose Rust for Version 3 explains that decision and what we kept from the exploration.

About this post

This is a technical implementation note from the development of Curling IO v3. It is written for software engineers and goes deeper into Gleam, Erlang, EUnit, test isolation, and parallel execution than our usual product posts.

While writing the previous post about our per-test SQLite databases, I was describing how each test gets its own in-memory database, no shared connections, no shared state. And I thought: wait, if nothing is shared, can we just run them all at the same time?

Turns out we could, and our server test suite went from ~4 seconds to ~0.85 seconds for around 800 tests. Zero code changes to the tests themselves. One 25-line Erlang module.

Test Isolation for Free with SQLite

· 9 min read
Dave Rapin
Dave Rapin
Founder @ Curling IO
Update: October 1, 2026

This post reflects our earlier Gleam exploration for Curling IO v3. We ultimately chose Rust and TypeScript. Why We Chose Rust for Version 3 explains that decision and what we kept from the exploration.

About this post

This is a technical implementation note about the testing architecture for Curling IO v3. It is written for software engineers and goes deeper into SQLite, in-memory databases, the backup API, and test isolation than our usual product posts.

Curling IO's tests don't need a shared test database, cleanup hooks, or transaction tricks. Each test gets its own database, so a test can pass alone or in the full suite for the same reason: nothing else can touch its data.

That falls out of one Version 3 choice: SQLite runs in-process. Each test gets a completely independent in-memory SQLite database, cloned from a template in microseconds using SQLite's backup API.

Why We Chose SQLite

· 13 min read
Dave Rapin
Dave Rapin
Founder @ Curling IO
Update: October 1, 2026

This post reflects our earlier Gleam exploration for Curling IO v3. We ultimately chose Rust and TypeScript. Why We Chose Rust for Version 3 explains that decision and what we kept from the exploration.

About this post

This is a technical architecture note about the database decision for Curling IO v3. It is written for software engineers and operators, and goes deeper into SQLite, Litestream, database isolation, hosting, and recovery tradeoffs than our usual product posts.

Version 3 should be cheaper to operate, easier to restore, and still fast during peak registration and live scoring. That pushed us toward a choice we didn't expect: SQLite.

We assumed PostgreSQL at first. After a decade running Postgres in production, we knew the tooling, the failure modes, and the operational playbook. Then we compared self-hosting Postgres with Litestream-backed SQLite and changed our minds.

Background Jobs Without the Baggage

· 7 min read
Dave Rapin
Dave Rapin
Founder @ Curling IO
Update: October 1, 2026

This post reflects our earlier Gleam exploration for Curling IO v3. We ultimately chose Rust and TypeScript. Why We Chose Rust for Version 3 explains that decision and what we kept from the exploration.

About this post

This is a technical implementation note from the development of Curling IO v3. It is written for software engineers and goes deeper into Gleam, the BEAM, OTP, SQLite-backed queues, durability, and job execution than our usual product posts.

When a curler asks for a login email, the page should respond immediately. They shouldn't wait on Postmark, and we shouldn't run a separate worker fleet just to send a message in the background.

Curling IO Version 3 runs background jobs inside the same BEAM runtime as the web app. No Redis. No separate worker. No additional deployment. The queue is durable because pending jobs live in SQLite, not memory.

Bilingual by Design

· 8 min read
Dave Rapin
Dave Rapin
Founder @ Curling IO
Update: October 1, 2026

This post reflects our earlier Gleam exploration for Curling IO v3. We ultimately chose Rust and TypeScript. Why We Chose Rust for Version 3 explains that decision and what we kept from the exploration.

About this post

This is a technical architecture note about localization in Curling IO v3. It is written for software engineers and goes deeper into Fluent, compile-time translation checks, bilingual content modeling, and language routing than our usual product posts.

English and French support has to be boring. A club in Quebec shouldn't find a half-translated registration page, and an organization running national competitions shouldn't need a support ticket to catch a missing label.

Curling IO has been bilingual since Version 2, but after a decade of maintaining around 10,000 YAML translation keys, we've hit the limits of what that approach can catch: missing keys, missing translations, and unused keys that accumulate silently over time. Version 3 keeps the bilingual experience and makes more of those mistakes impossible to ship.