<!-- task=mattermost_push_plugin_extraction/01_plugin_api plan=0 tag=API -->

# Mattermost Push Plugin API Scaffold Plan

## 이 파일을 읽는 구현 에이전트에게

구현을 끝내기 전 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 검증 명령을 실제로 실행하고 stdout/stderr를 붙여 넣은 뒤 active 파일을 그대로 둔 채 review ready를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.

## 배경

현재 Mattermost push Dart facade가 `apps/mobile/lib/services/push_notification_service.dart`에 있어 앱이 push plugin의 public API까지 직접 소유한다. 활성 Milestone은 이 facade를 `../mattermost-push-plugin`의 `mattermost_push_plugin` package API로 승격하고 앱은 consumer로 남기는 것을 요구한다. 첫 작업은 빈 sibling repo를 Flutter plugin으로 만들고, 후속 native/app 전환이 의존할 안정적인 Dart surface와 기본 platform channel 계약을 확정하는 것이다.

## 분석 결과

### 읽은 파일

- `agent-ops/roadmap/current.md`
- `agent-ops/roadmap/milestones/mattermost-push-plugin-extraction.md`
- `agent-ops/rules/project/domain/workspace-ops/rules.md`
- `agent-ops/rules/project/domain/mobile/rules.md`
- `apps/mobile/pubspec.yaml`
- `apps/mobile/analysis_options.yaml`
- `apps/mobile/lib/main.dart`
- `apps/mobile/lib/services/mattermost_auth_service.dart`
- `apps/mobile/lib/services/push_notification_background.dart`
- `apps/mobile/lib/services/push_notification_service.dart`
- `apps/mobile/test/widget_test.dart`
- `apps/mobile/android/app/build.gradle.kts`
- `apps/mobile/android/app/src/main/AndroidManifest.xml`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/MainActivity.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/MainApplication.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/MattermostFirebaseMessagingService.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/NotificationDismissService.java`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/NotificationReplyBroadcastReceiver.java`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/ReceiptDelivery.java`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/BitmapCache.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/CustomPushNotificationHelper.java`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/DatabaseHelper.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/Network.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/NotificationHelper.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/PushNotificationDataHelper.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/db/GlobalDatabase.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/db/GlobalEntities.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/db/MattermostDatabase.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/db/ServerEntities.kt`

### 테스트 커버리지 공백

- Plugin package scaffold와 public Dart API: 기존 테스트 없음. 새 plugin package에 unit test를 작성한다.
- Event stream/opened event parsing: 기존 `apps/mobile/test/widget_test.dart`는 dashboard rendering만 검증한다. Plugin test에서 map parsing과 callback dispatch를 검증한다.
- Native MethodChannel method 이름 계약: 기존 테스트 없음. 이 계획에서는 Dart side method call surface만 test하고 Android native registration은 `02+01_android_native`에서 build로 검증한다.

### 심볼 참조

- `PushNotificationService`: `apps/mobile/lib/main.dart:13`, `apps/mobile/lib/services/mattermost_auth_service.dart:9`, definition `apps/mobile/lib/services/push_notification_service.dart:31`.
- `NotificationOpenedEvent`: definition and stream only in `apps/mobile/lib/services/push_notification_service.dart:15`, `51`.
- Channel names: `com.tokilabs.mattermost/notifications` in `push_notification_service.dart:37` and `MainActivity.kt:22`; `com.tokilabs.mattermost/notification_actions` in `push_notification_service.dart:39` and `MainActivity.kt:23`.
- No symbols are removed in this subtask. Call-site migration is owned by `03+01,02_host_consumer`.

### 분할 판단

- Split decision policy was evaluated before choosing plan files.
- Shared task group: `mattermost_push_plugin_extraction`.
- `01_plugin_api`: independent foundation. Creates plugin scaffold and Dart API.
- `02+01_android_native`: depends on `01_plugin_api`; moves Android service/helper/resource/dependency implementation behind the plugin.
- `03+01,02_host_consumer`: depends on `01_plugin_api` and `02+01_android_native`; updates `apps/mobile` to consume the plugin.
- `04+02_risk_hardening`: depends on `02+01_android_native`; handles DB singleton/stub and manifest conflict risks after native code lives in the plugin.
- `05+03,04_docs_verify`: depends on `03+01,02_host_consumer` and `04+02_risk_hardening`; writes docs and final integration verification.

### 범위 결정 근거

- This subtask does not modify `apps/mobile` call sites; leaving them unchanged avoids breaking the app before native plugin code exists.
- This subtask does not move Android native files; Android registration, manifest, resources, and Gradle dependencies are owned by `02+01_android_native`.
- This subtask does not implement Room DAO stubs; storage behavior is owned by `04+02_risk_hardening`.
- This subtask does not add server-side Mattermost push gateway work because the Milestone excludes it.

### 빌드 등급

- Build lane: `cloud-G07`; review lane: `cloud-G07`. Public package API plus weak existing tests and cross-repo path make this broader than a local-only refactor, but this first step has no native storage/auth move.

## 구현 체크리스트

- [ ] [API-1] `../mattermost-push-plugin`을 `mattermost_push_plugin` Flutter plugin으로 scaffold한다.
- [ ] [API-2] 기존 Dart facade와 호환되는 public API, model, callback, channel constants를 plugin package에 정의한다.
- [ ] [API-3] plugin Dart API unit tests를 작성하고 scaffold 검증을 실행한다.
- [ ] 중간 검증과 최종 검증 명령을 모두 실행하고 실제 출력을 `CODE_REVIEW-cloud-G07.md`에 붙여 넣는다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Plugin Scaffold

#### 문제

`../mattermost-push-plugin`은 빈 git repo이며, Milestone의 `[plugin-scaffold]` 완료 기준인 Flutter plugin 구조가 없다. 현재 public facade는 앱 파일 `apps/mobile/lib/services/push_notification_service.dart:31`에 있다.

Before:

```text
../mattermost-push-plugin/
  .git/
