<!-- task=m-operator-runtime-refactor/03_client_bootstrap plan=0 tag=CLIENT -->

# 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<void> bootstrapAltClient() async {
  await applyFullscreenMode();
  await Firebase.initializeApp();
  await _mattermostHost.initialize();
}
```

After:

```dart
class AltClientBootstrapOptions {
  const AltClientBootstrapOptions({this.enablePush = false});
  final bool enablePush;
}

Future<MattermostPushHostIntegration?> 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<void> runAltClient() async {
  WidgetsFlutterBinding.ensureInitialized();
  await bootstrapAltClient();
  runApp(ProviderScope(child: AltClientApp(mattermostHost: _mattermostHost)));
}
```

After:

```dart
Future<void> 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
