docs: update notification test guides and remove smoke evidence templates

This commit is contained in:
toki 2026-06-02 18:51:58 +09:00
parent 4373528151
commit e49fb45dcb
11 changed files with 152 additions and 1435 deletions

View file

@ -74,35 +74,28 @@ Use the plugin-owned
[`Manual Smoke Checklist`](../../packages/messaging_flutter/README.md#manual-smoke-checklist)
for FCM delivery, ACK, inline reply, dismiss, and routing evidence.
For iOS preflight and smoke preparation, keep real Firebase and Apple files out
of git. Copy `ios/Runner/GoogleService-Info.example.plist` to
`ios/Runner/GoogleService-Info.plist` locally, replace placeholder values, and
make sure the real plist is added to the Runner target resources in Xcode.
Use `ios/Runner/Runner.example.entitlements` as the minimal Push Notifications
entitlement reference, then configure the real Runner target through Xcode.
Local routing fixtures live under `ios/Fixtures/*.apns`; replace
`REPLACE_WITH_IOS_BUNDLE_ID` before simulator injection.
See [`../../docs/ios-notification-test-guide.md`](../../docs/ios-notification-test-guide.md)
and [`../../docs/ios-notification-smoke-evidence-template.md`](../../docs/ios-notification-smoke-evidence-template.md).
For iOS notification preparation, keep real Firebase and Apple files out of
git. The user must provide the Apple Team/signing path, Bundle ID, APNs
credential choice, Firebase iOS app, local `GoogleService-Info.plist`, physical
device path, and smoke send path before an agent can complete a real APNs/FCM
smoke. Use `ios/Runner/GoogleService-Info.example.plist` and
`ios/Runner/Runner.example.entitlements` only as placeholder references. See
[`../../docs/ios-notification-test-guide.md`](../../docs/ios-notification-test-guide.md).
For macOS preflight and smoke preparation, keep real signing, provisioning,
For macOS notification preparation, keep real signing, provisioning,
notarization, Firebase, APNs, device token, and private host values out of git.
The macOS host scaffold keeps placeholder entitlements only; configure real
signing and notification capability locally when a smoke run requires it. Local
routing fixtures live under `macos/Fixtures/*.json`. See
[`../../docs/macos-notification-test-guide.md`](../../docs/macos-notification-test-guide.md)
and
[`../../docs/macos-notification-smoke-evidence-template.md`](../../docs/macos-notification-smoke-evidence-template.md).
The user must provide the macOS runner, signing mode, Bundle ID if needed,
APNs/Firebase scope, UI prompt responsibility, and evidence destination before
an agent can complete visible notification smoke. See
[`../../docs/macos-notification-test-guide.md`](../../docs/macos-notification-test-guide.md).
For Windows preflight and smoke preparation, keep real Microsoft account,
package identity, signing, push credentials, device token, and private host
values out of git. This repository does not currently include a Windows host
scaffold; add one only through a focused implementation plan that also registers
the plugin platform. Local routing fixtures live under
`windows/Fixtures/*.json`. See
[`../../docs/windows-notification-test-guide.md`](../../docs/windows-notification-test-guide.md)
and
[`../../docs/windows-notification-smoke-evidence-template.md`](../../docs/windows-notification-smoke-evidence-template.md).
For Windows notification preparation, keep real Microsoft account, package
identity, signing, push credentials, device token, and private host values out
of git. The user must provide the Windows runner, packaging mode, notification
API path, package identity/signing details if needed, UI prompt responsibility,
and evidence destination before an agent can complete visible notification
smoke. See
[`../../docs/windows-notification-test-guide.md`](../../docs/windows-notification-test-guide.md).
## Verification

View file

@ -14,12 +14,11 @@ Module-specific instructions stay with the module that owns them.
- Mattermost mobile app upstream snapshot baseline: `../apps/mattermost/UPSTREAM.md`
- Mattermost push-proxy upstream snapshot baseline: `../services/push-proxy/UPSTREAM.md`
- Runtime Image Validation details: `runtime-image-validation.md`
- iOS notification smoke prerequisites and runbook: `ios-notification-test-guide.md`
- iOS notification smoke evidence template: `ios-notification-smoke-evidence-template.md`
- macOS notification smoke prerequisites and runbook: `macos-notification-test-guide.md`
- macOS notification smoke evidence template: `macos-notification-smoke-evidence-template.md`
- Windows notification smoke prerequisites and runbook: `windows-notification-test-guide.md`
- Windows notification smoke evidence template: `windows-notification-smoke-evidence-template.md`
- Notification access preparation:
`ios-notification-test-guide.md`,
`macos-notification-test-guide.md`,
`windows-notification-test-guide.md`,
`linux-notification-test-guide.md`
## Upstream Following Policy

View file

@ -1,96 +0,0 @@
# iOS Notification Smoke Evidence Template
Use this template when collecting iOS notification smoke evidence. Replace every
private value with a placeholder before storing output in tracked files.
## Run Metadata
- Date:
- Operator:
- Device model:
- iOS version:
- Xcode version:
- Flutter version:
- Build mode: Debug | Profile | Release
- Bundle ID: `<redacted-or-placeholder>`
- Firebase project/app alias: `<redacted-or-placeholder>`
- APNs auth path: key | certificate | Firebase-managed | direct APNs console
## Preflight Inputs
- Local `GoogleService-Info.plist` present in `apps/flutter-test/ios/Runner/`: yes | no
- Example plist matched: yes | no
- Entitlements include `aps-environment`: yes | no
- Push Notifications capability enabled: yes | no
- Background Modes > Remote notifications enabled: yes | no | not tested
- iOS token contract used: `apple_rn-v2:<redacted-apns-token>`
- FCM smoke token used only for Firebase direct send: yes | no | not tested
## Permission And Token Evidence
Paste sanitized logs only.
```text
permission_status=<authorized|denied|provisional|not_determined>
apns_registration=<success|failure> reason=<redacted-or-placeholder>
apns_token_present=<yes|no>
fcm_token_present=<yes|no|not_used>
formatted_server_token_prefix=apple_rn-v2
```
## Payload Evidence
Payload file or sanitized JSON:
```json
{
"type": "message",
"server_url": "https://example.invalid",
"server_id": "server-smoke",
"channel_id": "channel-smoke",
"root_id": "",
"is_crt_enabled": "false",
"ack_id": "ack-smoke",
"post_id": "post-smoke"
}
```
Send path:
- Firebase direct FCM:
- Apple Push Notification Console:
- nexo push-proxy/server:
- Local simulator fixture:
Response ID or log reference:
```text
<redacted-or-placeholder>
```
## Foreground / Background / Open Routing
| Scenario | Expected | Actual | Evidence path |
|---|---|---|---|
| Foreground receipt | Dart/native event logged | | |
| Background display | System notification shown | | |
| Channel tap | `onNavigateToChannel` with server/channel | | |
| Thread tap | `onNavigateToThread` with server/root | | |
| Terminated tap | Launch payload forwarded | | |
## ACK, Dismiss, Inline Reply Boundary
| Feature | Expected for this run | Actual | Follow-up |
|---|---|---|---|
| ACK | implemented | not tested | pending gap |
| Dismiss | implemented | not tested | pending gap |
| Inline reply | implemented | not tested | pending gap |
## Sanitization Checklist
- [ ] Full APNs device token removed.
- [ ] Full FCM registration token removed.
- [ ] Apple Team ID, Key ID, certificates, provisioning profile details removed.
- [ ] Firebase API key, app ID, sender ID, service account details removed.
- [ ] Private server URL, auth token, signing key, username, channel names removed.
- [ ] Screenshots or recordings contain no private message/user data.

View file

@ -1,564 +1,44 @@
# iOS Notification Test Guide
# iOS Notification Credential Preparation
Last verified: 2026-06-01
Last verified: 2026-06-02
This guide lists what is needed to run iOS notification smoke tests for
`packages/messaging_flutter` through `apps/flutter-test`. It separates external
Apple/Firebase setup, local host-app setup, current implementation gaps, and
repeatable evidence to collect.
Read this when you need to provide Apple/Firebase access so an agent can finish
iOS notification implementation and smoke verification. You do not need to run
the test from this document.
Do not commit real Apple, APNs, Firebase, service-account, device-token, auth
token, private endpoint, Team ID, or provisioning values into tracked docs.
Use placeholders in examples and keep credential files in the approved local or
secret-store location.
## Provide
## Current Repository State
- The iOS plugin now registers the minimum debug/action bridge.
`packages/messaging_flutter/ios/Classes/NexoMessagingPlugin.swift` registers
the legacy `nexo_messaging` method channel (for `getPlatformVersion`
compatibility), the
`com.tokilabs.nexo.messaging/notification_actions` method channel, and the
`com.tokilabs.nexo.messaging/notifications` event channel.
- The iOS bridge implements `debugSendNativeEvent`, `saveDeviceToken`,
`getDeviceToken`, `setAuthToken`, `clearAuthToken`, `setSigningKey`, and
`getPlatformVersion`. `saveDeviceToken`/`getDeviceToken` persist only the
formatted token string via `UserDefaults`; `setAuthToken`, `clearAuthToken`,
and `setSigningKey` are validation-only no-ops that do not persist auth tokens
or signing secrets on iOS yet.
- This is a debug/local-routing bridge only. APNs registration, Firebase token
refresh handoff, notification center delegate, ACK, dismiss, and inline reply
are not implemented and remain future smoke/implementation work.
- The Dart plugin expects native notification and action channels:
`com.tokilabs.nexo.messaging/notifications` and
`com.tokilabs.nexo.messaging/notification_actions`.
- `apps/flutter-test/lib/main.dart` already calls `Firebase.initializeApp()`
before starting the app.
- `apps/flutter-test/ios/Runner/AppDelegate.swift` does not currently register
for remote notifications or install a `UNUserNotificationCenter` delegate.
- `apps/flutter-test/ios/Runner/Info.plist` has no notification-specific keys.
- `GoogleService-Info.plist` is intentionally ignored by `.gitignore`.
The first meaningful iOS smoke target is therefore:
1. Build and install the signed test app on a physical iOS device.
2. Initialize Firebase successfully with a local `GoogleService-Info.plist`.
3. Request notification permission.
4. Obtain an APNs token and an FCM registration token.
5. Deliver one visible FCM notification through APNs.
6. Map a notification open into the Dart `NotificationOpenedEvent` contract.
The minimum iOS debug/action bridge (channel registration plus the method
contract above) now exists, so Level 0 contract tests and local injection can
exercise the Dart routing path. APNs registration, FCM token handoff, ACK,
dismiss, inline reply, and native notification persistence are still follow-up
implementation work.
## Decisions Needed Before Testing
Record the answers in a private run note or task evidence, not in tracked docs.
| Decision | Why it matters |
| Needed | What to prepare |
|---|---|
| Apple Developer Team | Determines signing identity, App ID ownership, and provisioning profile. |
| Bundle ID | Must match Xcode, Apple App ID, Firebase iOS app, and APNs topic. |
| Provisioning flow | Automatic signing is easiest for smoke; manual profiles are better for controlled evidence. |
| APNs auth style | Prefer token key `.p8` for Firebase/FCM; certificate `.p12` is a fallback if the project requires it. |
| Firebase project/app | Existing project vs new smoke-only project affects data isolation and credential access. |
| Firebase config storage | This repo ignores `GoogleService-Info.plist`; decide who provides it and where local copies live. |
| Physical device | FCM via APNs requires a real iOS device for meaningful delivery testing. |
| Evidence path | Decide where screenshots, logs, payloads, and run notes are stored. |
| CI boundary | Keep real APNs/FCM delivery manual unless a signed device farm is explicitly available. |
## Apple Setup Checklist
### Account And App ID
- Active Apple Developer Program membership.
- Access to the target Apple Developer Team.
- Bundle ID reserved for the iOS smoke app.
- App ID with Push Notifications enabled.
- Registered physical device if manual provisioning is used.
The Bundle ID must be consistent everywhere:
- Xcode `PRODUCT_BUNDLE_IDENTIFIER`.
- Apple Developer App ID.
- Firebase iOS app bundle ID.
- APNs `apns-topic` when sending directly to APNs.
### Xcode Capabilities
Open `apps/flutter-test/ios/Runner.xcworkspace` in Xcode and configure the
`Runner` target.
- `Signing & Capabilities > Team`: selected smoke Team.
- `Signing & Capabilities > Bundle Identifier`: selected smoke Bundle ID.
- `Signing & Capabilities > Push Notifications`: enabled.
- `Signing & Capabilities > Background Modes`: enable `Remote notifications`
if the smoke includes background/silent delivery or background FCM callbacks.
Expected local file outcome:
- A Runner entitlements file exists or Xcode build settings point to one.
- The signed app includes `aps-environment` as `development` for debug smoke.
- Provisioning profile includes the App ID and push entitlement.
Useful verification commands after building:
```sh
codesign -d --entitlements :- build/ios/iphoneos/Runner.app
```
Look for:
```xml
<key>aps-environment</key>
<string>development</string>
```
### APNs Auth Key Or Certificate
Preferred path for Firebase/FCM:
- Create or reuse an APNs Authentication Key in Apple Developer.
- Download the `.p8` file once.
- Record the Key ID privately.
- Record the Team ID privately.
- Upload the `.p8`, Key ID, and Team ID to Firebase Cloud Messaging settings
for the matching iOS app.
Fallback path:
- Create an APNs provider certificate for the App ID.
- Export it as `.p12` if Firebase or a provider server requires certificate
auth.
- Track expiry and renewal ownership.
Do not confuse these with iOS code-signing certificates. Code signing lets the
app install and carry the push entitlement. APNs provider credentials let a
server, Firebase, or Apple Push Notification Console send pushes.
## Firebase Setup Checklist
### Project And iOS App
- Firebase project exists.
- iOS app is registered with the exact smoke Bundle ID.
- `GoogleService-Info.plist` is downloaded for that iOS app.
- The plist is copied locally to `apps/flutter-test/ios/Runner/GoogleService-Info.plist`.
- The plist is added to the Xcode `Runner` target resources.
- The plist remains untracked because `.gitignore` ignores
`**/GoogleService-Info.plist`.
The test app already calls:
```dart
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
```
If Firebase initialization fails on iOS, check that the plist is present in the
Runner target and that the bundle ID in the plist matches the signed app.
### Cloud Messaging Settings
In Firebase Console:
- Open Project Settings.
- Open Cloud Messaging.
- Find the matching iOS app configuration.
- Upload APNs auth key `.p8`, or configure APNs certificates if that is the
selected path.
- Confirm Firebase Cloud Messaging API is enabled for HTTP v1 sends.
For server-side sends:
- Use Firebase Admin SDK, or
- Use FCM HTTP v1 with a service account or Application Default Credentials.
Do not place service-account JSON in the repo.
## Native Bridge Work Needed For A Full Smoke
The minimum debug/action bridge is in place, but the iOS native side still
cannot complete a full APNs/FCM delivery smoke. Already done:
- Register the expected EventChannel:
`com.tokilabs.nexo.messaging/notifications`.
- Register the expected MethodChannel:
`com.tokilabs.nexo.messaging/notification_actions`.
- Implement `saveDeviceToken`, `getDeviceToken`, `setAuthToken`,
`clearAuthToken`, `setSigningKey`, and `debugSendNativeEvent` for iOS, with
auth token/signing key kept as validation-only no-ops for now.
Remaining native bridge work:
- Request user notification authorization or rely consistently on
FlutterFire's `FirebaseMessaging.requestPermission`.
- Call `UIApplication.shared.registerForRemoteNotifications()`.
- Handle `application(_:didRegisterForRemoteNotificationsWithDeviceToken:)`.
- Handle `application(_:didFailToRegisterForRemoteNotificationsWithError:)`.
- Decide whether Firebase Messaging method swizzling stays enabled.
- If swizzling is disabled, set `Messaging.messaging().apnsToken` manually.
- Subscribe to FCM token refresh and forward token changes to Dart.
- Set `UNUserNotificationCenter.current().delegate`.
- Forward foreground receipt, notification tap, and launch-from-notification
payloads into Dart.
- Map APNs/FCM userInfo fields to `NotificationOpenedEvent`.
## iOS Token Contract
The current Dart implementation stores Android tokens as
`android_rn-v2:<raw-fcm-token>`. For iOS preflight, reserve the
Mattermost-compatible APNs server-registration format:
```text
apple_rn-v2:<raw-apns-token>
```
Use this distinction during testing:
- APNs device token: server registration candidate for the existing
Mattermost-compatible Apple push-proxy path.
- FCM registration token: Firebase direct-send smoke token. Do not register it
with the nexo server unless a future server/push-proxy path explicitly
supports FCM-backed iOS delivery.
- Direct APNs sends through Apple Push Notification Console also require the raw
APNs token, not the FCM registration token.
Follow-up implementation should make token formatting platform-aware instead of
using the current Android-oriented Dart constant.
## Payload Contract
For nexo routing, keep custom fields outside the APNs `aps` dictionary. FCM
`data` values must be strings.
Required or high-value fields:
| Field | Purpose |
|---|---|
| `type` | `message`, `opened`, `clear`, `session`, or `token_refresh` equivalent in the Dart contract. |
| `server_url` | Preferred direct routing target when available. |
| `server_id` | Fallback key if the app resolves server IDs locally. |
| `channel_id` | Channel routing. |
| `root_id` | Thread routing when CRT is enabled. |
| `is_crt_enabled` | Determines channel vs thread navigation behavior. |
| `ack_id` | ACK contract and signed push validation. |
| `post_id` | Notification identity and ACK enrichment. |
Visible FCM HTTP v1 smoke payload shape:
```json
{
"message": {
"token": "<FCM_REGISTRATION_TOKEN>",
"notification": {
"title": "nexo smoke",
"body": "iOS notification smoke"
},
"data": {
"type": "message",
"server_url": "https://<SMOKE_SERVER>",
"channel_id": "<CHANNEL_ID>",
"root_id": "",
"is_crt_enabled": "false",
"ack_id": "<ACK_ID>",
"post_id": "<POST_ID>"
},
"apns": {
"headers": {
"apns-push-type": "alert",
"apns-priority": "10",
"apns-topic": "<BUNDLE_ID>"
},
"payload": {
"aps": {
"sound": "default"
}
}
}
}
}
```
Silent/background smoke payload shape:
```json
{
"message": {
"token": "<FCM_REGISTRATION_TOKEN>",
"data": {
"type": "message",
"server_url": "https://<SMOKE_SERVER>",
"channel_id": "<CHANNEL_ID>"
},
"apns": {
"headers": {
"apns-push-type": "background",
"apns-priority": "5",
"apns-topic": "<BUNDLE_ID>"
},
"payload": {
"aps": {
"content-available": 1
}
}
}
}
}
```
Use visible alert smoke first. Silent pushes are best-effort and easier to
misread during early setup.
## Test Levels
### Level 0: Repo Contract Tests
No Apple/Firebase credentials required.
- Dart widget tests.
- Dart event mapping tests using `handleNativeEvent`.
- Integration tests using `debugSendNativeEvent` when native debug bridge exists.
This level validates the Dart routing contract only. It does not prove APNs or
FCM delivery.
### Level 1: Simulator / Local Injection
No real FCM via APNs.
- Use simulator/local notification payload injection only to exercise
userInfo parsing and open routing.
- Do not mark FCM delivery, APNs token registration, or background delivery as
passed from simulator evidence.
Example local `.apns` fixture shape:
```json
{
"Simulator Target Bundle": "<BUNDLE_ID>",
"aps": {
"alert": {
"title": "nexo smoke",
"body": "local routing fixture"
},
"sound": "default"
},
"type": "message",
"server_url": "https://<SMOKE_SERVER>",
"channel_id": "<CHANNEL_ID>",
"root_id": "",
"is_crt_enabled": "false"
}
```
Tracked fixture files live in `apps/flutter-test/ios/Fixtures/`:
- `open_channel.apns`: channel routing payload.
- `open_thread.apns`: CRT thread routing payload.
Before simulator injection, replace `REPLACE_WITH_IOS_BUNDLE_ID` with the
signed Runner bundle identifier. These fixtures prove local payload shape and
open-routing parsing only; they do not prove APNs registration, FCM delivery, or
server ACK behavior.
### Level 2: Physical Device FCM Smoke
Required for meaningful iOS delivery.
- Signed debug or release app installed on a real iPhone/iPad.
- Notification permission requested and accepted.
- APNs registration succeeds.
- FCM registration token is displayed or logged.
- A visible FCM message arrives.
- Tapping the notification opens or resumes the app.
- Dart receives a notification-open event with routing fields.
### Level 3: Nexo Server End-To-End Smoke
Required before claiming server contract compatibility.
- FCM token is registered with the nexo server using the agreed iOS token
prefix.
- Server can send a nexo-compatible push payload.
- Signed payload validation behavior is known.
- ACK request behavior is observed.
- Open routing reaches the host app callback.
## Manual Smoke Runbook
### 1. Preflight
- Confirm Xcode version and iOS device OS version.
- Confirm Bundle ID in Xcode, Apple Developer, and Firebase match.
- Confirm `GoogleService-Info.plist` exists locally and is part of Runner
target resources.
- Confirm APNs auth key or certificate is configured in Firebase.
- Confirm the app target has Push Notifications capability.
- Confirm `aps-environment` is present in the built app entitlements.
- Confirm no real credential values are staged in git.
### 2. Build And Install
From the Flutter test app:
```sh
cd apps/flutter-test
flutter pub get
flutter run -d <IOS_DEVICE_ID>
```
If Xcode signing configuration is not already set, open:
```sh
open ios/Runner.xcworkspace
```
Build from Xcode once to resolve signing and provisioning issues.
### 3. Permission And Token
Expected evidence:
- App launches without Firebase initialization error.
- iOS notification permission prompt appears.
- Permission state is logged.
- APNs registration success is logged.
- FCM token is logged or displayed in the app UI.
- `Device token:` in the Flutter test app changes from `pending` to a formatted
token once the Dart/native bridge supports iOS token handoff.
Important distinction:
- FCM sends to the FCM registration token returned by Firebase Messaging.
- Apple Push Notification Console or direct APNs sends require the APNs device
token from `didRegisterForRemoteNotificationsWithDeviceToken`.
### 4. Send A Visible FCM Message
Use Firebase Admin SDK or FCM HTTP v1. Keep payload, response ID, timestamp,
device state, and app logs as evidence.
Expected result:
- Foreground: app receives callback or foreground presentation according to the
native delegate policy.
- Background: system displays a notification.
- Terminated: tapping the notification starts the app and exposes the launch
payload.
### 5. Verify Routing
For channel routing:
- Payload has `is_crt_enabled=false`.
- Payload has `server_url` and `channel_id`.
- App UI updates `Last opened`.
- App UI updates `Last navigation` with `channel:<server>/<channel>`.
For thread routing:
- Payload has `is_crt_enabled=true`.
- Payload has `server_url` and `root_id`.
- App UI updates `Last opened`.
- App UI updates `Last navigation` with `thread:<server>/<root>`.
### 6. Verify ACK, Dismiss, Inline Reply Boundaries
Current expected state:
- ACK is not passed unless an iOS ACK client/native bridge exists.
- Dismiss is not passed unless notification category/delete handling exists.
- Inline reply is not passed unless `UNTextInputNotificationAction` and reply
forwarding exist.
If these are not implemented, record them as known iOS gaps instead of smoke
failures.
## Evidence To Keep
Do keep:
- Date, operator, device model, iOS version, Xcode version.
- Bundle ID shape, but not private Team ID if policy treats it as sensitive.
- Build configuration: Debug/Profile/Release.
- Entitlement proof with secrets redacted.
- Firebase project/app alias, not raw private IDs if policy forbids them.
- Permission status.
- APNs registration success/failure log.
- FCM token presence, redacted.
- FCM message ID or API response ID.
- Payload shape with private values replaced by placeholders.
- Screenshot or screen recording of notification display and app routing UI.
- App logs around receipt, tap, routing, ACK/dismiss/reply if implemented.
Use `docs/ios-notification-smoke-evidence-template.md` as the repeatable
evidence record when real device smoke begins.
Do not keep:
- `.p8`, `.p12`, provisioning profile, private key, or service-account JSON.
- Full APNs device token or FCM registration token.
- Full auth token, signing key, private server URL, or user data.
## Troubleshooting Matrix
| Symptom | Likely cause | Check |
|---|---|---|
| Firebase initialization fails | Missing or wrong `GoogleService-Info.plist` | Confirm file is in Runner target and Bundle ID matches. |
| Permission prompt never appears | Permission already decided, request not called, or initialization failed | Check iOS Settings and app logs. |
| APNs registration fails | Missing entitlement or bad signing profile | Inspect built entitlements and provisioning profile. |
| FCM token is null | APNs token not linked, Firebase app mismatch, network issue, or swizzling/delegate conflict | Check Firebase logs, APNs token callback, and Messaging setup. |
| FCM send succeeds but no notification | Wrong token, wrong Firebase project, APNs key not uploaded, device offline, notification disabled | Confirm token source, Firebase app, APNs config, device notification settings. |
| Direct APNs send fails | Using FCM token instead of APNs token, wrong topic, wrong environment | Use APNs device token and matching Bundle ID/environment. |
| Foreground receives nothing | `UNUserNotificationCenter` delegate missing or not forwarding | Confirm delegate setup and callback implementation. |
| Tap opens app but no routing | Launch/tap payload not forwarded to Dart | Check native open handler and EventChannel emission. |
| Background/silent unreliable | iOS best-effort delivery, wrong headers, app force-quit, missing background mode | Start with visible alert smoke and test silent separately. |
## Completion Criteria For This Milestone
Minimum pass for `ios-notification-test` planning smoke:
- Apple/Firebase ownership and credential flow are decided.
- Bundle ID and provisioning approach are documented privately.
- Firebase iOS app and local plist path are confirmed.
- Physical-device smoke path is confirmed.
- Current native bridge gaps are listed.
- A visible FCM/APNs smoke checklist exists with evidence fields.
- CI/headless boundary is explicit.
Minimum pass for a future implementation smoke:
- iOS native bridge emits Dart notification events.
- FCM token is obtained and handed to Dart with an iOS-compatible prefix.
- Visible FCM notification arrives on a physical device.
- Tapping the notification triggers `NotificationOpenedEvent`.
- Channel and thread routing callbacks are exercised.
- ACK/dismiss/inline reply are either implemented and verified or explicitly
tracked as remaining gaps.
## Official References
- Apple: Registering your app with APNs
https://developer.apple.com/documentation/usernotifications/registering-your-app-with-apns
- Apple: Generating a remote notification
https://developer.apple.com/documentation/usernotifications/generating-a-remote-notification
- Apple: Sending notification requests to APNs
https://developer.apple.com/documentation/usernotifications/sending-notification-requests-to-apns
- Apple: Testing notifications using the Push Notification Console
https://developer.apple.com/documentation/usernotifications/testing-notifications-using-the-push-notification-console
- Firebase: Add Firebase to your Apple project
https://firebase.google.com/docs/ios/setup
- Firebase: Set up a Firebase Cloud Messaging client app on Apple platforms
https://firebase.google.com/docs/cloud-messaging/get-started?platform=ios
- Firebase: Receive messages in Apple platform apps
https://firebase.google.com/docs/cloud-messaging/ios/receive-messages
- Firebase: Build app server send requests
https://firebase.google.com/docs/cloud-messaging/send-message
- FlutterFire: FCM via APNs Integration
https://firebase.flutter.dev/docs/messaging/apple-integration/
- FlutterFire: Cloud Messaging usage
https://firebase.flutter.dev/docs/messaging/usage
| Apple Team | The Apple Developer Team that owns the smoke app. |
| Bundle ID | A Bundle ID reserved for the iOS smoke app. |
| Signing path | Either allow automatic signing or provide the provisioning profile path privately. |
| Push capability | Enable Push Notifications for the App ID. |
| APNs credential | Prefer APNs auth key `.p8` plus Key ID and Team ID; certificate is a fallback. |
| Firebase app | Create/select the Firebase iOS app with the exact same Bundle ID. |
| Firebase plist | Provide `GoogleService-Info.plist` privately for `apps/flutter-test/ios/Runner/`. |
| Physical device | Provide an installable iPhone/iPad or define who operates it remotely. |
| Send path | Choose Firebase Console, FCM HTTP v1, or an existing private sender. |
| Evidence path | Choose where sanitized logs/screenshots/run notes should be stored. |
## Do Not Put In Tracked Files
- Apple Team ID, private Bundle ID, provisioning profile, `.p8`, `.p12`, Key ID,
certificate, or Apple account values.
- `GoogleService-Info.plist` or service-account JSON.
- APNs device token, FCM token, auth token, private endpoint, channel name, user
name, or server URL.
## Agent Can Then
- configure the local iOS host app with the selected signing and Bundle ID;
- place the private Firebase plist in the ignored local path;
- implement or verify APNs registration, token handoff, and open routing;
- run or coordinate the smoke through the approved send path;
- record sanitized evidence in the task or private run note.
## Current Repo Note
The iOS plugin has a minimum debug/action bridge, but real APNs/FCM smoke still
needs the private Apple/Firebase/device setup above plus native APNs/token/open
routing work.

View file

@ -1,109 +0,0 @@
# Linux Notification Smoke Evidence Template
Use this template when collecting Linux notification smoke evidence. Replace
every private value with a placeholder before storing output in tracked files.
## Run Metadata
- Date:
- Operator:
- Hardware or VM:
- Distribution and version:
- Desktop environment:
- Session type: X11 | Wayland | headless | other
- Notification daemon:
- Desktop portal available: yes | no | not tested
- Flutter version:
- Build mode: Debug | Profile | Release
- Package/runtime dependency path: D-Bus | desktop portal | libnotify | debug injection | other
## Preflight Inputs
- Linux platform registered in `packages/messaging_flutter/pubspec.yaml`: yes | no
- Linux plugin scaffold present: yes | no
- Linux test host scaffold present: yes | no
- Linux bridge registers `com.tokilabs.nexo.messaging/notifications`: yes | no
- Linux bridge registers `com.tokilabs.nexo.messaging/notification_actions`: yes | no
- Local fixture used: `open_channel.json` | `open_thread.json` | other
- Real account, private host, token, and machine-identifying values kept out of tracked files: yes | no
## Environment Evidence
Paste sanitized logs only.
```text
distribution=<redacted-or-placeholder>
desktop_environment=<GNOME|KDE|XFCE|other|not_tested>
session_type=<X11|Wayland|headless|not_tested>
notification_daemon=<redacted-or-placeholder|not_detected|not_tested>
portal_status=<available|missing|not_tested>
```
## Display Evidence
Payload file or sanitized JSON:
```json
{
"type": "message",
"server_url": "https://example.invalid",
"server_id": "server-smoke",
"channel_id": "channel-smoke",
"root_id": "",
"is_crt_enabled": "false",
"ack_id": "ack-smoke",
"post_id": "post-smoke"
}
```
Display path:
- Local debug injection:
- D-Bus notification daemon:
- Desktop portal notification:
- libnotify wrapper:
- Not tested:
Observed result:
```text
displayed=<yes|no|not_implemented>
notification_title=<redacted-or-placeholder>
notification_body=<redacted-or-placeholder>
action_data_present=<yes|no|not_implemented>
```
## Click / Open Routing
| Scenario | Expected | Actual | Evidence path |
|---|---|---|---|
| Channel click | `onNavigateToChannel` with server/channel | | |
| Thread click | `onNavigateToThread` with server/root | | |
| Foreground receipt | Raw `onNotification` event logged | | |
| Background click | App resumes and emits opened event | | |
| Cold start click | Launch payload forwarded | | |
## Lifecycle Evidence
```text
app_state_before=<foreground|background|not_running|not_tested>
app_state_after=<foreground|background|not_running|not_tested>
event_type=<message|opened|not_implemented>
userInteraction=<true|false|not_set>
```
## ACK, Dismiss, Inline Reply Boundary
| Feature | Expected for this run | Actual | Follow-up |
|---|---|---|---|
| ACK | gap documented unless implemented by a later plan | | |
| Dismiss | gap documented unless implemented by a later plan | | |
| Inline reply | gap documented unless implemented by a later plan | | |
## Sanitization Checklist
- [ ] Private host, username, channel name, and machine-identifying values removed.
- [ ] Full device token or remote push token removed.
- [ ] Private server URL, auth token, signing key, username, channel names removed.
- [ ] Screenshots or recordings contain no private message/user data.
- [ ] Distribution, daemon, and desktop details are sanitized when they identify a private environment.

View file

@ -1,169 +1,38 @@
# Linux Notification Test Guide
# Linux Notification Environment Preparation
Last verified: 2026-06-02
This guide fixes the preflight contract for Linux notification work before a
Linux test machine, distribution, desktop environment, notification daemon, or
real OS notification UI evidence is available.
Read this when you need to provide Linux runner and desktop notification
environment choices so an agent can finish Linux notification implementation and
smoke verification. You do not need to run the test from this document.
Do not commit private host values, device tokens, auth tokens, signing keys,
runtime endpoints, user names, channel names, desktop session details that
identify a private machine, or private package repository values into tracked
docs. Use placeholders in examples and keep credential files in approved local
or secret-store locations.
## Provide
## Current Repository State
| Needed | What to prepare |
|---|---|
| Linux runner | Local machine, VM, remote desktop, headless-only runner, or deferral. |
| Distribution | Distribution and version, such as Ubuntu, Debian, or Fedora. |
| Desktop session | GNOME/KDE/XFCE/other and X11/Wayland/headless. |
| Notification path | D-Bus notifications, XDG Desktop Portal, libnotify, or debug injection only. |
| Dependencies | Whether the agent may install required system packages. |
| Operator path | Who handles UI prompts/clicks if the runner is remote. |
| Evidence path | Choose where sanitized logs/screenshots/run notes should be stored. |
- `packages/messaging_flutter/pubspec.yaml` does not currently register a
Linux plugin platform.
- There is no `packages/messaging_flutter/linux/` plugin scaffold and no
`apps/flutter-test/linux/` host scaffold in the current repository.
- Dart already expects native notification and action channels:
`com.tokilabs.nexo.messaging/notifications` and
`com.tokilabs.nexo.messaging/notification_actions`.
- The Linux bridge is therefore not implemented yet. Until it exists, local
routing can only be tested through Dart unit tests or a future debug injection
bridge.
- Local routing fixtures live under `apps/flutter-test/linux/Fixtures/*.json`.
## Do Not Put In Tracked Files
The first useful Linux smoke target is:
- Private host names, user names, private endpoints, auth tokens, channel names,
server URLs, machine-identifying session details, private package repository
credentials, device tokens, or signing keys.
1. Add a Linux plugin platform registration and generated host scaffold in a
controlled implementation plan.
2. Register the expected EventChannel and MethodChannel in the Linux plugin.
3. Implement a debug-only local injection path compatible with
`debugSendNativeEvent`.
4. Choose a notification API path and runtime dependency strategy for the smoke.
5. Map notification daemon action data or portal activation data into the Dart
`NotificationOpenedEvent` contract.
6. Collect display, click/open routing, lifecycle, and known ACK, dismiss, and
inline reply gap evidence.
## Agent Can Then
## Platform Registration Contract
- add Linux platform registration and host scaffold if selected;
- implement the native notification/action bridge;
- configure the selected desktop notification path;
- run routing checks and, if allowed, visible notification smoke;
- record sanitized evidence in the task or private run note.
Do not add a partial Linux platform entry without the matching source and host
scaffold. A future implementation plan should update these together:
## Current Repo Note
```yaml
flutter:
plugin:
platforms:
linux:
pluginClass: NexoMessagingPlugin
```
Expected source/host areas for that plan:
- `packages/messaging_flutter/linux/` for the Linux plugin implementation.
- `apps/flutter-test/linux/` for the test host scaffold generated by Flutter.
- `packages/messaging_flutter/pubspec.yaml` for platform registration.
- `apps/flutter-test/README.md` and package docs for smoke instructions.
The current preflight milestone documents this contract only. It does not claim
Linux runtime support.
## Minimum Bridge Contract
The Dart SDK expects the Linux native side to preserve these channel names:
| Channel | Name | Direction | Minimum Linux behavior |
|---|---|---|---|
| EventChannel | `com.tokilabs.nexo.messaging/notifications` | native to Dart | Emit codec-safe maps for notification, open, token, clear, session, and future action-failure events. |
| MethodChannel | `com.tokilabs.nexo.messaging/notification_actions` | Dart to native | Accept SDK action methods used by Dart without crashing the host app. |
Minimum methods for the action channel:
| Method | Minimum preflight result | Production expectation |
|---|---|---|
| `debugSendNativeEvent` | Validate the argument is a map and emit it on the EventChannel. | Debug/test-only injection path. |
| `saveDeviceToken` | Accept and store or no-op a formatted token string without logging secrets. | Store the Linux server-registration token once the platform token source is chosen. |
| `getDeviceToken` | Return the last stored formatted token or null. | Return the current formatted Linux server-registration token. |
| `setAuthToken` | Validate required string arguments and no-op. | Persist token for ACK/reply work only after storage rules are designed. |
| `clearAuthToken` | Validate `serverUrl` and no-op. | Clear persisted token for the server. |
| `setSigningKey` | Validate required string arguments and no-op without persistence. | Persist the signing key only after secure storage rules are designed. |
Fallback boundaries:
- Missing bridge methods should return the Flutter Linux equivalent of "not
implemented", not a successful placeholder.
- Token, auth, signing-key, ACK, dismiss, and inline reply persistence are not
implied by this preflight milestone.
- Real notification display and activation handling belong to `Linux 알림 테스트`
unless a smaller bridge implementation plan explicitly handles them first.
## Notification API Matrix
| Candidate | Environment fit | Strength | Preflight constraint |
|---|---|---|---|
| D-Bus `org.freedesktop.Notifications` | Common desktop notification daemon path | Broad support across GNOME, KDE, XFCE, and other desktops | Action callbacks and capabilities vary by daemon and session. |
| XDG Desktop Portal notifications | Flatpak/sandbox-friendly desktops | Better fit for portal-mediated desktop environments | Portal availability and notification support vary by distribution and session. |
| libnotify wrapper | Thin client around the freedesktop notification spec | Simple display smoke for common desktops | Callback/action support still depends on the underlying daemon. |
| Local debug injection only | No daemon, package, or desktop requirement | Deterministic EventChannel/opened-routing verification | Does not prove OS display or activation. |
Preflight default: use local debug injection for contract tests, and defer the
D-Bus vs desktop portal vs libnotify runtime decision to the `Linux 알림 테스트`
milestone or a dedicated implementation plan.
## Runtime Dependency Boundaries
Future Linux notification implementation should record the runtime assumptions
before claiming smoke support:
- Distribution and version, such as Ubuntu, Fedora, Debian, or another target.
- Desktop environment and session type, such as GNOME/KDE/XFCE and X11/Wayland.
- Notification daemon or portal availability.
- Required build packages and runtime libraries.
- Whether action callbacks are supported by the chosen daemon or portal path.
Tracked docs should keep these as sanitized evidence fields, not private machine
configuration dumps.
## Payload Mapping
Linux notification action data maps into Dart using the same stable keys as the
Android, iOS, macOS, and Windows contracts:
| Payload key | Dart event field | Notes |
|---|---|---|
| `type` | `type` | Use `message` for visible message notifications and `opened` for direct open events. |
| `server_url` | `server_url` | Required before host navigation callbacks can run. |
| `server_id` | `server_id` | Diagnostic and future server URL fallback input. |
| `channel_id` | `channel_id` | Drives `onNavigateToChannel` when thread routing is not selected. |
| `root_id` | `root_id` | Drives `onNavigateToThread` when CRT is enabled. |
| `is_crt_enabled` | `is_crt_enabled` | Accept boolean `true`, string `"true"`, or string `"1"`. |
| `ack_id` | `ack_id` | Preserve for future ACK evidence; do not invent ACK behavior. |
| `post_id` | `post_id` | Preserve for notification identity and future ACK/display evidence. |
| `userInteraction` | `userInteraction` | Set to `true` when a click/open path caused the event. |
Routing expectation:
- Channel fixture: `server_url` and `channel_id` are present, `root_id` is empty
and `is_crt_enabled` is false, so Dart calls `onNavigateToChannel`.
- Thread fixture: `server_url` and `root_id` are present and
`is_crt_enabled` is true, so Dart calls `onNavigateToThread`.
## Local Fixtures
Use the fixture files as sanitized payloads for a future debug injection method
or for manual D-Bus/portal action data construction during smoke testing:
- `apps/flutter-test/linux/Fixtures/open_channel.json`
- `apps/flutter-test/linux/Fixtures/open_thread.json`
The fixtures intentionally use `https://example.invalid` and smoke IDs. Replace
private runtime values only in local run notes or sanitized evidence.
## Evidence
Use [`linux-notification-smoke-evidence-template.md`](linux-notification-smoke-evidence-template.md)
for Linux runs. Preflight evidence should explicitly separate:
- distribution, desktop environment, session, and daemon or portal state
- visible display state
- click/open routing result
- app lifecycle state
- ACK, dismiss, and inline reply implementation gaps
Until the scaffold, bridge, and real OS smoke exist, a valid preflight result
may be "not implemented yet" with a concrete follow-up plan, rather than a
failed runtime claim.
Linux is not registered yet and has no plugin or test-host scaffold. Smoke work
first needs platform scaffold plus the environment choices above.

View file

@ -1,104 +0,0 @@
# macOS Notification Smoke Evidence Template
Use this template when collecting macOS notification smoke evidence. Replace
every private value with a placeholder before storing output in tracked files.
## Run Metadata
- Date:
- Operator:
- Hardware:
- macOS version:
- Xcode version:
- Flutter version:
- Build mode: Debug | Profile | Release
- Bundle ID: `<redacted-or-placeholder>`
- Signing mode: local ad hoc | Apple Developer | not signed for notification smoke
- Notarization state: not tested | local placeholder | submitted | accepted | failed
## Preflight Inputs
- macOS bridge registers `com.tokilabs.nexo.messaging/notifications`: yes | no
- macOS bridge registers `com.tokilabs.nexo.messaging/notification_actions`: yes | no
- Legacy `nexo_messaging` channel retained for `getPlatformVersion`: yes | no
- Local fixture used: `open_channel.json` | `open_thread.json` | other
- Entitlements inspected: yes | no
- Notification capability present in built app: yes | no | not tested
- Real signing values kept out of tracked files: yes | no
## Permission Evidence
Paste sanitized logs only.
```text
permission_status=<authorized|denied|provisional|not_determined|not_implemented>
request_path=<FlutterFire|UserNotifications|not_implemented>
notes=<redacted-or-placeholder>
```
## Display Evidence
Payload file or sanitized JSON:
```json
{
"type": "message",
"server_url": "https://example.invalid",
"server_id": "server-smoke",
"channel_id": "channel-smoke",
"root_id": "",
"is_crt_enabled": "false",
"ack_id": "ack-smoke",
"post_id": "post-smoke"
}
```
Display path:
- Local debug injection:
- Local notification API:
- Remote push provider:
- Not tested:
Observed result:
```text
displayed=<yes|no|not_implemented>
notification_title=<redacted-or-placeholder>
notification_body=<redacted-or-placeholder>
```
## Click / Open Routing
| Scenario | Expected | Actual | Evidence path |
|---|---|---|---|
| Channel click | `onNavigateToChannel` with server/channel | | |
| Thread click | `onNavigateToThread` with server/root | | |
| Foreground receipt | Raw `onNotification` event logged | | |
| Background click | App resumes and emits opened event | | |
| Terminated click | Launch payload forwarded | | |
## Lifecycle Evidence
```text
app_state_before=<foreground|background|terminated|not_tested>
app_state_after=<foreground|background|terminated|not_tested>
event_type=<message|opened|not_implemented>
userInteraction=<true|false|not_set>
```
## ACK, Dismiss, Inline Reply Boundary
| Feature | Expected for this run | Actual | Follow-up |
|---|---|---|---|
| ACK | gap documented unless implemented by a later plan | | |
| Dismiss | gap documented unless implemented by a later plan | | |
| Inline reply | gap documented unless implemented by a later plan | | |
## Sanitization Checklist
- [ ] Apple Team ID, signing identity, provisioning profile, certificate, and notarization values removed.
- [ ] Full device token or remote push token removed.
- [ ] Firebase API key, app ID, sender ID, service account details removed if Firebase was used.
- [ ] Private server URL, auth token, signing key, username, channel names removed.
- [ ] Screenshots or recordings contain no private message/user data.

View file

@ -1,143 +1,38 @@
# macOS Notification Test Guide
# macOS Notification Access Preparation
Last verified: 2026-06-02
This guide fixes the preflight contract for macOS notification work before
Apple signing, a macOS test device, or real OS notification UI evidence is
available.
Read this when you need to provide macOS runner/signing access so an agent can
finish macOS notification implementation and smoke verification. You do not need
to run the test from this document.
Do not commit real Apple, APNs, Firebase, service-account, device-token, auth
token, private endpoint, Team ID, signing identity, provisioning, notarization,
or private host values into tracked docs. Use placeholders in examples and keep
credential files in approved local or secret-store locations.
## Provide
## Current Repository State
| Needed | What to prepare |
|---|---|
| macOS runner | Local Mac, remote Mac, or a decision that visible smoke is deferred. |
| Operator path | Who handles UI prompts if the runner is remote. |
| Signing mode | Local ad hoc, Apple Developer signing, or distribution/notarization path. |
| Bundle ID | Required if signing beyond ad hoc is needed. |
| Apple access | Team/provisioning access if notification entitlement or APNs is in scope. |
| Push scope | Decide local notification only, APNs/Firebase-backed smoke, or deferred push. |
| Evidence path | Choose where sanitized logs/screenshots/run notes should be stored. |
- The macOS plugin is still a scaffold. `packages/messaging_flutter/macos/Classes/NexoMessagingPlugin.swift`
currently registers only the legacy `nexo_messaging` method channel and
implements `getPlatformVersion`.
- Dart expects native notification and action channels:
`com.tokilabs.nexo.messaging/notifications` and
`com.tokilabs.nexo.messaging/notification_actions`.
- The minimum macOS bridge is therefore not implemented yet. Until it exists,
local routing can only be tested through Dart unit tests or a future debug
injection bridge.
- `apps/flutter-test/macos` is a host scaffold. It carries sandbox
entitlements, but no notification entitlement, signing team, provisioning
profile, hardened runtime, or notarization value is tracked in this repo.
- Local routing fixtures live under `apps/flutter-test/macos/Fixtures/*.json`.
## Do Not Put In Tracked Files
The first useful macOS smoke target is:
- Team ID, signing identity, provisioning profile, notarization profile, Apple
ID, APNs/Firebase secrets, device token, private endpoint, auth token, channel
name, user name, or server URL.
1. Register the expected EventChannel and MethodChannel in the macOS plugin.
2. Implement a debug-only local injection path compatible with
`debugSendNativeEvent`.
3. Map notification `userInfo` fields into the Dart
`NotificationOpenedEvent` contract.
4. Build and run the test host with local placeholder signing.
5. Collect permission, display, click/open routing, lifecycle, and known ACK,
dismiss, and inline reply gap evidence.
## Agent Can Then
## Minimum Bridge Contract
- configure the macOS host app for the selected signing mode;
- implement or verify the native notification/action bridge;
- run local routing checks and, if allowed, visible notification smoke;
- record sanitized evidence in the task or private run note.
The Dart SDK expects the macOS native side to preserve these channel names:
## Current Repo Note
| Channel | Name | Direction | Minimum macOS behavior |
|---|---|---|---|
| EventChannel | `com.tokilabs.nexo.messaging/notifications` | native to Dart | Emit codec-safe maps for notification, open, token, clear, session, and future action-failure events. |
| MethodChannel | `com.tokilabs.nexo.messaging/notification_actions` | Dart to native | Accept SDK action methods used by Dart without crashing the host app. |
| Legacy MethodChannel | `nexo_messaging` | Dart/test compatibility | Keep `getPlatformVersion` until the generated example compatibility path is intentionally removed. |
Minimum methods for the action channel:
| Method | Minimum preflight result | Production expectation |
|---|---|---|
| `debugSendNativeEvent` | Validate the argument is a map and emit it on the EventChannel. | Debug/test-only injection path. |
| `saveDeviceToken` | Accept and store or no-op a formatted token string without logging secrets. | Store the macOS server-registration token once the platform token source is chosen. |
| `getDeviceToken` | Return the last stored formatted token or null. | Return the current formatted macOS server-registration token. |
| `setAuthToken` | Validate required string arguments and no-op. | Persist token for ACK/reply work only after storage rules are designed. |
| `clearAuthToken` | Validate `serverUrl` and no-op. | Clear persisted token for the server. |
| `setSigningKey` | Validate required string arguments and no-op without persistence. | Persist the signing key only after secure storage rules are designed. |
Fallback boundaries:
- Missing bridge methods should return `FlutterMethodNotImplemented`, not a
successful placeholder.
- Token, auth, signing-key, ACK, dismiss, and inline reply persistence are not
implied by this preflight milestone.
- Real notification permission prompts and visible notification display belong
to `macOS 알림 테스트` unless a small debug bridge task explicitly implements
them first.
## Host Placeholder Criteria
Tracked macOS host files must stay generic:
- Keep real signing Team ID, identity, provisioning profile, notarization
profile, Apple ID, and private host values out of tracked files.
- Keep `apps/flutter-test/macos/Runner/DebugProfile.entitlements` and
`Release.entitlements` as placeholder entitlements until a smoke run requires
a specific capability.
- Add notification capability through Xcode or local build settings for a smoke
run, then record sanitized evidence instead of committing private signing
state.
- Do not add real credential files under `apps/flutter-test/macos`.
Expected local placeholder checks:
```sh
codesign -d --entitlements :- build/macos/Build/Products/Debug/flutter_test.app
```
Look for sandbox values and any locally added notification-related capability.
Record private identifiers as placeholders in evidence.
## Payload Mapping
macOS notification `userInfo` maps into Dart using the same stable keys as the
Android and iOS contracts:
| `userInfo` key | Dart event field | Notes |
|---|---|---|
| `type` | `type` | Use `message` for visible message notifications and `opened` for direct open events. |
| `server_url` | `server_url` | Required before host navigation callbacks can run. |
| `server_id` | `server_id` | Diagnostic and future server URL fallback input. |
| `channel_id` | `channel_id` | Drives `onNavigateToChannel` when thread routing is not selected. |
| `root_id` | `root_id` | Drives `onNavigateToThread` when CRT is enabled. |
| `is_crt_enabled` | `is_crt_enabled` | Accept boolean `true`, string `"true"`, or string `"1"`. |
| `ack_id` | `ack_id` | Preserve for future ACK evidence; do not invent ACK behavior. |
| `post_id` | `post_id` | Preserve for notification identity and future ACK/display evidence. |
| `userInteraction` | `userInteraction` | Set to `true` when a click/open path caused the event. |
Routing expectation:
- Channel fixture: `server_url` and `channel_id` are present, `root_id` is empty
and `is_crt_enabled` is false, so Dart calls `onNavigateToChannel`.
- Thread fixture: `server_url` and `root_id` are present and
`is_crt_enabled` is true, so Dart calls `onNavigateToThread`.
## Local Fixtures
Use the fixture files as sanitized payloads for a future debug injection method
or for manual `userInfo` construction during smoke testing:
- `apps/flutter-test/macos/Fixtures/open_channel.json`
- `apps/flutter-test/macos/Fixtures/open_thread.json`
The fixtures intentionally use `https://example.invalid` and smoke IDs. Replace
private runtime values only in local run notes or sanitized evidence.
## Evidence
Use [`macos-notification-smoke-evidence-template.md`](macos-notification-smoke-evidence-template.md)
for macOS runs. Preflight evidence should explicitly separate:
- permission state
- visible display state
- click/open routing result
- app lifecycle state
- ACK, dismiss, and inline reply implementation gaps
Until the bridge and real OS smoke exist, a valid preflight result may be
"not implemented yet" with a concrete follow-up plan, rather than a failed
runtime claim.
The macOS plugin is still a scaffold. It currently has only the legacy
`nexo_messaging` channel, so visible notification smoke also needs bridge
implementation work.

View file

@ -1,95 +0,0 @@
# Windows Notification Smoke Evidence Template
Use this template when collecting Windows notification smoke evidence. Replace
every private value with a placeholder before storing output in tracked files.
## Run Metadata
- Date:
- Operator:
- Hardware or VM:
- Windows version:
- Visual Studio version:
- Windows SDK version:
- Flutter version:
- Build mode: Debug | Profile | Release
- Packaging mode: unpackaged | MSIX | sparse package | not tested
- Package identity/AppUserModelID: `<redacted-or-placeholder>`
- Notification API path: Windows App SDK | WinRT toast | debug injection | other
## Preflight Inputs
- Windows platform registered in `packages/messaging_flutter/pubspec.yaml`: yes | no
- Windows plugin scaffold present: yes | no
- Windows test host scaffold present: yes | no
- Windows bridge registers `com.tokilabs.nexo.messaging/notifications`: yes | no
- Windows bridge registers `com.tokilabs.nexo.messaging/notification_actions`: yes | no
- Local fixture used: `open_channel.json` | `open_thread.json` | other
- Real account, package, signing, and private host values kept out of tracked files: yes | no
## Display Evidence
Payload file or sanitized JSON:
```json
{
"type": "message",
"server_url": "https://example.invalid",
"server_id": "server-smoke",
"channel_id": "channel-smoke",
"root_id": "",
"is_crt_enabled": "false",
"ack_id": "ack-smoke",
"post_id": "post-smoke"
}
```
Display path:
- Local debug injection:
- Windows App SDK notification:
- WinRT toast notification:
- Not tested:
Observed result:
```text
displayed=<yes|no|not_implemented>
notification_title=<redacted-or-placeholder>
notification_body=<redacted-or-placeholder>
activation_arguments_present=<yes|no|not_implemented>
```
## Click / Open Routing
| Scenario | Expected | Actual | Evidence path |
|---|---|---|---|
| Channel click | `onNavigateToChannel` with server/channel | | |
| Thread click | `onNavigateToThread` with server/root | | |
| Foreground receipt | Raw `onNotification` event logged | | |
| Background click | App resumes and emits opened event | | |
| Cold start click | Launch payload forwarded | | |
## Lifecycle Evidence
```text
app_state_before=<foreground|background|not_running|not_tested>
app_state_after=<foreground|background|not_running|not_tested>
event_type=<message|opened|not_implemented>
userInteraction=<true|false|not_set>
```
## ACK, Dismiss, Inline Reply Boundary
| Feature | Expected for this run | Actual | Follow-up |
|---|---|---|---|
| ACK | gap documented unless implemented by a later plan | | |
| Dismiss | gap documented unless implemented by a later plan | | |
| Inline reply | gap documented unless implemented by a later plan | | |
## Sanitization Checklist
- [ ] Microsoft account, package identity, certificate, Store association, and AppUserModelID values removed.
- [ ] Full device token or remote push token removed.
- [ ] Private server URL, auth token, signing key, username, channel names removed.
- [ ] Screenshots or recordings contain no private message/user data.

View file

@ -1,155 +1,38 @@
# Windows Notification Test Guide
# Windows Notification Environment Preparation
Last verified: 2026-06-02
This guide fixes the preflight contract for Windows notification work before a
Windows test machine, Microsoft account, packaging decision, or real OS
notification UI evidence is available.
Read this when you need to provide Windows runner, packaging, and signing
choices so an agent can finish Windows notification implementation and smoke
verification. You do not need to run the test from this document.
Do not commit Microsoft account values, package identity values, signing
certificates, store association files, push credentials, device tokens, auth
tokens, private endpoints, or private host values into tracked docs. Use
placeholders in examples and keep credential files in approved local or
secret-store locations.
## Provide
## Current Repository State
| Needed | What to prepare |
|---|---|
| Windows runner | Local PC, VM, remote machine, or a decision that visible smoke is deferred. |
| Toolchain | Windows version plus Visual Studio/C++ desktop workload/Windows SDK availability. |
| Packaging mode | Unpackaged, MSIX, sparse package, or debug injection only. |
| Notification API | Windows App SDK, WinRT toast, desktop AppUserModelID fallback, or debug injection only. |
| Identity | Package identity or AppUserModelID if visible toast activation is in scope. |
| Signing | Test certificate, organization cert, Store association, or no signing. |
| Evidence path | Choose where sanitized logs/screenshots/run notes should be stored. |
- `packages/messaging_flutter/pubspec.yaml` does not currently register a
Windows plugin platform.
- There is no `packages/messaging_flutter/windows/` plugin scaffold and no
`apps/flutter-test/windows/` host scaffold in the current repository.
- Dart already expects native notification and action channels:
`com.tokilabs.nexo.messaging/notifications` and
`com.tokilabs.nexo.messaging/notification_actions`.
- The Windows bridge is therefore not implemented yet. Until it exists, local
routing can only be tested through Dart unit tests or a future debug injection
bridge.
- Local routing fixtures live under `apps/flutter-test/windows/Fixtures/*.json`.
## Do Not Put In Tracked Files
The first useful Windows smoke target is:
- Microsoft account values, package identity if private, signing certificate
contents/passwords, Store association files, push credentials, device token,
private endpoint, auth token, channel name, user name, or server URL.
1. Add a Windows plugin platform registration and generated host scaffold in a
controlled implementation plan.
2. Register the expected EventChannel and MethodChannel in the Windows plugin.
3. Implement a debug-only local injection path compatible with
`debugSendNativeEvent`.
4. Choose a notification API path and package identity strategy for the smoke.
5. Map notification activation arguments or user data into the Dart
`NotificationOpenedEvent` contract.
6. Collect display, click/open routing, lifecycle, and known ACK, dismiss, and
inline reply gap evidence.
## Agent Can Then
## Platform Registration Contract
- add Windows platform registration and host scaffold if selected;
- implement the native notification/action bridge;
- configure the selected notification API and packaging path;
- run routing checks and, if allowed, visible toast smoke;
- record sanitized evidence in the task or private run note.
Do not add a partial Windows platform entry without the matching source and host
scaffold. A future implementation plan should update these together:
## Current Repo Note
```yaml
flutter:
plugin:
platforms:
windows:
pluginClass: NexoMessagingPlugin
```
Expected source/host areas for that plan:
- `packages/messaging_flutter/windows/` for the Windows plugin implementation.
- `apps/flutter-test/windows/` for the test host scaffold generated by Flutter.
- `packages/messaging_flutter/pubspec.yaml` for platform registration.
- `apps/flutter-test/README.md` and package docs for smoke instructions.
The current preflight milestone documents this contract only. It does not claim
Windows runtime support.
## Minimum Bridge Contract
The Dart SDK expects the Windows native side to preserve these channel names:
| Channel | Name | Direction | Minimum Windows behavior |
|---|---|---|---|
| EventChannel | `com.tokilabs.nexo.messaging/notifications` | native to Dart | Emit codec-safe maps for notification, open, token, clear, session, and future action-failure events. |
| MethodChannel | `com.tokilabs.nexo.messaging/notification_actions` | Dart to native | Accept SDK action methods used by Dart without crashing the host app. |
Minimum methods for the action channel:
| Method | Minimum preflight result | Production expectation |
|---|---|---|
| `debugSendNativeEvent` | Validate the argument is a map and emit it on the EventChannel. | Debug/test-only injection path. |
| `saveDeviceToken` | Accept and store or no-op a formatted token string without logging secrets. | Store the Windows server-registration token once the platform token source is chosen. |
| `getDeviceToken` | Return the last stored formatted token or null. | Return the current formatted Windows server-registration token. |
| `setAuthToken` | Validate required string arguments and no-op. | Persist token for ACK/reply work only after storage rules are designed. |
| `clearAuthToken` | Validate `serverUrl` and no-op. | Clear persisted token for the server. |
| `setSigningKey` | Validate required string arguments and no-op without persistence. | Persist the signing key only after secure storage rules are designed. |
Fallback boundaries:
- Missing bridge methods should return the Flutter Windows equivalent of
"not implemented", not a successful placeholder.
- Token, auth, signing-key, ACK, dismiss, and inline reply persistence are not
implied by this preflight milestone.
- Real notification display and activation handling belong to `Windows 알림 테스트`
unless a smaller bridge implementation plan explicitly handles them first.
## Notification API Matrix
| Candidate | Packaging fit | Strength | Preflight constraint |
|---|---|---|---|
| Windows App SDK notifications | Best with package identity or sparse package | Modern toast activation path and current Microsoft guidance | Requires Windows App SDK dependency and packaging decision. |
| WinRT toast notifications | Best with MSIX/package identity | Direct OS toast APIs and activation arguments | Packaging/AppUserModelID details must be decided before reliable smoke. |
| Desktop bridge / AppUserModelID shortcuts | Unpackaged desktop fallback | Can support classic desktop notification experiments | More local setup variance and weaker repeatability. |
| Local debug injection only | No packaging requirement | Deterministic EventChannel/opened-routing verification | Does not prove OS display or activation. |
Preflight default: use local debug injection for contract tests, and defer the
Windows App SDK vs WinRT packaging decision to the `Windows 알림 테스트` milestone
or a dedicated implementation plan.
## Payload Mapping
Windows activation payloads map into Dart using the same stable keys as the
Android, iOS, and macOS contracts:
| Payload key | Dart event field | Notes |
|---|---|---|
| `type` | `type` | Use `message` for visible message notifications and `opened` for direct open events. |
| `server_url` | `server_url` | Required before host navigation callbacks can run. |
| `server_id` | `server_id` | Diagnostic and future server URL fallback input. |
| `channel_id` | `channel_id` | Drives `onNavigateToChannel` when thread routing is not selected. |
| `root_id` | `root_id` | Drives `onNavigateToThread` when CRT is enabled. |
| `is_crt_enabled` | `is_crt_enabled` | Accept boolean `true`, string `"true"`, or string `"1"`. |
| `ack_id` | `ack_id` | Preserve for future ACK evidence; do not invent ACK behavior. |
| `post_id` | `post_id` | Preserve for notification identity and future ACK/display evidence. |
| `userInteraction` | `userInteraction` | Set to `true` when a click/open path caused the event. |
Routing expectation:
- Channel fixture: `server_url` and `channel_id` are present, `root_id` is empty
and `is_crt_enabled` is false, so Dart calls `onNavigateToChannel`.
- Thread fixture: `server_url` and `root_id` are present and
`is_crt_enabled` is true, so Dart calls `onNavigateToThread`.
## Local Fixtures
Use the fixture files as sanitized payloads for a future debug injection method
or for manual activation/user-data construction during smoke testing:
- `apps/flutter-test/windows/Fixtures/open_channel.json`
- `apps/flutter-test/windows/Fixtures/open_thread.json`
The fixtures intentionally use `https://example.invalid` and smoke IDs. Replace
private runtime values only in local run notes or sanitized evidence.
## Evidence
Use [`windows-notification-smoke-evidence-template.md`](windows-notification-smoke-evidence-template.md)
for Windows runs. Preflight evidence should explicitly separate:
- packaging and identity state
- visible display state
- click/open routing result
- app lifecycle state
- ACK, dismiss, and inline reply implementation gaps
Until the scaffold, bridge, and real OS smoke exist, a valid preflight result
may be "not implemented yet" with a concrete follow-up plan, rather than a
failed runtime claim.
Windows is not registered yet and has no plugin or test-host scaffold. Smoke
work first needs platform scaffold plus the environment choices above.

View file

@ -51,8 +51,8 @@ copying upstream mobile code.
| --- | --- | --- |
| Android | Production target | Native FCM service, Room storage, JJWT verification, OkHttp ACK client, notification builder, inline reply receiver, dismiss receiver |
| iOS | Stub | Plugin scaffold exists, behavior is not implemented yet |
| macOS | Stub | Plugin scaffold exists; preflight bridge, fixture, host placeholder, and evidence criteria are documented in [`../../docs/macos-notification-test-guide.md`](../../docs/macos-notification-test-guide.md) |
| Windows | Not registered | No Flutter plugin scaffold yet; platform registration, bridge scope, API matrix, fixture, and evidence criteria are documented in [`../../docs/windows-notification-test-guide.md`](../../docs/windows-notification-test-guide.md) |
| macOS | Stub | Plugin scaffold exists; user-provided signing, machine, APNs/Firebase scope, and evidence handoff requirements are documented in [`../../docs/macos-notification-test-guide.md`](../../docs/macos-notification-test-guide.md) |
| Windows | Not registered | No Flutter plugin scaffold yet; user-provided Windows runner, packaging, identity, signing, and evidence handoff requirements are documented in [`../../docs/windows-notification-test-guide.md`](../../docs/windows-notification-test-guide.md) |
## Repository Layout
@ -516,8 +516,9 @@ implementation work starts. Dart expects the macOS plugin to register
notification `userInfo` fields to `NotificationOpenedEvent`.
Use [`../../docs/macos-notification-test-guide.md`](../../docs/macos-notification-test-guide.md)
for the bridge scope, no-op/fallback boundaries, host placeholder criteria,
payload mapping, fixture paths, and evidence expectations.
to identify the Apple signing, macOS runner, APNs/Firebase scope, and private
handoff values the user must provide before an agent can complete macOS
notification smoke work.
## Windows Preflight Notes
@ -532,8 +533,9 @@ Dart still expects any future Windows bridge to use
or debug injection payloads to `NotificationOpenedEvent`.
Use [`../../docs/windows-notification-test-guide.md`](../../docs/windows-notification-test-guide.md)
for platform registration scope, bridge boundaries, notification API candidates,
fixture paths, and evidence expectations.
to identify the Windows runner, packaging mode, notification API path, package
identity, signing, and private handoff values the user must provide before an
agent can complete Windows notification smoke work.
## Test Strategy