<!-- task=mattermost_push_plugin_extraction/03+01,02_host_consumer plan=0 tag=REFACTOR -->

# Code Review Reference - REFACTOR

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-05-25
task=mattermost_push_plugin_extraction/03+01,02_host_consumer, plan=0, tag=REFACTOR

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/mattermost_push_plugin_extraction/03+01,02_host_consumer/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] `apps/mobile`에 `mattermost_push_plugin` path dependency를 추가하고 Dart imports/call sites를 plugin API로 전환한다. | [x] |
| [REFACTOR-2] app-owned push Dart facade/background handler와 Android native source/resource ownership을 제거한다. | [x] |
| [REFACTOR-3] host Android manifest와 Gradle dependencies를 plugin consumer 형태로 정리하고 app 검증을 실행한다. | [x] |

## 구현 체크리스트

- [x] [REFACTOR-1] `apps/mobile`에 `mattermost_push_plugin` path dependency를 추가하고 Dart imports/call sites를 plugin API로 전환한다.
- [x] [REFACTOR-2] app-owned push Dart facade/background handler와 Android native source/resource ownership을 제거한다.
- [x] [REFACTOR-3] host Android manifest와 Gradle dependencies를 plugin consumer 형태로 정리하고 app 검증을 실행한다.
- [x] 중간 검증과 최종 검증 명령을 모두 실행하고 실제 출력을 `CODE_REVIEW-cloud-G07.md`에 붙여 넣는다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/mattermost_push_plugin_extraction/03+01,02_host_consumer/`를 `agent-task/archive/YYYY/MM/mattermost_push_plugin_extraction/03+01,02_host_consumer/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/mattermost_push_plugin_extraction/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.

## 계획 대비 변경 사항

- 계획에는 명시되지 않았지만 host `apps/mobile/android/app/src/main/res/` 아래 알림 아이콘(`ic_notification.png`, `ic_notif_action_reply.png`)이 plugin `android/src/main/res/`에도 동일하게 존재하여 plugin이 ownership을 가져갔다. host의 동일 자원은 launcher 아이콘(`ic_launcher`, `ic_launcher_round`)이 아니어서 더 이상 host UI가 참조하지 않으므로 함께 제거했다. 계획의 "Remove app notification icons/resources only if they are no longer used by host launcher" 조건과 일치한다.
- 계획의 `build.gradle.kts` "Before/After" 예시는 `kotlin-kapt`를 plugins 블록에서 제거하지 않았지만, host app 코드에서 `kapt` 사용처(Room compiler)가 모두 제거되어 plugin id가 죽은 코드가 되므로 `kotlin-kapt`도 함께 삭제했다.
- `dart format --output=none --set-exit-if-changed lib test` 검증이 host가 손대지 않은 `lib/models/project_workspace.dart`, `lib/screens/workspace_home_page.dart`, `test/widget_test.dart`의 기존 포매팅 차이로 실패하는 상태였다. 검증 명령은 계약상 고정이고 dart format은 순수 whitespace 변경이므로 해당 파일들도 함께 포매팅하여 검증을 통과시켰다. 행 의미를 바꾸는 변경은 없다.
- 최종 검증 중 `flutter build apk --debug`는 현재 환경에 Android SDK가 설치되지 않아 (`[!] No Android SDK found.`) 로컬에서 실행 불가능하다. Build lane이 `cloud-G07`로 지정된 만큼, 실제 APK 빌드 검증은 cloud build 환경에서 수행되어야 한다. 명령 자체는 대체하지 않고 stdout/stderr를 그대로 기록한다.

## 주요 설계 결정

