Taylor Addison

tvOS · Web · Pre-release · active this week

Dozehound

Calm TV for the dog you had to leave at home.

Dozehound is calm television for dogs left alone. Open it on the Apple TV and it is already playing; press one button on your way out and a screened channel runs gently and continuously until you are back. No ads, no sudden cuts, no “are you still watching?” — the whole product is one promise a buyer can check in five minutes. It is the first thing I have built for the screen in the living room rather than the one in a pocket, and almost all of the engineering is about what happens over the following eight hours with nobody watching.

Screenshots coming soon — Dozehound is pre-release.

Features

What it does

Instant on

The app opens playing. No browsing, no profiles, no loading screen between the dog and the calm.

Leaving Home

One press on the way out lands on the free channel without navigating anything. It is the entire interaction the product exists for.

A promise you can check

No ads, no sudden cuts, nothing that jumps. Every hour is watched by a person before it goes on the air.

Daily rotation

Clients deal the same per-day shuffle from a shared seed, so two TVs in one house agree, and a crash resumes the order it was already on.

Link a TV with a code

Six characters on the screen, typed once on a phone. No password ever gets entered with a remote.

Free forever

The Naptime channel needs no account and no card, and that is not a trial.

Architecture

How it's built

tvOSSwift 6 + SwiftUI on tvOS 26, XcodeGen-generated project, zero package dependencies — a two-item AVQueuePlayer kept fed by a playback engine that heals its own stalls, an auto-hiding channel bar as the entire interface, and a Top Shelf extension that deep-links straight into a channel
BackendSelf-hosted Supabase on owned hardware behind a Cloudflare tunnel — Postgres 17, GoTrue, PostgREST and Kong, plus an edge-function runtime. Row-level security is deny-by-default and every table's negative case is tested explicitly, including the one that would let an account grant itself a subscription
MediaAn ffmpeg ingest pipeline turns a source file into a two-rung 60 fps HLS ladder with real motion interpolation; an nginx origin serves immutable directories and Cloudflare's edge cache carries the load. Provenance for every clip — licence or source, and who screened it — travels with the file
WebTwo Cloudflare Workers. The marketing apex is static HTML under a default-src 'none' policy that fetches nothing from anywhere, so it renders offline and leaks no referrer; the signed-in app is React 19 behind a sealed HttpOnly session cookie, with the API key held server-side and never shipped to the browser
Signing in on a TVNobody types a password with a remote. The set shows six characters, the human claims them on a phone, and the TV trades a one-shot secret for a real session through an edge function — a flow that crosses client, database function, edge runtime and row-level security in one round trip
BillingStripe hosted checkout from the web app; the webhook running next to the database is the only thing in the system that can write an entitlement. Store purchases wait on a trademark clearance, because product identifiers are permanent once they appear on a receipt
TestingPer-table row-level-security suites, interface tests driven by an actual TV remote, a live end-to-end test that reads the link code off the screen, a 27-check smoke suite against production every day — and an eight-hour unattended playback soak that gates every release tag
Other living-room platformsRoku (BrightScript/SceneGraph) and Fire TV (Kotlin, Compose, Media3) clients are written against the same backend and share a generated rotation contract so every device in a house agrees on the day's schedule. Neither has run on real hardware yet, and the repos say so on their first screen

Reliability

Built to be left running

Every other app I have built is judged in the thirty seconds someone is looking at it. This one is judged over eight hours in an empty house, where the failure that matters is not a crash the user sees but a frozen frame nobody is there to notice. That inverts what the engineering is for, and it is the part of this project worth reading.

A player that heals itself

Playback runs through a two-item queue that is always one clip ahead, with a watchdog sampling progress. One dead interval gets a nudge; two consecutive ones get the queue rebuilt from scratch. A failed item is skipped rather than retried into the same wall, because the same wall is still there.

The release gate is eight hours of nothing happening

No tag ships until the app has played unattended for eight hours on a simulator. The run fails on process death, on memory crossing a ceiling, or on playback stalling — but deliberately tolerates a single bad sample, because the engine's own recovery needs about forty seconds and a gate that fails on a blip that heals is a gate people learn to rerun until it passes.

One schedule, agreed without a server

There is no scheduling service. Each client deals a deterministic per-day shuffle from the same seed, and the vectors are generated from the reference client and read by every other client's test suite. It is a cross-platform contract enforced by tests, because no database constraint can make two devices in one house agree about what plays next.

Tests that wait past the auto-hide

Two focus bugs reached a build because every interface test acted the moment a screen appeared. The channel bar hides itself after a few seconds, so a suite that never waited had never once pressed a button on the screen a household actually sits in front of. Every remote test now waits past the hide first.

The invisible view that killed the remote

Hiding the channel bar by making it transparent left nothing on screen that could hold focus — and with nothing focusable, tvOS stops delivering directional input entirely. The bar could never come back, so the remote was dead six seconds after launch, in a build whose tests were green. The fix is a focusable catcher that stays behind the hidden bar; the lesson is that a platform's input model can have a floor you only find by sitting still.

One test that crosses everything

An opt-in end-to-end test drives the real remote against the real backend, reads the six-character link code off the screen, claims it, and checks that a subscriber-only channel becomes playable. It is the only test that touches client, database function, edge runtime and row-level security in a single run, and it found the dead remote on its first execution.

The product itself is deliberately modest: no camera, no social features, no claim that a TV channel treats separation anxiety, because nothing on the market has ever been shown to. What it promises is narrow enough to verify, which is the only kind of promise worth engineering this hard.