About

How we built it

A running account of how the club's digital home grew — what we built, why we built it, and what we learned along the way.

How we built it

How we built it

A running account of how the club’s digital home grew — what we built, why we built it, and what we learned along the way.

19–20 April 2026 — Building the whole thing in a weekend

The timing software — LiveRC — kept the lap data from race nights, but there was no reliable path from a Saturday night at Cygnet Town Hall to a result that members could read on their phones the next morning. The club needed its own home on the internet — something fast to update, reliable enough to leave alone, and simple enough that one person could run the whole operation without a developer in the room.

So we started from scratch. In the space of about two days, the foundations came together: a Python command-line tool to pull events and results from LiveRC, a flat-file data layer in YAML and CSV, and a public website built on Astro and deployed automatically through Cloudflare Pages. None of this was assembled from a pre-made template. Each piece was designed specifically for how the club actually works — fortnightly races, a small regular field, one person (Chris) who should be able to run the whole digital operation without a developer in the room.

The data model took a deliberate shape. Race results live in CSV files, one per event date. Events are YAML. Standings are computed fresh from the results rather than being stored as a separate number that can drift out of sync. The site reads these files at build time and publishes them — it never writes anything. That clean separation meant we could trust the data without worrying about the website corrupting it.

One decision that happened immediately and has never wavered: driver privacy. LiveRC stores full names. We don’t publish them. Every name that goes into the public data is truncated to “First L.” — first name, last initial, full stop. The enforcement happens in code before any file is written, and the website’s data schema is explicitly designed so the full name field cannot accidentally slip through even if someone tries.

19–28 April 2026 — LiveRC integration, results pipeline, and the first real data

The LiveRC adapter was the first major technical piece: a web scraper that reads the club’s results from tassieminiz.liverc.com and converts them into the structured files the site understands. It handles the awkward parts of LiveRC’s HTML — timing cells that include superscript characters, class names that have heat and main suffixes that need to be stripped, driver identifiers that change depending on whether a transponder is present.

The results pipeline built on top of this. When Chris runs the ingest command after a race night, it takes the raw timing data, applies the privacy rule to every driver name, registers any new drivers in a persistent registry, and writes a results CSV. It also automatically drafts a race recap blog post with the results tables and the current championship standings — so Chris gets a preview of what the post will look like before anything goes live. He can edit the draft, add a personal note about the evening, then flip it from draft to published.

The standings engine was built as a separate computation pass rather than being stored directly. This matters because class names in LiveRC are messy — a single driver can appear under “Box Stock A1-Main”, “Box Stock A2-Main”, and “Box Stock Heat 1/1” across different sessions on the same night. The standings engine normalizes all of these to “Box Stock”, takes the best result per driver per event, applies the 10/8/6/5/4/3/2/1 points system, and produces a clean championship table. Early on we found a bug where a driver who ran two mains was getting double points — the two-pass approach (best result first, then aggregate) was the fix.

22 April 2026 — The club’s look and feel

The site’s first visual pass was functional but anonymous — it could have been any club’s website. The second pass was about making it specifically ours. Chris’s club logo — a round badge — became the favicon and the reference point for the colour palette: cream background, racing navy, deep blue accents, a measured use of red.

The display typeface became Big Shoulders Display: wide, condensed, confident — the kind of letterform you associate with motorsport timing boards. Body text and navigation use a clean system font stack so the site feels native on iOS and macOS. A Tasmania silhouette was added as the section divider — the kind of detail that makes a site feel like it belongs to a specific place rather than having been assembled from a generic template.

Dark mode was built in from the start. The club races indoors in the evenings and members check results on their phones on the way home. A site that assaults you with a white screen at 10pm is not a good experience. The theme toggle — sun or moon icon in the header — lets members pick, and the preference is remembered.

28–30 April 2026 — Security, structure, and a domain

Once the basic infrastructure was working, the focus shifted to getting it ready for real use. This meant three things happening roughly in parallel.

First, structure. The Python CLI had grown organically and needed a proper internal architecture before it got any more complicated. We split the single large file into one module per command group — events, results, standings, drivers, import. A shared base layer handles the file-writing operations that every command needs, using atomic writes throughout. An atomic write means the file is never in a half-written state: the new content goes to a temporary file first, and then the OS swaps it into place in a single operation that either fully succeeds or leaves the original intact.