- `MattermostPushPlugin` API는 `instance` 정적 싱글톤이라 host 측 `final _pushService = MattermostPushPlugin.instance;`만 두면 충분하다. 기존 `PushNotificationService()` constructor 호출이 그대로 매핑된다.
- host `main.dart`에서 `FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler)` 호출과 host-owned background handler 파일(`push_notification_background.dart`)을 제거했다. Android 측 FCM 수신은 plugin의 native `MattermostFirebaseMessagingService`가 전담하고, host에는 별도 background 처리 책임이 남지 않는다.
- host `MainActivity.kt`는 Flutter window/system UI 책임만 남기고 EventChannel/MethodChannel 등록 및 intent extras forwarding 로직을 모두 제거했다. plugin이 `ActivityAware.onAttachedToActivity`에서 intent extras를 처리하고 `onAttachedToEngine`에서 channel을 등록한다.
- host `MainApplication.kt`와 `AppLifecycleTracker.kt`는 push 책임만 가지고 있어 완전 삭제했다. AndroidManifest에서 `android:name=".MainApplication"`을 제거하여 default `Application`을 사용한다. plugin의 `onAttachedToEngine`이 `DatabaseHelper.init`, `CustomPushNotificationHelper.createNotificationChannels`, `AppLifecycleTracker` 등록을 모두 담당한다.
- host AndroidManifest에서 `POST_NOTIFICATIONS`, `VIBRATE`, `RECEIVE_BOOT_COMPLETED` 권한과 `FlutterFirebaseMessaging*` `tools:node="remove"` override를 제거했다. 모두 plugin manifest에 동일하게 선언되어 있으므로 manifest merge로 들어온다. host는 `INTERNET`만 유지하여 `mattermost_auth_service.dart`의 HTTP 로그인 흐름을 지원한다.
- host `build.gradle.kts`에서 `firebase-bom`, `firebase-messaging`, `jjwt-*`, `okhttp`, `kotlinx-coroutines-android`, `androidx.room:*`, `kotlin-kapt`를 모두 제거했다. plugin이 이들 의존성을 자체 `android/build.gradle`에서 owning한다. host는 `coreLibraryDesugaring`만 유지하며 `com.google.gms.google-services` plugin은 Firebase project config (host 소유) 적용을 위해 그대로 둔다.

## 리뷰어를 위한 체크포인트

- `01_plugin_api` and `02+01_android_native` complete logs exist before implementation.
- `apps/mobile` imports plugin API, not local `push_notification_service.dart`.
- App Android native push service/helper files are removed or reduced to host-only code.
- Host keeps Firebase project config but no longer owns Mattermost push native dependencies.

## 검증 결과

_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.

### REFACTOR-1 중간 검증

```bash
$ cd /config/workspace/nomadcode/apps/mobile && flutter pub get
Resolving dependencies...
Downloading packages...
  _flutterfire_internals 1.3.59 (1.3.71 available)
  async 2.13.0 (2.13.1 available)
  cupertino_icons 1.0.8 (1.0.9 available)
  dbus 0.7.12 (0.7.13 available)
  firebase_core 3.15.2 (4.9.0 available)
  firebase_core_platform_interface 6.0.2 (7.0.1 available)
  firebase_core_web 2.24.1 (3.7.0 available)
  firebase_messaging 15.2.10 (16.2.2 available)
  firebase_messaging_platform_interface 4.6.10 (4.7.11 available)
  firebase_messaging_web 3.10.10 (4.1.7 available)
  flutter_local_notifications 18.0.1 (21.0.0 available)
  flutter_local_notifications_linux 5.0.0 (8.0.0 available)
  flutter_local_notifications_platform_interface 8.0.0 (11.0.0 available)
  matcher 0.12.19 (0.12.20 available)
  meta 1.17.0 (1.18.2 available)
  test_api 0.7.10 (0.7.12 available)
  timezone 0.10.1 (0.11.0 available)
  vector_math 2.2.0 (2.3.0 available)
  vm_service 15.0.2 (15.2.0 available)
  xml 6.6.1 (7.0.1 available)
Got dependencies!
20 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
$ echo "exit=$?"
exit=0
```

`mattermost_push_plugin` path dependency 해결 및 lockfile 갱신 완료. `firebase_messaging`은 host 직접 의존성에서 제거되었으나 plugin이 transitive로 보유하므로 `pub` outdated 리스트에는 그대로 나타난다.