```

After:

```text
../mattermost-push-plugin/
  pubspec.yaml
  lib/mattermost_push_plugin.dart
  android/
  ios/
  macos/
  test/
```

#### 해결 방법

빈 repo 안에서 Flutter plugin scaffold를 생성한다.

```bash
cd /config/workspace/mattermost-push-plugin
flutter create --template=plugin --platforms=android,ios,macos --org com.tokilabs --project-name mattermost_push_plugin .
```

`pubspec.yaml`은 `name: mattermost_push_plugin`, `publish_to: 'none'`을 유지하고 Android-first임을 설명한다. iOS/macOS는 generated no-op scaffold를 남기고 native push full implementation은 후속으로 둔다.

#### 수정 파일 및 체크리스트

- [ ] `../mattermost-push-plugin/pubspec.yaml` package name, description, `publish_to: 'none'`, SDK range 확인
- [ ] `../mattermost-push-plugin/lib/mattermost_push_plugin.dart` public export entry로 정리
- [ ] `../mattermost-push-plugin/README.md`는 이 subtask에서는 scaffold 수준 설명만 작성하고 상세 통합 문서는 `05+03,04_docs_verify`로 남김
- [ ] Generated sample/test 중 package name이 `mattermost_push_plugin`과 일치하는지 확인

#### 테스트 작성

작성: scaffold smoke용 기본 plugin test를 유지하되, API test는 `[API-3]`에서 확장한다.

#### 중간 검증

```bash
cd /config/workspace/mattermost-push-plugin && flutter pub get
```

Expected: dependency resolution succeeds.

### [API-2] Public Dart API Surface

#### 문제

앱 facade가 `FirebaseMessaging`, `EventChannel`, `MethodChannel`을 직접 소유한다.

Before (`apps/mobile/lib/services/push_notification_service.dart:31`):

```dart
class PushNotificationService {
  static final PushNotificationService _instance =
      PushNotificationService._internal();
  factory PushNotificationService() => _instance;
  PushNotificationService._internal();

  static const EventChannel _notificationChannel =
      EventChannel('com.tokilabs.mattermost/notifications');
  static const MethodChannel _actionChannel =
      MethodChannel('com.tokilabs.mattermost/notification_actions');
```

After (`../mattermost-push-plugin/lib/mattermost_push_plugin.dart`):

```dart
library mattermost_push_plugin;

export 'src/mattermost_push_plugin.dart';
export 'src/notification_opened_event.dart';
export 'src/push_notification_type.dart';
```

After (`../mattermost-push-plugin/lib/src/mattermost_push_plugin.dart`):

```dart
class MattermostPushPlugin {
  MattermostPushPlugin._();
  static final MattermostPushPlugin instance = MattermostPushPlugin._();

  Stream<Map<String, dynamic>> get onNotification;
  Stream<NotificationOpenedEvent> get onNotificationOpened;

