Getting notified when something finishes
The point of the whole product is the moment a job finishes while you are somewhere else. Progress Watch delivers that through Web Push, straight from the server to your browser — desktop and phone through one mechanism, with nothing in between that belongs to us.
Turning it on
Push is opt-in per space. Open a space, press Notify me, and that browser is registered for that space and no other. Nothing about you is stored until you press it, and forgetting the space unregisters it again.
On a phone, install the site first
iOS delivers Web Push only to a site added to the Home Screen, and there is no way around it: open the space in Safari, Share, Add to Home Screen, and press Notify me from there. Android and desktop work from an ordinary tab.
Installing has a second benefit on iOS. Safari clears an ordinary site's local storage after seven days without a visit, and an installed web app is exempt — and that storage is where your list of spaces lives. There is no account to restore it from.
A tree of steps is one notification
Every task that finishes notifies, including each step of a job. They do not stack: a step replaces its job's previous notification in place rather than adding to it, so a ten-step deploy reads as one line that keeps changing, and only the job's own completion buzzes again.
What each party learns
Payloads are encrypted for the browser that subscribed, with keys only it holds. Google, Mozilla and Apple relay ciphertext and cannot read your task titles. What they do learn is that a particular browser received something, and when.
Your own server learns one thing it otherwise would not: that a browser opted in on a given space. That is the reason it is a button per space rather than a global setting — the list of spaces a browser holds lives in that browser and nowhere else, and a subscription is the one thing that puts a space in the server's records.
If you are self-hosting, PUSH_CONTENT=minimal sends "Task completed" with no title. The payload is encrypted either way; this is for the lock screen of a phone somebody else can see.
A server at home
Notifications leave your server, so it can sit at home behind a router and still send them. What has to reach the server is your phone and your browser, to turn the bell on and to open what a notification points at, and for that it needs an HTTPS address with a real certificate. A browser offers no notifications on http://192.168.1.50:7979, and the iOS app connects to HTTPS servers only. localhost is the one exception, which is why it all works while you try it on the machine itself.
Without a domain of your own, Tailscale gives the server a public HTTPS address in a single command. Install it on the machine that runs Progress Watch, sign in, and run:
The first time, it asks you to allow Funnel for your network. Then it prints the address, https://your-machine.your-tailnet.ts.net, and that is your server from now on: in the app, in progresswatch configure --server and in the browser. The address is public, as progress.watch is, and a space is still read only by whoever holds its link.
Self-hosting: generating the keys
Both keys unset means the feature does not exist — no button, and nothing stored about anyone. Generate a pair once:
Put both into the environment: block in docker-compose.yml and docker compose up -d again. Not restart, which reuses the configuration the container already has.
There is nothing to register anywhere — no Firebase project, no Apple developer account, no API key. Whoever made the browser runs the push service; its address arrives inside the subscription the browser hands your server, and the VAPID pair is self-signed proof that every notification came from the same sender. VAPID_SUBJECT is only a contact address and nobody verifies it.
Do not rotate the keys. Existing subscriptions are bound to them, browsers are not told, and they simply stop receiving.
The iOS app
An iOS app is on its way to the App Store. It is this same site in a native shell, and it is notified through Apple's push service rather than Web Push, so there is nothing to add to a Home Screen. From a self-hosted server those notifications pass through progress.watch, because only the app's publisher holds the key Apple asks for: what passes is the device and the text of the notification, never a space UUID. PUSH_CONTENT=minimal keeps task titles out of it, and PUSH_RELAY=false turns it off altogether. What it does not do yet is watch several servers at once: it is pointed at one server at a time.