2026 · Solo · Production
Sleeping Beauty
A lateness leaderboard whose rules are data: effective-dated rulesets, an append-only ledger, cosmetic consequences only.
Mission
A team daily call kept starting late. Sleeping Beauty turns that into a game: late joins earn points, Monday and Friday cost double, self-reporting halves it, and the board crowns the worst offender. Consequences are cosmetic and social only; no money, by decision.
For whom: one ~15-person team. Constraints: rules will be argued about and changed, history must never be rewritten when they are, and the attendance source could not be assumed.
Manifest
| Layer | Choice | Version | Why |
|---|---|---|---|
| app | Next.js + React | 19 | Public board, member app, and admin console from one app segmented by hostname. |
| services | NestJS | — | Ruleset evaluation and the ledger live in one service with a single write path. |
| data | PostgreSQL + Prisma + Kysely | — | Append-only ledger rows that keep the ruleset that produced them. |
| services | Better Auth (Google SSO) | — | The team already lives in Google Workspace. |
| services | Slack API | — | Where the roasts and the daily standings actually get read. |
| infra | Docker Swarm | — | Same deploy shape as everything else I run. |
Decisions
Rules are data. Effective-dated rulesets and an append-only point ledger that stores the ruleset that produced each event. Amending a rule changes the future, never the past.
Centipoint arithmetic, unit-tested. Half points and double days never produce a rounding argument.
One Next app, three faces. Public board, member app, and admin console segmented by hostname from one deployment.
Attendance behind an adapter. The board does not care whether a join came from a Meet report, a Slack reaction, or a self-report form.
AI roasts behind a human review queue. The model drafts, a person approves, then Slack posts.
Impact
- Team members
- ~15
- Commits
- 128
- Days to first version
- 4
- Money involved
- 0
124 commits in four days, running for a ~15-person team with Google SSO and Slack posting. The call starts closer to on time, which was the whole point.
What broke
The attendance source. The plan was to read join times straight from the Google Meet Reports API, and that API sits behind Workspace admin access I did not have on day one. The board was ready before its only honest data source was.
Because attendance was already behind an adapter, I shipped with the self-report adapter and a Slack reaction adapter instead, and kept the Meet adapter as a stub behind the same interface. When the access lands, the ledger does not change shape.
Ship log
- First commit
- Live for the team; 124 commits in four days
- Slack: each person is DM'd what the night paid them
- Deploys from GitHub with sealed secrets; a favicon at last
- Now: running for the team, Meet adapter still stubbed, 128 commits