### REFACTOR-2 중간 검증

```bash
$ cd /config/workspace/nomadcode && rg --sort path "PushNotificationService|push_notification_service" apps/mobile/lib apps/mobile/test
$ echo "exit=$?"
exit=1
```

매치 없음, exit code 1. host에서 모든 local push facade 참조가 제거됨.

### REFACTOR-3 중간 검증

```bash
$ cd /config/workspace/nomadcode/apps/mobile && flutter build apk --debug
Resolving dependencies...
Downloading packages...
  _flutterfire_internals 1.3.59 (1.3.71 available)
  async 2.13.0 (2.13.1 available)
  cupertino_icons 1.0.8 (1.0.9 available)
  dbus 0.7.12 (0.7.13 available)
  firebase_core 3.15.2 (4.9.0 available)
  firebase_core_platform_interface 6.0.2 (7.0.1 available)
  firebase_core_web 2.24.1 (3.7.0 available)
  firebase_messaging 15.2.10 (16.2.2 available)
  firebase_messaging_platform_interface 4.6.10 (4.7.11 available)
  firebase_messaging_web 3.10.10 (4.1.7 available)
  flutter_local_notifications 18.0.1 (21.0.0 available)
  flutter_local_notifications_linux 5.0.0 (8.0.0 available)
  flutter_local_notifications_platform_interface 8.0.0 (11.0.0 available)
  matcher 0.12.19 (0.12.20 available)
  meta 1.17.0 (1.18.2 available)
  test_api 0.7.10 (0.7.12 available)
  timezone 0.10.1 (0.11.0 available)
  vector_math 2.2.0 (2.3.0 available)
  vm_service 15.0.2 (15.2.0 available)
  xml 6.6.1 (7.0.1 available)
Got dependencies!
20 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
[!] No Android SDK found. Try setting the ANDROID_HOME environment variable.
$ echo "exit=$?"
exit=1
```

현재 환경에 Android SDK 미설치로 로컬 실행 불가. Build lane이 `cloud-G07`이므로 실제 APK 빌드는 cloud build 환경에서 수행되어야 한다. (상세 내용은 위 `계획 대비 변경 사항` 참고.)

### 최종 검증

```bash
$ cd /config/workspace/nomadcode/apps/mobile && flutter pub get
Resolving dependencies...
Downloading packages...
  _flutterfire_internals 1.3.59 (1.3.71 available)
  ... (REFACTOR-1 중간 검증과 동일한 outdated 목록) ...
  xml 6.6.1 (7.0.1 available)
Got dependencies!
20 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
$ echo "exit=$?"
exit=0

$ cd /config/workspace/nomadcode/apps/mobile && dart format --output=none --set-exit-if-changed lib test
Formatted 6 files (0 changed) in 0.04 seconds.
$ echo "exit=$?"
exit=0

$ cd /config/workspace/nomadcode/apps/mobile && flutter analyze --no-fatal-infos
Analyzing mobile...

   info • Don't invoke 'print' in production code • lib/main.dart:39:5 • avoid_print
   info • Don't invoke 'print' in production code • lib/main.dart:63:11 • avoid_print
   info • Don't invoke 'print' in production code • lib/main.dart:66:11 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:25:7 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:32:5 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:38:7 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:41:7 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:48:7 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:52:5 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:96:5 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:114:9 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:123:9 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:125:9 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:128:7 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:145:7 • avoid_print
   info • Don't invoke 'print' in production code • lib/services/mattermost_auth_service.dart:147:7 • avoid_print

16 issues found. (ran in 2.0s)
$ echo "exit=$?"
exit=0
```

`avoid_print` info만 발생하며 `--no-fatal-infos`로 exit 0. 모두 기존 host 코드의 print 호출이며 이번 작업에서 신규 추가된 것은 없다.

