From 0faa687954ad1beeaa5d54dd82156177117a6156 Mon Sep 17 00:00:00 2001 From: toki Date: Tue, 2 Jun 2026 04:32:38 +0900 Subject: [PATCH] feat: macos notification support and documentation updates - Add macos notification smoke evidence template and test guide - Add Fixtures for open_channel and open_thread json - Update roadmaps for messaging runtime phase and macos notification milestone - Update flutter-test and messaging_flutter READMEs --- agent-roadmap/current.md | 2 +- .../phase/messaging-runtime/PHASE.md | 2 +- .../macos-notification-preflight.md | 23 +-- apps/flutter-test/README.md | 9 ++ .../macos/Fixtures/open_channel.json | 11 ++ .../macos/Fixtures/open_thread.json | 11 ++ ...os-notification-smoke-evidence-template.md | 104 +++++++++++++ docs/macos-notification-test-guide.md | 143 ++++++++++++++++++ packages/messaging_flutter/README.md | 15 +- 9 files changed, 307 insertions(+), 13 deletions(-) create mode 100644 apps/flutter-test/macos/Fixtures/open_channel.json create mode 100644 apps/flutter-test/macos/Fixtures/open_thread.json create mode 100644 docs/macos-notification-smoke-evidence-template.md create mode 100644 docs/macos-notification-test-guide.md diff --git a/agent-roadmap/current.md b/agent-roadmap/current.md index fce2ecb1..fd4021e4 100644 --- a/agent-roadmap/current.md +++ b/agent-roadmap/current.md @@ -7,7 +7,7 @@ ## 활성 Milestone -- [계획] macOS 알림 선행 정리 +- [검토중] macOS 알림 선행 정리 - Phase: `agent-roadmap/phase/messaging-runtime/PHASE.md` - 경로: `agent-roadmap/phase/messaging-runtime/milestones/macos-notification-preflight.md` diff --git a/agent-roadmap/phase/messaging-runtime/PHASE.md b/agent-roadmap/phase/messaging-runtime/PHASE.md index 7463b7d1..ae1ab83a 100644 --- a/agent-roadmap/phase/messaging-runtime/PHASE.md +++ b/agent-roadmap/phase/messaging-runtime/PHASE.md @@ -25,7 +25,7 @@ Mattermost server/webapp/push-proxy는 upstream-followable runtime으로 두고, - [완료] iOS 알림 선행 정리 - 경로: `agent-roadmap/archive/phase/messaging-runtime/milestones/ios-notification-preflight.md` - 요약: Apple/Firebase/APNs 실자산 없이 iOS token 계약, native bridge 최소 범위, fixture, evidence 기준을 정리했다. -- [계획] macOS 알림 선행 정리 +- [검토중] macOS 알림 선행 정리 - 경로: `agent-roadmap/phase/messaging-runtime/milestones/macos-notification-preflight.md` - 요약: Apple signing/장비 없이 macOS native bridge 최소 범위, host placeholder, fixture, evidence 기준을 먼저 정리한다. - [계획] Windows 알림 선행 정리 diff --git a/agent-roadmap/phase/messaging-runtime/milestones/macos-notification-preflight.md b/agent-roadmap/phase/messaging-runtime/milestones/macos-notification-preflight.md index e4dbb7a1..eb1373fa 100644 --- a/agent-roadmap/phase/messaging-runtime/milestones/macos-notification-preflight.md +++ b/agent-roadmap/phase/messaging-runtime/milestones/macos-notification-preflight.md @@ -12,7 +12,7 @@ macOS native bridge 최소 범위, host placeholder, payload fixture, evidence ## 상태 -[계획] +[검토중] ## 승격 조건 @@ -38,21 +38,24 @@ macOS native bridge 최소 범위, host placeholder, payload fixture, evidence Apple signing, 장비, OS UI 수동 검증 전에 먼저 확정할 수 있는 계약과 검증 표면을 분리한다. -- [ ] [bridge-scope] Dart가 기대하는 EventChannel/MethodChannel 이름, macOS 최소 구현 method, no-op/fallback 경계를 정리한다. -- [ ] [host-placeholder] entitlement, signing/notarization placeholder, ignored credential 위치, macOS target resource 기준을 정리한다. -- [ ] [payload-map] macOS notification userInfo를 `NotificationOpenedEvent`와 channel/thread callback에 매핑하는 기준을 정리한다. -- [ ] [fixtures] local notification payload fixture와 click/open routing injection 시나리오를 작성한다. -- [ ] [evidence-template] permission, display, click/open routing, lifecycle, ACK/dismiss gap evidence 템플릿을 작성한다. +- [x] [bridge-scope] Dart가 기대하는 EventChannel/MethodChannel 이름, macOS 최소 구현 method, no-op/fallback 경계를 정리한다. +- [x] [host-placeholder] entitlement, signing/notarization placeholder, ignored credential 위치, macOS target resource 기준을 정리한다. +- [x] [payload-map] macOS notification userInfo를 `NotificationOpenedEvent`와 channel/thread callback에 매핑하는 기준을 정리한다. +- [x] [fixtures] local notification payload fixture와 click/open routing injection 시나리오를 작성한다. +- [x] [evidence-template] permission, display, click/open routing, lifecycle, ACK/dismiss gap evidence 템플릿을 작성한다. ## 완료 리뷰 -- 상태: 없음 -- 요청일: 없음 -- 완료 근거: 없음 +- 상태: 요청됨 +- 요청일: 2026-06-02 +- 완료 근거: + - `docs/macos-notification-test-guide.md`에 macOS bridge scope, host placeholder, userInfo mapping, fixture 사용 기준을 정리했다. + - `apps/flutter-test/macos/Fixtures/*.json`에 channel/thread open routing fixture를 추가했다. + - `docs/macos-notification-smoke-evidence-template.md`에 permission, display, click/open routing, lifecycle, ACK/dismiss/inline reply gap evidence 양식을 추가했다. - 리뷰 필요: - [ ] 사용자가 완료 결과를 확인했다 - [ ] archive 이동을 승인했다 -- 리뷰 코멘트: 없음 +- 리뷰 코멘트: 모든 기능 Task가 문서/fixture/evidence 기준으로 충족되어 완료 후보로 올린다. 실제 macOS native bridge 구현과 OS notification UI smoke는 후속 Milestone 또는 별도 plan 대상이다. ## 범위 제외 diff --git a/apps/flutter-test/README.md b/apps/flutter-test/README.md index 41491b96..41b5c2e8 100644 --- a/apps/flutter-test/README.md +++ b/apps/flutter-test/README.md @@ -85,6 +85,15 @@ Local routing fixtures live under `ios/Fixtures/*.apns`; replace 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 macOS preflight and smoke 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). + ## Verification The integration test uses the plugin's test/debug event injection path for diff --git a/apps/flutter-test/macos/Fixtures/open_channel.json b/apps/flutter-test/macos/Fixtures/open_channel.json new file mode 100644 index 00000000..c308153e --- /dev/null +++ b/apps/flutter-test/macos/Fixtures/open_channel.json @@ -0,0 +1,11 @@ +{ + "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-channel", + "post_id": "post-smoke-channel", + "userInteraction": true +} diff --git a/apps/flutter-test/macos/Fixtures/open_thread.json b/apps/flutter-test/macos/Fixtures/open_thread.json new file mode 100644 index 00000000..dee92e18 --- /dev/null +++ b/apps/flutter-test/macos/Fixtures/open_thread.json @@ -0,0 +1,11 @@ +{ + "type": "message", + "server_url": "https://example.invalid", + "server_id": "server-smoke", + "channel_id": "channel-smoke", + "root_id": "thread-smoke", + "is_crt_enabled": "true", + "ack_id": "ack-smoke-thread", + "post_id": "post-smoke-thread", + "userInteraction": true +} diff --git a/docs/macos-notification-smoke-evidence-template.md b/docs/macos-notification-smoke-evidence-template.md new file mode 100644 index 00000000..0c8609c7 --- /dev/null +++ b/docs/macos-notification-smoke-evidence-template.md @@ -0,0 +1,104 @@ +# 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: `` +- 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= +request_path= +notes= +``` + +## 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= +notification_title= +notification_body= +``` + +## 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= +app_state_after= +event_type= +userInteraction= +``` + +## 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. diff --git a/docs/macos-notification-test-guide.md b/docs/macos-notification-test-guide.md new file mode 100644 index 00000000..550af521 --- /dev/null +++ b/docs/macos-notification-test-guide.md @@ -0,0 +1,143 @@ +# macOS Notification Test Guide + +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. + +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. + +## Current Repository State + +- 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`. + +The first useful macOS smoke target is: + +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. + +## Minimum Bridge Contract + +The Dart SDK expects the macOS native side to preserve these channel names: + +| 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. diff --git a/packages/messaging_flutter/README.md b/packages/messaging_flutter/README.md index 126e3efd..015e0bf5 100644 --- a/packages/messaging_flutter/README.md +++ b/packages/messaging_flutter/README.md @@ -51,7 +51,7 @@ 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, 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) | ## Repository Layout @@ -504,6 +504,19 @@ If the local machine does not have an Android SDK or Android device/emulator, use the separate SSH-based Android test environment documented in [`docs/android-test-environment.md`](docs/android-test-environment.md). +## macOS Preflight Notes + +macOS remains a scaffold, but its preflight contract is fixed before native +implementation work starts. Dart expects the macOS plugin to register +`com.tokilabs.nexo.messaging/notifications` and +`com.tokilabs.nexo.messaging/notification_actions`, preserve the legacy +`nexo_messaging` `getPlatformVersion` compatibility channel, and map +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. + ## Test Strategy Use deterministic tests for repository-owned behavior. The example integration