Skip to content
All work

Retail — thrift shop, Calgary · 2026

Scavenger

A shift board for a shop staffed by volunteers, where the database refuses to double-book.

A thrift shop was scheduling its floor staff through a signup page built for a single weekend. It became an open-ended calendar that keeps working month after month — with the opening hours as data rather than code, and the fairness rules the owner ran informally written into the software.

Engagement
Fixed scope, shipped
Users
Volunteers on their phones; one owner on /admin
Constraint
The owner is not technical and should never need to be
that keeps working every month
1 linkthat keeps working every month
double-bookings possible, by construction
0double-bookings possible, by construction
for every destructive admin action
2 clicksfor every destructive admin action

Hours are data, not code

The shop's opening hours change. A holiday, a late Thursday, a day cut into two shifts instead of one. If that lives in the source, every change is a deploy and a phone call to me — which is the same as saying it never changes.

So a week's hours are one row in the database, edited from a tab in the admin. A day is stored as a list of shifts rather than an opening span and a length, which means two shifts on a day can differ in length and a gap between them is simply a break the shop isn't staffed for. A fresh database needs no setup: there's a sensible default week that applies until somebody changes it.

The subtle part is what happens to shifts that already exist. Changing Monday's hours never touches a shift somebody already agreed to — bookings carry their own start and end. So an existing shift wins, and a day already covered end to end shows none of the new split. Saving therefore reports both the bookings that no longer line up and the shop-covered shifts sitting across the new hours, which is the answer to "I changed Monday and nothing happened."

Two people, one shift, one winner

Two volunteers tapping the same shift at the same moment is the bug that erodes trust in a scheduling tool faster than anything else. It isn't solved by checking whether the shift is free and then claiming it — there's a window between those two statements.

The shift id is the primary key and the claim is a single insert that does nothing on conflict and reports what it actually wrote. The database decides. The volunteer who lost is told which shifts were taken and keeps the ones that landed, rather than having the whole submission bounce.

Fairness, made explicit

The owner had an informal rule: the people who work the most get first pick of next month. The software now does it. The board can be released a person at a time down a running order — opening booking to that person and everyone above them, and texting only them. It waterfalls. It's enforced on the server, not greyed out in the interface. And it's opt-in: the default is that everyone sees everything at once.

Cancelling is treated as an undo rather than a policy. A volunteer can hand a shift straight back for a grace window after booking it — enforced in the same statement as the delete, against the database's own clock. After that, Cancel opens a dialog asking them to message the owner, and the shift stays theirs until she frees it. The software doesn't pretend to arbitrate something that's really a conversation between two people.

What the public can see

The calendar shows first names and nothing else — never a phone number — and the whole board is signed-in only, because who is minding a shop on which day isn't public information. Shift ids are re-validated on the server against the same schedule the interface drew, so a hand-crafted request can't book a closed day or a three-in-the-morning shift.

Next case study

Dynasty Advisory

There is probably one task in your week that shouldn't be yours.

Tell me what it is and roughly how often it happens. If it's a fit I'll say what I'd build and what it costs. If it isn't, I'll say that too.