What it is
My dad needs to reach me and be certain he reached me, without learning anything new. He gets one button on a plain web page — no app, no link with a key in it, no QR. My phone rings at full volume even in silent mode or Do Not Disturb, shows a full-screen alarm, and his page tells him whether I stopped it. Shipping it meant fixing four real failures: a second device stealing the ring, an ‘online’ flag that stayed online, a press queued for three minutes that fired out of nowhere, and a line that would not turn on over mobile data because the app dialled an IPv6 address that never connected.
Version history
Straight from the project’s own card data — what each version added.
Prove the phone can be made to scream: foreground service + full-screen intent + DND access, tested in an emulator with total silence set (zen=2). Log line that decided it: dnd filter was 3, lifted, then alarm volume 0 -> 7 / max 7.
Make Dad's side one button and no key. The key is injected server-side so the address he opens is the bare domain, and the relay refuses rather than queues so nothing rings later out of nowhere.
Survive being closed: stopWithTask=false, START_STICKY, a persisted JobScheduler watchdog and a boot receiver. kill -9 respawned the process in 3 s but not always the service, which is why the watchdog exists rather than a claim.
Fix what the tests exposed: per-install client ids so no device can steal a ring, stage=off for instant offline, IPv4 pinned before any socket opens so mobile data arms the line, and shownAt retained across pause so one ring makes one alarm.
Files on the site
What broke, and what I’d change
Not written yet. This is the part only I can write — what actually went wrong, and what I’d do differently. It lives in builds/reflections/dadfinder.md (see _TEMPLATE.md); as soon as that file has words in it, this section fills in on the next build.
Everything else on this page — the spec, the version history, the file counts — is generated from the project’s own build notes and files, so nothing here is guesswork.
Build spec
The project’s own build notes, rendered. Open the raw .md ↗
Show the spec
Build spec — DadFinder emergency line
The ask
Isaac's dad needs to reach him and be sure he was reached. Dad must do nothing new: no app, no link, no QR, no access key. Isaac's phone rings at full volume even in silent mode / DND, and Dad sees confirmation rather than guessing.
Shape
- One Cloudflare Worker (
dadfinder.isaac1804.workers.dev) + one Durable Object hub holding the ring - One native Android app on Isaac's phone (no Gradle: shell + javac + aapt2 + apksigner) holding a
- The website is the only surface Dad touches. The app is installed once, by Isaac.
state, plus the dad page served from the same worker.
foreground service that long-polls the hub and a full-screen-intent alarm activity.
Contract (asserted in the simulator's self-test)
- One ring id is offered to every connected phone, not consumed by the first poller.
- No phone listening →
409 refused:offline; never queue a ring that would fire minutes later. - Turning the line off posts
stage=offand the hub reports offline immediately. - Rate limit: one press per 20 s per IP (
429 too soon). - The phone acknowledges
stage=startwhen the alarm starts andstage=stopwhen it is closed. - DND is lifted and the alarm stream is raised to max before the siren starts.
Failure modes found and fixed
- Ring stolen by a second device. The hub marked a ring
consumedon first delivery, so a test - "Online" was sticky. Offline only appeared after a 90 s poll timeout, and any device kept it warm.
- Queued surprise ring. A press with no phone listening was parked for 3 minutes and fired when the
- Mobile data could not arm the line. The app dialled the IPv6 address of the worker and died there
- Double alarm. One ring produced two alarm screens;
RingActivity.onPause()re-posted the
emulator swallowed the ring meant for Isaac's phone. Fixed with per-install client ids and a deliveredTo list.
Fixed with an explicit stage=off ack.
line came back. Now refused outright.
(ConnectException … 2606:4700:…:443); carriers hand out an IPv6 route that does not work. Fixed by pinning IPv4 before any socket opens.
full-screen intent. Fixed by retaining shownAt across pause/resume.
Verified
- Emulator: DND total silence (
zen=2) →dnd filter was 3, lifted,alarm volume 0 -> 7 / max 7, - Killed process (
kill -9): START_STICKY respawn in 3 s; a persisted JobScheduler watchdog restores the - Two pollers, one ring: both receive the same id.
- Simulator: 9/9 checks, zero network requests, no console errors, no horizontal overflow at 390/768/1440.
siren playing, ack stop -> 200.
service within 15 min when it does not.
Not built
- No iOS app. No SMS fallback. The watchdog cannot beat a force-stop; the card says so.