- switch root package manager to pnpm 10.33.4 via packageManager field - add .npmrc with node-linker=hoisted for RN/Expo compatibility - convert yarn resolutions to pnpm.overrides; pin psl@1.9.0 and @types/psl@1.1.1 to keep types resolving under exports-aware resolution - pin react-compiler-runtime and babel-plugin-react-compiler to real rc.3 (the deprecated "Wrong version name was published" tag would otherwise resolve) - add pnpm.onlyBuiltDependencies allowlist for @sentry/cli, core-js-pure, esbuild, unrs-resolver - regenerate pnpm-lock.yaml; delete yarn.lock; drop postinstall-postinstall - update all workflows, Dockerfile, Dockerfile.embedr (root-level), Makefile, conductor.json; bskyembed/bskylink/bskyogcard/dev-env stay on yarn - update docs (CLAUDE.md, docs/build.md, docs/testing.md, docs/localization.md, bskyweb/README.md, scripts/push-notification/README.md, BlueskyClip/README.md) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
push-notification
Sends sample APNS payloads to a booted iOS simulator via xcrun simctl push.
Useful for exercising useNotificationsHandler (src/lib/hooks/useNotificationHandler.ts)
without a real APNS round-trip — foreground display behavior, tap responses,
and notification-driven navigation.
What this does and doesn't cover
Covers — anything in useNotificationsHandler:
setNotificationHandlerforeground behavior (banner, list, badge, sound flags)addNotificationResponseReceivedListenertap handling- Account-switch flow when
recipientDiddiffers from the signed-in account - Navigation routing for each
reason(post threads, profiles, conversations)
Does not cover — BlueskyNSE (the iOS Notification Service Extension):
- Communication Notification styling for chat messages
- Badge increment via
mutateWithBadge - Custom
dm.aiffsound for chat messages
The simulator does not reliably invoke NSEs for simctl push on recent iOS
versions (verified bypassed on iOS 26.4). To test NSE behavior, run on a real
device with a real APNS push, or unit-test NotificationService.didReceive
directly in Xcode.
Setup
- Boot an iOS simulator and install the app:
pnpm ios - Sign in to the account you'll be testing against. The
recipientDidin each payload is substituted at send time and must match the signed-in DID, otherwise:- Chat notifications trigger the account-switch flow
- Other reasons are silently dropped by the handler
- Find your DID. Easiest: visit your profile in a web browser and copy it
from the URL, or grep dev logs for
currentAccount.
Usage
./send.sh <payload-name> [--did <did>] [--device <udid>] [--bundle <id>]
Pass the DID once via env var to avoid repeating it:
export BLUESKY_TEST_DID=did:plc:yourdidhere
./send.sh like
./send.sh chat-message
./send.sh follow
Defaults: --device booted, --bundle xyz.blueskyweb.app. Run ./send.sh --help
for the full list of available payloads.
Foreground vs background
useNotificationsHandler behaves differently depending on app state:
- Foreground —
setNotificationHandler.handleNotificationdecides whether to show a banner, play a sound, etc. For chat reasons, the banner is suppressed ifpayload.convoId === currentConvoId(you're already viewing that conversation). - Background or tapped —
addNotificationResponseReceivedListenerfires on tap and runs the navigation routing innotificationToURL.
To test the response listener, background the app first (cmd+shift+H in the
sim), send the push, then tap the banner.
Available payloads
| Payload | Reason | Navigation target |
|---|---|---|
like.apns |
like |
post thread (from subject) |
reply.apns |
reply |
post thread (from uri) |
follow.apns |
follow |
sender's profile (from uri.host) |
chat-message.apns |
chat-message |
MessagesConversation with convoId |
chat-reaction.apns |
chat-reaction |
MessagesConversation with convoId |
The subject AT URIs reference fake post rkeys, so the destination screens
will fail to load real content — that's expected. Routing exercises the
navigation path, not the data fetch.
Adding a new payload
- Copy an existing
.apnsfile inpayloads/whose shape matches. - Set
aps.mutable-content: 1and a realaps.alert.{title,body}. - Match the payload shape to
NotificationPayloadinsrc/lib/hooks/useNotificationHandler.tsfor thereasonyou're testing. - Use
__RECIPIENT_DID__as the placeholder for the recipient —send.shsubstitutes it at send time. Use it anywhere a DID needs to belong to the logged-in user (typicallyrecipientDid, and postsubjectfor likes/replies).