  Future<void> initialize();
  Future<void> setAuthToken(String serverUrl, String token, {String? identifier});
  Future<void> clearAuthToken(String serverUrl);
  Future<void> setSigningKey(String serverUrl, String signingKey);
  Future<String?> getDeviceToken();
}
```

#### 해결 방법

Move the Dart behavior from the app facade into plugin `lib/src/` files with stable names. Keep channel names unchanged for compatibility: `com.tokilabs.mattermost/notifications` and `com.tokilabs.mattermost/notification_actions`. Keep callback fields or expose setter methods compatible with current app usage:

```dart
void Function(String deviceToken)? onDeviceTokenReady;
void Function(String serverUrl, String channelId)? onNavigateToChannel;
void Function(String serverUrl, String rootId)? onNavigateToThread;
```

Do not import app code from the plugin. The plugin may depend on `firebase_messaging` for Dart token refresh/request permission if needed by the existing facade behavior.

#### 수정 파일 및 체크리스트

- [ ] `../mattermost-push-plugin/lib/mattermost_push_plugin.dart` export entry 작성
- [ ] `../mattermost-push-plugin/lib/src/mattermost_push_plugin.dart` singleton/API/channel implementation 작성
- [ ] `../mattermost-push-plugin/lib/src/notification_opened_event.dart` value object 작성
- [ ] `../mattermost-push-plugin/lib/src/push_notification_type.dart` constants 작성
- [ ] `../mattermost-push-plugin/pubspec.yaml`에 필요한 `firebase_messaging` dependency 추가 여부 결정

#### 테스트 작성

작성: `../mattermost-push-plugin/test/mattermost_push_plugin_test.dart`.

- `NotificationOpenedEvent` parses `server_url`, `channel_id`, `root_id`, `is_crt_enabled`.
- `token_refresh` event invokes `onDeviceTokenReady`.
- `message` event with `userInteraction: true` routes to opened event logic.

#### 중간 검증

```bash
cd /config/workspace/mattermost-push-plugin && flutter test
```

Expected: plugin Dart tests pass.

### [API-3] API Verification

#### 문제

Existing `apps/mobile/test/widget_test.dart` only checks UI text and launcher callback, so it cannot protect the new plugin API. The plugin needs its own API tests before native code depends on it.

#### 해결 방법

Use testable helpers or injectable channel wrappers inside the plugin so event parsing can be tested without a real Android engine. Avoid requiring Firebase runtime initialization in unit tests.

After:

```dart
@visibleForTesting
void handleNativeEvent(Map<String, dynamic> data) {
  // shared parser used by EventChannel listener and unit tests
}
```

#### 수정 파일 및 체크리스트

- [ ] `../mattermost-push-plugin/lib/src/mattermost_push_plugin.dart`에 testable event handler 추가
- [ ] `../mattermost-push-plugin/test/mattermost_push_plugin_test.dart`에 event parsing tests 추가
- [ ] `../mattermost-push-plugin/analysis_options.yaml` lint baseline 확인

#### 테스트 작성

작성: `../mattermost-push-plugin/test/mattermost_push_plugin_test.dart`.

#### 중간 검증

```bash
cd /config/workspace/mattermost-push-plugin && dart format --output=none --set-exit-if-changed lib test
```

Expected: no formatting diff.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `../mattermost-push-plugin/pubspec.yaml` | API-1, API-2 |
| `../mattermost-push-plugin/lib/mattermost_push_plugin.dart` | API-2 |
| `../mattermost-push-plugin/lib/src/mattermost_push_plugin.dart` | API-2, API-3 |
| `../mattermost-push-plugin/lib/src/notification_opened_event.dart` | API-2, API-3 |
| `../mattermost-push-plugin/lib/src/push_notification_type.dart` | API-2 |
| `../mattermost-push-plugin/test/mattermost_push_plugin_test.dart` | API-3 |
| `../mattermost-push-plugin/README.md` | API-1 |

## 최종 검증

```bash
cd /config/workspace/mattermost-push-plugin && flutter pub get
cd /config/workspace/mattermost-push-plugin && dart format --output=none --set-exit-if-changed lib test
cd /config/workspace/mattermost-push-plugin && flutter analyze --no-fatal-infos
cd /config/workspace/mattermost-push-plugin && flutter test
```

Expected: all commands succeed. Cached test output is acceptable for Flutter tests in this subtask because no native build is involved.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
