Astro · Cloudflare Workers · Supabase Postgres · live site
Runebinder does three jobs. It tracks a collection across two games — every card, every printing, and what the whole thing is worth as prices move. It turns that inventory into deck building, so I can see which cards are already spoken for and which decks are actually sleeveable tonight. And it follows every online order from checkout to the binder, so nothing gets quietly dropped somewhere between a ten-seller checkout and a pile of envelopes.
Getting cards in used to be the thing that killed it. Typing names off a stack of cardboard is exactly the chore that kills a collection database within a month — you skip it once, the data goes stale, and then it's wrong forever. Now I photograph the pile and Claude reads it, resolving names, counting duplicates, and matching printings before writing the rows. Orders work the same way: a Claude skill sweeps my email for confirmations and shipping notices and reconciles them against what's outstanding, so the collection knows what's on a truck somewhere without me telling it.
The question this page answers is "can I sleeve this deck tonight?" — which is harder than it sounds, because the same physical cards get spoken for by several decks at once. Availability is allocated in priority order, and decks marked as variants of each other are treated as alternatives rather than both reserving the pile. A deck can show as complete without me owning enough copies to build it and its sibling simultaneously, which is correct: I only ever sleeve one.
A nightly job pulls prices into a snapshot table, so the value line is real history rather than a number recomputed on each page load. The dashboard reads that history, splits the collection by domain, and surfaces the two things I actually act on: what's arriving, and how little it would cost to finish the current buy list.
Buying singles means one checkout explodes into a dozen seller orders that ship separately and arrive over three weeks. This page models that grain directly — one row per seller sub-order — and attributes each one back to the decks it was bought for. Cards in transit count as on-order, so the buy list doesn't tell me to re-purchase something already in a padded envelope.
The site itself is strictly read-only: every mutation runs through Claude, which holds the write credentials, and the web app only renders what's there. That split is deliberate — the agent is good at messy input, and a surface that can't write can't corrupt the collection when I'm tired and clicking fast.
Astro rendering per request in a Cloudflare Worker, with no client framework — interactivity is a handful of inline scripts. The browser reads Supabase Postgres directly over PostgREST with a publishable key, so there's no API layer of my own to maintain. That's only safe because the database was hardened for it: the anonymous role holds SELECT and nothing else, every readable table carries a select-only policy, views run as the invoker so they can't tunnel around those policies, and the audit tables aren't granted at all.
Ten tables hold facts and eight views derive everything else, which was a deliberate correction. The version of this that lived in Notion stored derived counts by hand, and they drifted — the collection would confidently report that a deck was complete when it wasn't. Facts in tables, everything computed in views, and the drift stops.