Commit c091e39943

c091e399437be5536da5e5047265b1491c24da0b

parent: c08f54fe1f

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-21 01:38 UTC

docs: correct the spec on identity and the privacy label

The .id is on the TabView inside ContentView, not on ContentView
itself, so ContentView's own state survives an account switch. Both
the spec and PushRouter's doc comment said otherwise, and that claim
was the stated reason for hoisting the router — the hoist is right,
the reason was not.

The device token is not declared under Identifiers. Records the
decision and its reasoning so a later pass does not read it as an
outstanding to-do.

Layout: unified · split

docs/superpowers/specs/2026-09-20-push-notifications-design.md +16 −5
@@ -105,9 +105,14 @@ call rather than being duplicated; a repository root or an unknown kind
105yields no route, and such a tap opens the app without navigating. 105yields no route, and such a tap opens the app without navigating.
106 106
107`PushRouter` is owned by `gitbayApp` beside `SessionStore`. That 107`PushRouter` is owned by `gitbayApp` beside `SessionStore`. That
108placement is the design: `ContentView` is `.id(account.id)`, so 108placement is the design: the `TabView` inside `ContentView` carries
109anything held inside it is destroyed by the account switch a 109`.id(account.id)`, so a cross-account tap tears that subtree down and
110cross-account tap performs. 110rebuilds it. A target held inside would go with it; held above, the
111rebuilt tree drains it.
112
113Note the `.id` is on the `TabView`, not on `ContentView` itself —
114`ContentView`'s own `@State` survives an account switch, which is why
115the Dashboard path has to be reset explicitly rather than by identity.
111 116
112## Registration 117## Registration
113 118
@@ -225,8 +230,14 @@ an APNs token, and it does so itself.
225- The **Push Notifications** capability on `org.gitbay.gitbay`, already 230- The **Push Notifications** capability on `org.gitbay.gitbay`, already
226 enabled in the developer portal, needs its matching entitlement in 231 enabled in the developer portal, needs its matching entitlement in
227 the Xcode project. 232 the Xcode project.
228- The **privacy nutrition label** gains the device token under 233- The **privacy nutrition label** does not gain the device token. It is
229 Identifiers: not linked to the user, not used for tracking. 234 per-app, it rotates, it is not the advertising identifier, and it is
235 not used for tracking under any reading Apple's rules care about —
236 nothing is shared with a data broker and nothing is linked to
237 third-party data. Decided, not skipped; do not add it on a later pass
238 without raising it again. Note the token *is* stored against the
239 account (`push_devices.user_id`), so if it were ever declared it would
240 be "linked to the user", not unlinked.
230- An App Store resubmission. If Apple asks what push is for, it is 241- An App Store resubmission. If Apple asks what push is for, it is
231 activity on repositories the account follows. 242 activity on repositories the account follows.
232 243
gitbay/Push/PushRouter.swift +10 −4
@@ -2,10 +2,16 @@ import Observation
2 2
3/// Carries a tapped notification's destination into the view tree. 3/// Carries a tapped notification's destination into the view tree.
4/// 4///
5/// Owned by `gitbayApp`, above `ContentView` — which is `.id(account.id)` 5/// Owned by `gitbayApp`, above `ContentView`. The `TabView` inside
6/// and is therefore destroyed and rebuilt by the account switch a 6/// `ContentView` carries `.id(account.id)`, so the account switch a
7/// cross-account tap performs. A target held inside that subtree would 7/// cross-account tap performs tears that subtree down and rebuilds it.
8/// go with it; held here, the rebuilt tree drains it on appear. 8/// A target held inside would go with it; held here, the rebuilt tree
9/// drains it.
10///
11/// The `.id` is on the `TabView`, not on `ContentView` itself, so
12/// `ContentView`'s own `@State` survives the switch — which is why the
13/// Dashboard path is reset explicitly in the drain rather than by
14/// identity.
9@Observable 15@Observable
10@MainActor 16@MainActor
11final class PushRouter { 17final class PushRouter {