diff --git a/agent-roadmap/phase/messaging-runtime/milestones/linux-notification-preflight.md b/agent-roadmap/archive/phase/messaging-runtime/milestones/linux-notification-preflight.md similarity index 67% rename from agent-roadmap/phase/messaging-runtime/milestones/linux-notification-preflight.md rename to agent-roadmap/archive/phase/messaging-runtime/milestones/linux-notification-preflight.md index eef06bc5..08545d86 100644 --- a/agent-roadmap/phase/messaging-runtime/milestones/linux-notification-preflight.md +++ b/agent-roadmap/archive/phase/messaging-runtime/milestones/linux-notification-preflight.md @@ -12,7 +12,7 @@ Linux plugin 등록, notification daemon 연동 후보, bridge 계약, fixture, ## 상태 -[계획] +[완료] ## 승격 조건 @@ -38,21 +38,24 @@ Linux plugin 등록, notification daemon 연동 후보, bridge 계약, fixture, Linux 장비, 배포판, desktop environment 결정을 기다리지 않고 먼저 확정할 수 있는 계약과 검증 표면을 분리한다. -- [ ] [platform-contract] Linux plugin 등록 방식, Dart channel 이름, host scaffold 필요 범위를 정리한다. -- [ ] [bridge-scope] Linux notification daemon click/open event를 `NotificationOpenedEvent`와 channel/thread callback에 연결하는 최소 bridge 계약을 정리한다. -- [ ] [api-matrix] D-Bus notification daemon, desktop portal, package/runtime dependency 후보와 제약을 비교한다. -- [ ] [fixtures] local/debug notification payload fixture와 opened-routing injection 시나리오를 작성한다. -- [ ] [evidence-template] display, click/open routing, lifecycle, ACK/dismiss gap evidence 템플릿을 작성한다. +- [x] [platform-contract] Linux plugin 등록 방식, Dart channel 이름, host scaffold 필요 범위를 정리한다. +- [x] [bridge-scope] Linux notification daemon click/open event를 `NotificationOpenedEvent`와 channel/thread callback에 연결하는 최소 bridge 계약을 정리한다. +- [x] [api-matrix] D-Bus notification daemon, desktop portal, package/runtime dependency 후보와 제약을 비교한다. +- [x] [fixtures] local/debug notification payload fixture와 opened-routing injection 시나리오를 작성한다. +- [x] [evidence-template] display, click/open routing, lifecycle, ACK/dismiss gap evidence 템플릿을 작성한다. ## 완료 리뷰 -- 상태: 없음 -- 요청일: 없음 -- 완료 근거: 없음 +- 상태: 승인됨 +- 요청일: 2026-06-02 +- 완료 근거: + - `docs/linux-notification-test-guide.md`에 Linux platform registration, bridge scope, API matrix, runtime dependency boundary, payload mapping, fixture 사용 기준을 정리했다. + - `apps/flutter-test/linux/Fixtures/*.json`에 channel/thread open routing fixture를 추가했다. + - `docs/linux-notification-smoke-evidence-template.md`에 display, click/open routing, lifecycle, ACK/dismiss/inline reply gap evidence 양식을 추가했다. - 리뷰 필요: - - [ ] 사용자가 완료 결과를 확인했다 - - [ ] archive 이동을 승인했다 -- 리뷰 코멘트: 없음 + - [x] 사용자가 완료 결과를 확인했다 + - [x] archive 이동을 승인했다 +- 리뷰 코멘트: 모든 기능 Task가 문서/fixture/evidence 기준으로 충족되어 완료 승인 및 archive 대상으로 전환한다. 실제 Linux plugin scaffold, platform registration, native bridge 구현과 OS notification UI smoke는 후속 Milestone 또는 별도 plan 대상이다. ## 범위 제외 diff --git a/agent-roadmap/phase/messaging-runtime/milestones/windows-notification-preflight.md b/agent-roadmap/archive/phase/messaging-runtime/milestones/windows-notification-preflight.md similarity index 90% rename from agent-roadmap/phase/messaging-runtime/milestones/windows-notification-preflight.md rename to agent-roadmap/archive/phase/messaging-runtime/milestones/windows-notification-preflight.md index f83f034b..1c5be82c 100644 --- a/agent-roadmap/phase/messaging-runtime/milestones/windows-notification-preflight.md +++ b/agent-roadmap/archive/phase/messaging-runtime/milestones/windows-notification-preflight.md @@ -12,7 +12,7 @@ Windows plugin 등록, native notification API 후보, bridge 계약, fixture, e ## 상태 -[검토중] +[완료] ## 승격 조건 @@ -46,16 +46,16 @@ Windows 장비, 계정, packaging 결정을 기다리지 않고 먼저 확정할 ## 완료 리뷰 -- 상태: 요청됨 +- 상태: 승인됨 - 요청일: 2026-06-02 - 완료 근거: - `docs/windows-notification-test-guide.md`에 Windows platform registration, bridge scope, API matrix, payload mapping, fixture 사용 기준을 정리했다. - `apps/flutter-test/windows/Fixtures/*.json`에 channel/thread open routing fixture를 추가했다. - `docs/windows-notification-smoke-evidence-template.md`에 display, click/open routing, lifecycle, ACK/dismiss/inline reply gap evidence 양식을 추가했다. - 리뷰 필요: - - [ ] 사용자가 완료 결과를 확인했다 - - [ ] archive 이동을 승인했다 -- 리뷰 코멘트: 모든 기능 Task가 문서/fixture/evidence 기준으로 충족되어 완료 후보로 올린다. 실제 Windows plugin scaffold, platform registration, native bridge 구현과 OS notification UI smoke는 후속 Milestone 또는 별도 plan 대상이다. + - [x] 사용자가 완료 결과를 확인했다 + - [x] archive 이동을 승인했다 +- 리뷰 코멘트: 모든 기능 Task가 문서/fixture/evidence 기준으로 충족되어 완료 승인 및 archive 대상으로 전환한다. 실제 Windows plugin scaffold, platform registration, native bridge 구현과 OS notification UI smoke는 후속 Milestone 또는 별도 plan 대상이다. ## 범위 제외 diff --git a/agent-roadmap/current.md b/agent-roadmap/current.md index 307f0619..d792dc91 100644 --- a/agent-roadmap/current.md +++ b/agent-roadmap/current.md @@ -7,9 +7,9 @@ ## 활성 Milestone -- [검토중] Windows 알림 선행 정리 +- [계획] iOS 알림 테스트 - Phase: `agent-roadmap/phase/messaging-runtime/PHASE.md` - - 경로: `agent-roadmap/phase/messaging-runtime/milestones/windows-notification-preflight.md` + - 경로: `agent-roadmap/phase/messaging-runtime/milestones/ios-notification-test.md` ## 선택 규칙 diff --git a/agent-roadmap/phase/messaging-runtime/PHASE.md b/agent-roadmap/phase/messaging-runtime/PHASE.md index a83048c2..2ee11829 100644 --- a/agent-roadmap/phase/messaging-runtime/PHASE.md +++ b/agent-roadmap/phase/messaging-runtime/PHASE.md @@ -28,12 +28,12 @@ Mattermost server/webapp/push-proxy는 upstream-followable runtime으로 두고, - [완료] macOS 알림 선행 정리 - 경로: `agent-roadmap/archive/phase/messaging-runtime/milestones/macos-notification-preflight.md` - 요약: Apple signing/장비 없이 macOS native bridge 최소 범위, host placeholder, fixture, evidence 기준을 먼저 정리한다. -- [검토중] Windows 알림 선행 정리 - - 경로: `agent-roadmap/phase/messaging-runtime/milestones/windows-notification-preflight.md` - - 요약: Windows 장비/packaging 결정 없이 plugin 등록, native API 후보, bridge 계약, fixture, evidence 기준을 먼저 정리한다. -- [계획] Linux 알림 선행 정리 - - 경로: `agent-roadmap/phase/messaging-runtime/milestones/linux-notification-preflight.md` - - 요약: Linux 배포판/desktop environment 결정 없이 plugin 등록, notification daemon 후보, bridge 계약, fixture, evidence 기준을 먼저 정리한다. +- [완료] Windows 알림 선행 정리 + - 경로: `agent-roadmap/archive/phase/messaging-runtime/milestones/windows-notification-preflight.md` + - 요약: Windows 장비/packaging 결정 없이 plugin 등록, native API 후보, bridge 계약, fixture, evidence 기준을 먼저 정리했다. +- [완료] Linux 알림 선행 정리 + - 경로: `agent-roadmap/archive/phase/messaging-runtime/milestones/linux-notification-preflight.md` + - 요약: Linux 배포판/desktop environment 결정 없이 plugin 등록, notification daemon 후보, bridge 계약, fixture, evidence 기준을 먼저 정리했다. - [계획] iOS 알림 테스트 - 경로: `agent-roadmap/phase/messaging-runtime/milestones/ios-notification-test.md` - 요약: 선행 정리 후 iOS/APNs 권한, token, open routing, ACK/dismiss 계약을 실제 설정과 smoke 기준으로 검증한다. diff --git a/apps/flutter-test/linux/Fixtures/open_channel.json b/apps/flutter-test/linux/Fixtures/open_channel.json new file mode 100644 index 00000000..c308153e --- /dev/null +++ b/apps/flutter-test/linux/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/linux/Fixtures/open_thread.json b/apps/flutter-test/linux/Fixtures/open_thread.json new file mode 100644 index 00000000..dee92e18 --- /dev/null +++ b/apps/flutter-test/linux/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/apps/flutter-test/linux/flutter/generated_plugin_registrant.cc b/apps/flutter-test/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/apps/flutter-test/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/apps/flutter-test/linux/flutter/generated_plugin_registrant.h b/apps/flutter-test/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/apps/flutter-test/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/apps/flutter-test/linux/flutter/generated_plugins.cmake b/apps/flutter-test/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..2e1de87a --- /dev/null +++ b/apps/flutter-test/linux/flutter/generated_plugins.cmake @@ -0,0 +1,23 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/docs/linux-notification-smoke-evidence-template.md b/docs/linux-notification-smoke-evidence-template.md new file mode 100644 index 00000000..1cc10765 --- /dev/null +++ b/docs/linux-notification-smoke-evidence-template.md @@ -0,0 +1,109 @@ +# 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= +desktop_environment= +session_type= +notification_daemon= +portal_status= +``` + +## 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= +notification_title= +notification_body= +action_data_present= +``` + +## 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= +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 + +- [ ] 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. diff --git a/docs/linux-notification-test-guide.md b/docs/linux-notification-test-guide.md new file mode 100644 index 00000000..d6760983 --- /dev/null +++ b/docs/linux-notification-test-guide.md @@ -0,0 +1,169 @@ +# Linux Notification Test Guide + +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. + +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. + +## Current Repository State + +- `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`. + +The first useful Linux smoke target is: + +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. + +## Platform Registration Contract + +Do not add a partial Linux platform entry without the matching source and host +scaffold. A future implementation plan should update these together: + +```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.