app spec: the answers from the app repo, and alpha caught up

three questions answered from monorepo-mobile, and two of my assumptions were
wrong.

bundle ids exist — dev.officer.mobile and dev.officer.mobile.dev. two of them
needs nothing special: push_devices is unique on (token, bundleId), so the
variants are separate rows on one phone, and the dev variant is exactly the
sandbox/production distinction the doc warned about. concrete now rather than
derived from build config by guesswork.

scheme is officer:// and officer-dev://, both from app.config.ts.

expo prebuild, not bare — and a config plugin that creates an ios app
extension target ALREADY EXISTS there, withVaultAutofillTarget.js for the
autofill extension. the nse follows it. that removes what i had called the
longest pole in phase 3.

corrected: expo-notifications is not in apps/mobile at all, only apps/chat.

the real blocker is narrower than the doc implied and is not code. today's
signed build has no aps-environment entitlement, and adding one fails while
xcode has no account signed in — same failure as the network extension on
2026-08-05. one manual sign-in, team RGHC4NJC77, then automatic again.

alpha: /api/notify was 404 all day because that server predated the work.
pulled, db:push, gen:ecosystem, officer-notify started, officer restarted.
verified 401 unauthenticated and push_devices with its 10 columns.
This commit is contained in:
2026-08-19 18:59:13 +00:00
parent e57d6cc1cc
commit 98960d2827
+44 -9
View File
@@ -15,6 +15,11 @@ the build order, and it does not repeat the reasoning.
`{"ok":true,"channels":[]}` and every send is a silent no-op that returns success. **A green response `{"ok":true,"channels":[]}` and every send is a silent no-op that returns success. **A green response
does not mean a push was sent** — check `channels` first, or you will debug the app for a day over a does not mean a push was sent** — check `channels` first, or you will debug the app for a day over a
server with no key. server with no key.
- **Alpha is up to date as of 2026-08-19 19:xx.** `/api/notify/*` answered `404` there for most of the day
because the server predated this work; it now answers `401` unauthenticated, `push_devices` has its 10
columns, and `officer-notify` is running. If you get a `404`, that server needs `git pull` +
`pm2 restart officer`, and `officer-notify` needs starting — it is a new core process, so an existing
install will not have it until `bun gen:ecosystem` runs.
## Which app ## Which app
@@ -31,7 +36,7 @@ work, nothing later will, and everything later is harder to debug.
### 1. iOS project setup ### 1. iOS project setup
- A bundle id for `apps/mobile` (only `dev.officer.music` was ever confirmed to exist — check). - A bundle id for `apps/mobile` — **confirmed: `dev.officer.mobile`**, plus `dev.officer.mobile.dev`.
- `aps-environment` entitlement. Do **not** copy chat's plugin here; the hub needs the opposite. - `aps-environment` entitlement. Do **not** copy chat's plugin here; the hub needs the opposite.
- Push capability on the App ID in the developer portal, and a provisioning profile that includes it. - Push capability on the App ID in the developer portal, and a provisioning profile that includes it.
@@ -143,14 +148,15 @@ longer installed and stop.
**One build, ever.** The extension is a generic resolver — it must never `switch` on known codes, or every **One build, ever.** The extension is a generic resolver — it must never `switch` on known codes, or every
new notification kind becomes an app store release, which is the entire thing this design exists to avoid. new notification kind becomes an app store release, which is the entire thing this design exists to avoid.
Note for Expo: a custom NSE needs a config plugin (`expo-notifications` alone does not give you one). Worth Expo: `ios/` is generated by prebuild, so the NSE target comes from a config plugin. **One already exists
scoping early — it may be the longest pole in Phase 3. in this repo** — `packages/bw-vault/plugin/withVaultAutofillTarget.js` creates the AutoFill extension
target. Follow it rather than inventing one.
## Phase 4 — deep linking ## Phase 4 — deep linking
`route` from the mapping is the destination. Tapping a notification opens the hub, which routes onward to `route` from the mapping is the destination. Tapping a notification opens the hub, which routes onward to
the target app or screen. Confirm the scheme with the owner — the platform uses `officer://<hex>` origins the target app or screen. The scheme is **`officer://`** (`officer-dev://` in the dev variant), from
for app surfaces, and I have not verified what `apps/mobile` registers today. `app.config.ts`.
## Android — later, but design for it now ## Android — later, but design for it now
@@ -177,8 +183,37 @@ Android build and is not yours to absorb.
- **`appSlug` means the deep-link destination**, not "which app receives this". The name is a leftover from - **`appSlug` means the deep-link destination**, not "which app receives this". The name is a leftover from
the first design, where each app had its own token. the first design, where each app had its own token.
## Open questions for the owner ## Answered, from the app repo (2026-08-19)
1. Does `apps/mobile` have a bundle id and a provisioning profile with push enabled? **Bundle ids exist.** `dev.officer.mobile`, and `dev.officer.mobile.dev` for the side-by-side dev variant.
2. What URL scheme does `apps/mobile` register for deep links? Two bundle ids is fine and needs nothing special: `push_devices` is unique on `(token, bundleId)`, so the
3. Is the hub in Expo managed or bare? It decides how the NSE gets built. two variants register as separate rows on the same phone. **Register the dev variant as
`environment: "sandbox"` and production as `"production"`** — that mapping is now concrete, so derive it
from the variant rather than the build config guesswork this document suggested earlier.
**The URL scheme is `officer://`, and `officer-dev://` in the dev variant.** Both come from
`app.config.ts`, and the share extension embeds the same string natively, so they cannot desync. So the
unknown-code fallback deep link is `officer://resync-notification-mapping` as written.
**Expo prebuild (CNG), not bare** — `ios/` is generated and gitignored; everything native comes from config
plugins. This is better news than the document assumed: **a config plugin that creates an iOS app-extension
target already exists in this repo**, `packages/bw-vault/plugin/withVaultAutofillTarget.js`, which builds
the AutoFill extension that prebuild cannot. **The NSE follows that pattern rather than being invented**,
which removes what this document called the longest pole in Phase 3.
**`expo-notifications` is NOT in `apps/mobile`** — only in `apps/chat`, for local turn alerts. Correcting
this document's assumption. Chat's `withoutPushEntitlement.js` still stays exactly as described above; the
hub needs the opposite.
## The one real blocker: the push entitlement
Today's signed build (`2026.08.19.02`) has **no `aps-environment` entitlement at all**. Nothing else in
Phase 1 can be tested until it does.
It has a known shape here. On 2026-08-05 adding a Network Extension failed for the same reason:
`-allowProvisioningUpdates` **cannot add a capability while Xcode has no account signed in**. The fix is a
one-time manual step by the owner — Xcode → Settings → Accounts, team `RGHC4NJC77` — after which it is
automatic again.
**Owner action, and it is the critical path.** Along with the APNs `.p8`, this is the whole of what stands
between here and a banner on a phone.