chadtmiller.com/knotless
git that lives in your Atmosphere account
The iOS app gets push notifications, sent by knotless-serve
MergedThis adds registerPush and unregisterPush, a push_device table, and a 30-second round in knotless-serve. The round reuses the notification page code, so an account's choices, blocks, mutes and private-repository access still apply. It sends through APNs with an ES256 token that is cached for 50 minutes. The iOS app registers after sign-in, unregisters on sign-out, routes taps, and clears the badge when the inbox is read.
I read the Rust, lexicons, deploy and Swift changes and found nothing that must change. Specifically:
- Token validation (hex only, at most 200 digits) keeps the token out of the APNs URL.
- A device keeps its watermark when it re-registers, and resets it when a different account takes the token.
- Notifications about private repositories are sent as a fixed string, with no title, text or author.
- A failed send leaves the watermark where it was, so the device is retried next round.
I did not read the integration tests (tests/gateway/push.rs, serve.rs) or the docs hunks. When I looked, check, test, lexicons, ios, shots and e2e had not finished, so the 100% coverage gate and the query-plan check are still unknown.
Two small things you could take or leave:
- A single
Failedsend for a device drops the rest of its messages for that round. That is fine for retry, but a persistently failing device is retried every 30 seconds, which logs a warning each time. - A round sends to devices one at a time with a 15 s timeout each. A slow Apple response would delay every other account's notifications, so this is worth watching as the device count grows.
Nothing has changed in the push code since my last review. Since then the author made a one-line fix in tests/gateway/push.rs (the final timeout is now awaited inside block_on(async { … }), so it runs inside the runtime), and merged main. That fix is the likely cause of the earlier test failure at 3808eb4. That check is still running on this head (check is running, test is queued), so I haven't seen it pass. Merge only once test is green, including the coverage gate. The two small notes from my last round still stand: a failing device is retried and logged every 30 seconds, and sends go to devices one at a time.
The only change since my last review is in index.rs. The row-mapping closure in push_devices is now a named function, push_device, so no unrun line counts against the 100% coverage gate. The query and the mapping are the same, so behaviour doesn't change. This is the likely cause of the earlier test failure, though I haven't seen the log. I found nothing new to ask for.
The earlier test failure at 1fa68f6 ran for 11 minutes and then failed. On this head, check is running and test, ios, e2e, docs, shots and lexicons are queued, so I haven't seen any of them pass. Merge only once test is green, including the coverage gate.
My two earlier notes still stand. A failing device is retried and logged every 30 seconds. Sends go to devices one at a time, each with a 15 s timeout.
9 passed, 1 skipped
What changes
The iOS app gets push notifications. Once signed in it asks permission and registers the device with the appview. Tapping a notification opens its thread, repository or author, as the inbox's row does. The badge is the unread count, and clears when the inbox is read.
knotless-serve sends them.
dev.knotless.registerPushandunregisterPushkeep a device per token. With--apns-key,--apns-key-idand--apns-team, a round every 30 seconds tells each device what is new, through Apple's push service. More than three at once become one notification saying how many. A device Apple says is gone is forgotten; other failures are tried again next round.Deploy.
deploy.shpasses the key only when$KNOTLESS_DATA/apns.p8is on the host, so this lands with push off until the key is put there.Changeset: none
Why
The app's inbox only showed anything when opened. Notifications stay worked out on demand: a round asks the same code
listNotificationsdoes, so an account's choices, blocks, mutes and private repository access all hold without a second set of rules. The only new state is a device and how far it has been told.Notifications about a private repository say only that there is new activity, because what is sent passes through Apple.
How it was checked
tests/gateway/push.rsregisters devices and runs rounds against a stand-in for Apple: a follow, an issue and a batch of issues reach a device, the signed token verifies, gone tokens are dropped, a failure is retried and nothing is told twice. Unit tests cover what is due and each notification's wording. The app builds for the simulator. Nothing has been delivered by Apple yet: that needs the key on a host and a signed-in device.Shortcomings
A notification that reaches the index late, dated before one already told, is not pushed, though the inbox shows it.
Each round reads every account with a device, as an inbox request does, so the cost grows with those accounts. That is fine at today's size and worth measuring as it grows.
The body carries a comment's first line as written, Markdown included.
🤖 Generated with Claude Code