# PLAN - CLIENT ## 이 파일을 읽는 구현 에이전트에게 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 구현이 막히면 active review stub의 `사용자 리뷰 요청`에 정확한 결정/근거/실행 명령을 기록하고 멈춘다. 후속 에이전트가 재실행으로 메울 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다. ## 배경 Flutter app bootstrap currently initializes Firebase and Mattermost push before rendering the operator shell. The current Milestone requires the operator shell to start without push/Firebase/Mattermost readiness, while push behavior remains a later Milestone. This plan separates core shell bootstrap from optional push integration without changing dashboard UI or generated contracts. ## 사용자 리뷰 요청 흐름 구현 중 사용자 결정이 필요한 경우 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다. code-review가 검증한 경우에만 `USER_REVIEW.md`를 작성한다. ## Roadmap Targets - Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` - Task ids: - `client-bootstrap`: Flutter operator shell이 push/Firebase/Mattermost 초기화 없이도 시작 가능한 경계로 분리되고, push-specific integration은 후속 push Milestone 범위로 남는다. - Completion mode: check-on-pass ## 분석 결과 ### 읽은 파일 - `agent-ops/rules/project/rules.md` - `agent-ops/rules/private/rules.md` - `agent-ops/rules/common/rules-roadmap.md` - `agent-ops/rules/project/domain/client/rules.md` - `agent-ops/rules/project/domain/operations/rules.md` - `agent-test/local/rules.md` - `agent-test/local/client-smoke.md` - `agent-roadmap/current.md` - `agent-roadmap/phase/operator-surface/PHASE.md` - `agent-roadmap/phase/operator-surface/milestones/operator-runtime-refactor.md` - `apps/client/lib/main.dart` - `apps/client/lib/src/app/bootstrap.dart` - `apps/client/lib/src/app/app.dart` - `apps/client/test/widget_test.dart` - `apps/client/test/integrations/mattermost_push_host_integration_test.dart` - `apps/client/integration_test/socket_runtime_smoke_test.dart` - `apps/client/pubspec.yaml` ### 테스트 환경 규칙 - `test_env=local`. - `agent-test/local/rules.md`는 존재하고 읽었다. local shell에서 테스트를 직접 실행하지 않고 remote host의 ALT checkout root에서 실행한다. - 매칭 profile은 `agent-test/local/client-smoke.md`다. client-only 변경 기본 명령은 `cd apps/client && flutter test`. - Socket runtime/integration smoke는 API socket/platform runtime이 필요하면 실행 가능 여부를 확인하고, 실행할 수 없으면 pass가 아니라 skip/blocker와 미실행 명령을 기록한다. ### 테스트 커버리지 공백 - `apps/client/test/widget_test.dart:18` already proves `AltClientApp` can render without a `mattermostHost`, but it bypasses `runAltClient` and `bootstrapAltClient`. - `apps/client/lib/src/app/bootstrap.dart:25` has no unit test proving Firebase/push are optional. - Mattermost host integration tests cover host initialization behavior in isolation (`apps/client/test/integrations/mattermost_push_host_integration_test.dart:91`) but not app bootstrap separation. ### 심볼 참조 - renamed/removed symbol candidate: none required. - `bootstrapAltClient` references: `apps/client/lib/src/app/bootstrap.dart:25`, `apps/client/lib/src/app/bootstrap.dart:33`. - `runAltClient` references: `apps/client/lib/main.dart:3`, `apps/client/lib/src/app/bootstrap.dart:31`. - `MattermostPushHostIntegration` app references: `apps/client/lib/src/app/bootstrap.dart:6`, `apps/client/lib/src/app/app.dart:6`. ### 분할 판단 - split decision policy를 먼저 평가했다. - shared task group: `m-operator-runtime-refactor`. - this subtask: `03_client_bootstrap`, no predecessors. - It is independent because it touches Flutter bootstrap/test boundaries only. It must not wait for worker/API runtime plans and must not implement push notification behavior itself. ### 범위 결정 근거 - Do not change dashboard layout, navigation depth, chart/form wireframe, or production operator screens. - Do not edit generated Dart protobuf files. - Do not remove Mattermost/Firebase integration packages; only make them optional at app bootstrap. - Do not implement push notification behavior. Push-specific integration remains in the later push Milestone. ### 빌드 등급 - build lane: `cloud-G07`; review lane: `cloud-G07`. - Flutter bootstrap touches platform/Firebase startup and optional external integration boundaries; tests are runnable but runtime smoke may be environment-gated. ## 구현 체크리스트 - [ ] [CLIENT-1] core operator shell bootstrap을 Firebase/Mattermost push initialization과 분리한다. - [ ] [CLIENT-2] bootstrap tests를 추가해 기본 `runAltClient` path가 push/Firebase 없이 app shell을 시작할 수 있음을 검증한다. - [ ] [CLIENT-3] push-specific initialization은 explicit opt-in API로 남기고 후속 push Milestone 범위임을 코드 경계로 드러낸다. - [ ] 원격 ALT checkout root에서 `cd apps/client && flutter test`를 실행하고 실제 stdout/stderr를 review stub에 기록한다. - [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. ### [CLIENT-1] Core Shell Bootstrap #### 문제 `apps/client/lib/src/app/bootstrap.dart:25` calls `Firebase.initializeApp()` and `_mattermostHost.initialize()` before `runApp`. This makes optional push readiness a prerequisite for the operator shell, contrary to the client domain rule and current Milestone. #### 해결 방법 Split bootstrap into core shell setup and optional push setup. Keep `applyFullscreenMode` in the core path. Return or pass a nullable `MattermostPushHostIntegration` only when push is explicitly enabled. Before: ```dart // apps/client/lib/src/app/bootstrap.dart:25 Future bootstrapAltClient() async { await applyFullscreenMode(); await Firebase.initializeApp(); await _mattermostHost.initialize(); } ``` After: ```dart class AltClientBootstrapOptions { const AltClientBootstrapOptions({this.enablePush = false}); final bool enablePush; } Future bootstrapAltClient({ AltClientBootstrapOptions options = const AltClientBootstrapOptions(), }) async { await applyFullscreenMode(); if (!options.enablePush) return null; await Firebase.initializeApp(); await _mattermostHost.initialize(); return _mattermostHost; } ``` Allow injectable functions or host factory if needed for tests; do not call Firebase in the default test path. #### 수정 파일 및 체크리스트 - [ ] `apps/client/lib/src/app/bootstrap.dart`: introduce bootstrap options/result and default push-off core bootstrap. - [ ] `apps/client/lib/main.dart`: keep `main` simple; default `runAltClient()` should render the core shell. - [ ] `apps/client/lib/src/app/app.dart`: no change expected unless constructor docs or nullable host behavior needs tightening. #### 테스트 작성 Add `apps/client/test/app/bootstrap_test.dart` or equivalent: - default `bootstrapAltClient` does not call injected Firebase/push functions. - opt-in push path calls injected Firebase and host initialize once. #### 중간 검증 Run from the remote ALT checkout root: ```bash cd apps/client && flutter test test/app/bootstrap_test.dart ``` Expected: exit code 0. ### [CLIENT-2] App Shell Starts Without Push #### 문제 `apps/client/test/widget_test.dart:19` instantiates `AltClientApp` directly, so it does not protect the `runAltClient` bootstrap path. #### 해결 방법 Make `runAltClient` accept injectable `runApp`/bootstrap hooks or a testable `buildAltClientApp` helper. Keep tests simple and avoid launching platform Firebase. Before: ```dart // apps/client/lib/src/app/bootstrap.dart:31 Future runAltClient() async { WidgetsFlutterBinding.ensureInitialized(); await bootstrapAltClient(); runApp(ProviderScope(child: AltClientApp(mattermostHost: _mattermostHost))); } ``` After: ```dart Future runAltClient({ AltClientBootstrapOptions options = const AltClientBootstrapOptions(), void Function(Widget app)? runAppOverride, }) async { WidgetsFlutterBinding.ensureInitialized(); final mattermostHost = await bootstrapAltClient(options: options); (runAppOverride ?? runApp)( ProviderScope(child: AltClientApp(mattermostHost: mattermostHost)), ); } ``` If `runAppOverride` creates awkward production API, prefer a private testable helper that returns the root widget after bootstrap. #### 수정 파일 및 체크리스트 - [ ] `apps/client/lib/src/app/bootstrap.dart`: make default run path pass `mattermostHost: null`. - [ ] `apps/client/test/widget_test.dart`: keep direct shell tests passing. - [ ] `apps/client/test/app/bootstrap_test.dart`: assert default run path builds `AltClientApp` without a Mattermost host. #### 테스트 작성 Required. Use injected callbacks/fakes so no Firebase platform initialization is needed. #### 중간 검증 Run from the remote ALT checkout root: ```bash cd apps/client && flutter test test/widget_test.dart test/app/bootstrap_test.dart ``` Expected: exit code 0. ### [CLIENT-3] Push Opt-In Boundary #### 문제 `apps/client/lib/src/app/bootstrap.dart:10` creates a package-level `_mattermostHost` and immediately uses it during default bootstrap. This hides the optional boundary and invites later push work to leak into the operator shell. #### 해결 방법 Keep the host factory private but only instantiate or initialize it when push is explicitly enabled. Existing `MattermostPushHostIntegration` tests remain the behavior owner for push-specific logic. Before: ```dart // apps/client/lib/src/app/bootstrap.dart:10 final _mattermostHost = MattermostPushHostIntegration( pushClient: MattermostPushPluginClient(), ); ``` After: ```dart MattermostPushHostIntegration createMattermostPushHost() { return MattermostPushHostIntegration( pushClient: MattermostPushPluginClient(), ); } ``` Use the factory only in opt-in push bootstrap. If a singleton is retained, ensure it is not initialized on the default path. #### 수정 파일 및 체크리스트 - [ ] `apps/client/lib/src/app/bootstrap.dart`: isolate push host creation/initialization behind explicit opt-in. - [ ] `apps/client/test/integrations/mattermost_push_host_integration_test.dart`: no behavior change expected; run to confirm. - [ ] Do not edit `apps/client/lib/src/integrations/mattermost/*` unless a compile error requires a narrow import/API update. #### 테스트 작성 No new Mattermost behavior test is needed beyond existing host tests, but run them because imports/factory usage may change. #### 중간 검증 Run from the remote ALT checkout root: ```bash cd apps/client && flutter test test/integrations/mattermost_push_host_integration_test.dart ``` Expected: exit code 0. ## 수정 파일 요약 | 파일 | 항목 | |------|------| | `apps/client/lib/src/app/bootstrap.dart` | CLIENT-1, CLIENT-2, CLIENT-3 | | `apps/client/lib/main.dart` | CLIENT-2 | | `apps/client/lib/src/app/app.dart` | CLIENT-1 if nullable host docs/types need tightening | | `apps/client/test/app/bootstrap_test.dart` | CLIENT-1, CLIENT-2 | | `apps/client/test/widget_test.dart` | CLIENT-2 | | `apps/client/test/integrations/mattermost_push_host_integration_test.dart` | CLIENT-3 verification only | ## 최종 검증 Run from the remote ALT checkout root: ```bash cd apps/client && flutter test ``` Expected: exit code 0. Flutter test cache is not accepted as evidence; record actual stdout/stderr. If runtime socket smoke is attempted, use the client profile rules and record actual skip/blocker conditions instead of treating skipped integration as pass. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.