```bash
$ cd /config/workspace/nomadcode/apps/mobile && flutter test
Resolving dependencies...
... (outdated 목록 동일) ...
Got dependencies!
20 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
00:00 +0: loading /config/workspace/nomadcode/apps/mobile/test/widget_test.dart
00:00 +0: App renders console dashboard title
00:00 +1: WorkspaceHomePage triggers launcher callback on button tap
00:00 +2: All tests passed!
$ echo "exit=$?"
exit=0

$ cd /config/workspace/nomadcode/apps/mobile && flutter build apk --debug
Resolving dependencies...
... (outdated 목록 동일) ...
Got dependencies!
20 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
[!] No Android SDK found. Try setting the ANDROID_HOME environment variable.
$ echo "exit=$?"
exit=1
```

`flutter build apk --debug`는 Android SDK 부재로 로컬에서 실행 불가. `cloud-G07` build lane이 실제 APK 빌드를 담당한다. (`계획 대비 변경 사항` 참고.)

```bash
$ cd /config/workspace/nomadcode && rg --sort path "PushNotificationService|push_notification_service|MattermostFirebaseMessagingService|NotificationReplyBroadcastReceiver|NotificationDismissService|ReceiptDelivery|PushNotificationDataHelper" apps/mobile/lib apps/mobile/android/app/src/main/java apps/mobile/test
$ echo "exit=$?"
exit=1
```

매치 없음, exit code 1. host에 push 관련 잔존 심볼 없음.

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

## 섹션 소유권

| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
| 구현 항목별 완료 여부 (item names) | Fixed at stub creation | Implementing agent checks `[ ]` to `[x]` only |
| 구현 체크리스트 (item text/order) | Fixed at stub creation from plan | Implementing agent checks `[ ]` to `[x]` only |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder text with actual content |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan |
| 검증 결과 (section headings + commands) | Fixed at stub creation | Implementing agent fills in command output only |

## 코드리뷰 결과

- `종합 판정`: FAIL
- `차원별 평가`:
  - Correctness: Fail
  - Completeness: Fail
  - Test coverage: Warn
  - API contract: Fail
  - Code quality: Warn
  - Plan deviation: Fail
  - Verification trust: Fail
- `발견된 문제`:
  - Required — `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostPushPlugin.kt:85`, `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/MainActivity.kt:7`: host `MainActivity`에서 notification extras forwarding을 제거했지만 plugin은 `onAttachedToActivity`의 현재 `activity.intent`만 즉시 `PushNotificationEvents.send`로 보냅니다. `launchMode="singleTop"` 재진입의 `onNewIntent`를 듣지 않고, EventChannel `onListen` 전 payload도 보존하지 않아 notification tap/open event가 drop될 수 있습니다. plugin에 intent payload 처리 helper를 두고 `ActivityPluginBinding.addOnNewIntentListener`/remove를 등록하며, `PushNotificationEvents`가 sink 준비 전 이벤트를 큐잉 후 flush하도록 고쳐야 합니다.
  - Required — `apps/mobile/pubspec.yaml:49`: host push facade/background/native ownership은 제거했지만 `flutter_local_notifications` direct dependency와 generated registrant가 남아 있습니다. 이 패키지는 삭제된 host foreground notification 구현의 소유물이므로 `pubspec.yaml`에서 제거하고 `flutter pub get`으로 lockfile 및 generated registrant를 갱신한 뒤, app 쪽에 `flutter_local_notifications` 참조가 남지 않음을 확인해야 합니다.
  - Required — `agent-task/mattermost_push_plugin_extraction/03+01,02_host_consumer/CODE_REVIEW-cloud-G07.md:235`: 최종 필수 검증인 `flutter build apk --debug`가 `[!] No Android SDK found`로 exit 1입니다. 계획의 완료 기준은 Android debug build 성공이므로, SDK가 있는 환경에서 같은 명령을 재실행해 성공 stdout/stderr를 기록해야 verification trust가 회복됩니다.
- `다음 단계`: FAIL: Required 문제를 수정하는 후속 `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 재리뷰한다.
