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.