Second, security. The serverless backend — a Cloudflare Worker that would eventually handle Stripe payments — needed to be hardened before it was exposed to the internet. An early review found that the email endpoint had no authentication at all, which would have allowed anyone to send email through the club’s domain. That got fixed immediately with a bearer token requirement. The Stripe checkout flow needed an allowlist of domains that payment redirects could go to, so a malicious link couldn’t redirect buyers to an arbitrary site after they’d paid.

Third, the domain. The club now has tassieminiz.com — registered through Cloudflare, with the DNS pointed at Cloudflare Pages, so every push to the main branch triggers an automatic rebuild and the live site updates automatically. The fallback at tassie-mini-z.pages.dev stays working in case anything goes wrong, but the canonical address is the proper one.

30 April 2026 — Content: twelve posts and operational documents

A website for a club is only as useful as its content. The infrastructure was in place but the information that new members actually need — what cars we race, how a race night runs, what “Box Stock” means, how to set up a Mini-Z front end — wasn’t there yet.

Twelve posts went into draft at once: a primer on the cars we race and the two classes, a glossary of Mini-Z terminology, setup guides for the front and rear ends, a track surfaces guide, a tyre compound guide, an about page, a getting started guide, a code of conduct, track etiquette, FAQ, and a piece on the broader Tasmanian RC scene. All of them went up as drafts for Chris to review before publishing, because several touch on club policy that Chris should sign off on before it goes out under the club’s name.

At the same time, a set of operational documents was drafted for internal use: a race day runsheet that Chris can print and follow, volunteer role descriptions, a member onboarding sequence, equipment inventory, photo and media release policy, an incident escalation policy, sponsor packages, and a twelve-month season calendar skeleton. These don’t appear on the public site but they’re committed to the repository so they’re versioned and can be handed to any committee member who needs them.

The editorial sweep that accompanied publication fixed a few things that had crept in: Mini-Z RWD is officially 1/27 scale, not 1:28, and several places had the wrong number. Claims about the club that were a bit promotional in tone were softened to something more honest. The sponsor tier order on the sponsors page was corrected to match the documented pricing structure.

2–5 May 2026 — A proper online shop and the first live product

The shop had existed as a set of pages since the beginning, but nothing in it was actually buyable. That changed when Chris set up a Stripe account and created a payment link for the Das Speed Wheel — a steering wheel upgrade for Kyosho transmitters that the club stocks. The product page on the site now linked directly to Stripe, customers could pay, and money could go into the club’s account.

A Cloudflare D1 database was provisioned — D1 is a serverless SQL database that runs alongside the Worker. When a customer completes a checkout through Stripe, Stripe sends a webhook notification to the Worker, which writes the registration to D1. This creates a clean record of every purchase that Chris can query and export. A reg sync command was added to the CLI that pulls registrations from D1 and writes them to CSV files in the data directory — so Chris has a local archive of who bought what, when.

Inventory tracking was also wired in at this stage. The D1 database keeps a count of stock on hand for each product. The checkout flow checks available stock before creating a Stripe session, and the webhook decrements the count when a payment completes. There is a timing gap — two people could both pass the stock check and both complete payment before either has decremented the count — but for a club selling a few items a month, this is an acceptable tradeoff. The alternative would require database locking that the Cloudflare platform doesn’t support.

The RC28R team kit was added as a product during this period, with proper photo galleries that members can swipe through. Product pages got category labels and a clean breadcrumb navigation so the shop didn’t feel like a dead end.

7–8 May 2026 — Shop polish and the EasyLap transponder

The shop got a focused polish pass: the product gallery on each card gained swipe support for mobile, the image handling was cleaned up so photos displayed at their natural aspect ratio rather than being cropped, and the product detail page layout was simplified to remove a duplicated call-to-action that had appeared in the wrong place. The Silver Horse Colt-25 electronics kit was also added as a product at this point.

The EasyLap Micro Transponder — a timing transponder listed in the shop — got its live Stripe payment link the day after the product was first added, once the checkout infrastructure had been validated. Connecting it took a few minutes. It means a new member can now arrive at a race night with a car, look up the site on their phone, and buy a transponder right there if they need one.

