INTRO
Stickr is the online half of a sticker chart on the wall. Both parents install the PWA; the kid never opens it. When a kid does something good, a parent taps once from the home screen — a sticker drops onto a shared board with hand-placed-looking jitter, and the other parent's phone reflects it within a couple seconds. Hit a threshold and you redeem a reward: the board archives as a 'chapter', confetti fires, any surplus stickers carry over, and a fresh board begins. Stickers only ever go up — pure positive, no punishment mechanic.
The toy framing hides the actual problem. The interesting constraints are correctness constraints: two phones can't disagree (stale state means a sticker logged twice), an award has to survive a dead elevator connection, and a redemption must fire exactly once even if both parents tap it in the same instant. ~7.3k lines of app source, 18 migrations across 10 row-level-security-protected tables, 157 unit tests plus Playwright journeys — and it went from empty repo to CI-gated, CSP-hardened, production-audited deploy in under 72 hours.
SECTION
The board is the product — and the board is deterministic
A chore-chart app's first instinct is to store each sticker's x/y in the database. Stickr doesn't. Placement is a pure function: an `xmur3` hash of the sticker event's UUID seeds a `mulberry32` PRNG, which produces the jitter — ±8px horizontal, ±6px vertical, ±28° rotation — that makes a packed board read like someone placed each sticker by hand instead of snapping it to a grid.
Because it's seeded on the event's own ID, the same sticker lands in the same spot on every device, across every refresh, forever. But positions are recomputed on render, not trusted from storage, so the board reflows to the viewport — four stickers per row on a phone, two dozen on a wide screen — while staying stable. (The award still snapshots a position into the row for audit; the render just doesn't depend on it.) That property is exactly what makes it testable: same seed, same board, every time, which is why placement has real unit tests instead of an eyeball check.
Sticker board near a reward threshold, ~20+ jittered custom stickers
/work/stickr/board-packed.png1600 × 900 PX
SECTION
Two phones, one truth
Sync isn't a feature here, it's a correctness requirement — the spec states it plainly: stale state causes double-logging. So the board subscribes to two Postgres changefeeds per kid: one on `sticker_event` inserts and deletes, one on the `kid` row itself, so a redemption on either phone reflows the other. The callbacks read state through refs, so resizes and every-sticker re-renders never tear down and rebuild the realtime subscriptions.
Awarding is optimistic: the sticker drops and the phone buzzes instantly, then it persists. If the write fails, the code branches on `navigator.onLine` — offline, the award queues to `localStorage` with a 'queued, will sync' toast; online, it rolls back and offers a Retry. Client-generated UUIDs mean the realtime echo and any later refetch dedupe cleanly against the optimistic copy. The offline queue flushes once, at the Home level, rather than per-board — a deliberate call, because multiple mounted boards racing a shared queue is a bug waiting to happen.
Offline toast over the board reading stickers queued, will sync
/work/stickr/offline-queued.png1170 × 2532 PX
Kid-facing board view with large name, live count, springy stickers
/work/stickr/kid-board.png1170 × 2532 PX
SECTION
Money-loop discipline, applied to stickers
Anything that mutates shared truth — kids, chapters, redemptions — is never written directly by the client. It goes through `SECURITY DEFINER` Postgres functions with a hardened `search_path`. The marquee one is `redeem_chapter()`: it authorizes the caller, closes the current chapter, opens a fresh one, carries any stickers earned beyond the threshold onto the new board, and resets the balance — atomically, in one transaction.
The race is two parents tapping redeem on the same chapter at the same moment. The guard isn't application logic, it's a storage constraint: a `UNIQUE (chapter_id)` on the redemption table. The loser blocks on the key, its whole transaction rolls back, and the RPC catches the `unique_violation` and re-raises a clean 'chapter already redeemed'. Ten tables sit behind row-level security scoped to a household via a `current_household_id()` helper — the same posture you'd want on a payments table, applied to gold stars, because the failure mode (a reward granted twice) is the same shape.
Redemption sheet mid-claim with confetti and screen flash firing
/work/stickr/redemption.png1170 × 2532 PX
SECTION
Shipping to two real iPhones is harder than it looks
Custom stickers come from a photo. The background gets removed in-browser — a ~10MB ONNX/WASM model, lazy-imported only on first capture, pinned to the quantized `isnet_quint8` model on the CPU backend specifically because iOS Safari's per-tab WASM memory ceiling and missing WebGPU make the default model fail outright. When it can't run, it degrades to the uncut photo instead of hard-failing. The result is downscaled to a 256px WebP, given a die-cut border, and stored.
Except Safari can't encode WebP via `canvas.toBlob` — it silently emits PNG — so the storage mime allowlist needed a migration just to admit `image/png`. Push notifications fan out through a Deno edge function that fires on the `sticker_event` insert, notifies only the other parent, prunes dead subscriptions on 404/410, and logs every outcome so a silent push outage is distinguishable from success. Account deletion is its own edge function for Apple's 5.1.1(v) rule, deleting the auth user last so a failed step leaves the account safe to retry rather than orphaned. None of this shows up in a demo. All of it is the difference between 'works on my machine' and an app installed on both phones.
Sticker library showing a photo turned into a die-cut background-removed sticker
/work/stickr/sticker-library.png1600 × 900 PX
OUTCOME
Live at stickr.brac.dev, installed on two phones and running a real household. Empty repo to production-audited deploy in under 72 hours: 157 unit tests, Playwright journeys, a cross-tenant RLS suite, ~87% coverage, a full CSP with the WASM allowances the background remover needs, and a readiness audit that came back with no launch-blocking defects. Small surface, real engineering — the failure modes were never about stickers.
EXHIBITS
Captures
CAPTURE / 01 OF 03
Chore manager screen defining chores with assigned art and sticker values
/work/stickr/gallery-01.png1170 × 2532 PX
CAPTURE / 02 OF 03
Reward manager screen with threshold tiers
/work/stickr/gallery-02.png1170 × 2532 PX
CAPTURE / 03 OF 03
Household settings showing co-parents and the join code
/work/stickr/gallery-03.png1170 × 2532 PX