12–20 May 2026 — Stripe buy buttons and the Das Speed Wheel goes live

The shop gained a second checkout path alongside the Worker-backed flow: Stripe’s own buy button, which embeds directly on the page and handles the checkout experience inside the site rather than redirecting away. The Das Speed Wheel became the first product to use this.

The Das Speed Wheel was the only product published at this point — the rest of the catalog (RC28R, Silver Horse electronics) was kept in draft for further review. Dependencies were updated at the same time.

1–3 June 2026 — The shopping cart

The shop had worked on a per-product basis: you found an item, you clicked buy, you went through Stripe for that single item. If you wanted to buy multiple things — a transponder and a spare body, say — you had to complete two separate checkouts. That limitation was addressed in the biggest single feature addition since launch: a proper multi-product shopping cart.

The cart drawer slides in from the right side of the screen. An icon in the header shows how many items are in the bag. Product cards have an Add to Cart button. On the product detail page there’s a quantity selector. When you’re ready to checkout, the cart sends the full list of items to the Worker, which validates stock for each one and creates a single Stripe Embedded Checkout session — the payment form appears on the page rather than redirecting to Stripe’s hosted page.

The cart contents persist in the browser’s local storage, so if you navigate away and come back your basket is still there. When a checkout completes successfully, the cart clears automatically. If you abandon checkout partway through, a banner on the shop page lets you know you can pick up where you left off.

The Worker was also updated to handle cart-format webhooks from Stripe, which have a different structure from single-product purchases. The D1 inventory update loops through each item in the cart rather than handling a single product. All of this needed its own suite of automated tests — the Worker now has tests for valid cart payloads, malformed inputs, out-of-stock conditions, and what happens when Stripe returns an unexpected response.

3–4 June 2026 — QA rounds and making things solid

Once the shopping cart was working, parallel QA reviews ran across the full codebase — one reviewer looking at the Python CLI, one at the Worker, one at the site, one at the overall data pipeline. The pattern is to run several independent reviews and compare findings, on the theory that different reviewers notice different things.

Several small but real problems came up. The standings file was being written in a format that included Python-specific data markers — it would open fine but was technically a non-standard YAML file that other tools might reject. That was fixed to use plain standard YAML. When the ops tool imports events from RCClubs (the event management platform), it was writing empty placeholder values for the registration link and calendar link fields, which caused a validation error if you tried to load the events file straight afterwards. The fix was to use the club’s main website URL as the placeholder instead of an empty string.

The LiveRC race result page — which shows a live lap chart during a race meet — had a potential issue where if LiveRC’s servers were slow to respond, the Worker would sit waiting for the response body even after it had decided to give up. The fix wraps the entire fetch-and-read sequence in a timeout that covers both the initial connection and reading the response, returning a clean error message rather than letting the connection hang indefinitely.

A few edge cases in the driver name processing were also addressed: names with Unicode characters in the last initial (covering non-ASCII surnames), single-character first names, and a quoting issue in the Worker’s race data parsing that would have caused the lap-by-lap position chart to display a literal backslash if a driver’s display name contained a single quote.

A migration script was also added for moving the entire infrastructure — Worker, database, site configuration, secrets — to a new Cloudflare account if that ever becomes necessary. It doesn’t affect members at all but it means the club isn’t locked in to any particular account, and the steps for moving are documented and tested rather than being something that would have to be figured out under pressure.

Where things stand now

As of early June 2026, the club’s digital infrastructure is fully operational. Members can look up upcoming events, check results from past races, follow the championship standings, and buy products from the shop. Chris can publish events to the club calendar, import results from a race night, trigger a recap post, and send email announcements to subscribers — all from the command line, and all with a dry-run preview before anything goes live.

The site has around 65 pages, the test suite has nearly 400 automated tests across the Python tools, the Worker, and the site, and the whole thing rebuilds and deploys automatically through Cloudflare Pages whenever a change is pushed.

What we’ve learned along the way is that the right architecture for a small club’s digital infrastructure is one that keeps the human in control of every significant decision. The tools make the work fast; Chris makes the calls. Race results don’t publish themselves — they wait in draft for a review. Payments go through Stripe, which is designed for this. The data lives in flat files that any text editor can open. That combination of automation where automation helps and human judgment where it matters has been the guiding principle from the first commit.

← All posts