<!-- task=m-artifact-browser/05+04_install_handoff plan=0 tag=INSTALL_HANDOFF -->

# Plan - INSTALL_HANDOFF

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

구현의 마지막 단계는 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이다. 검증 명령을 실행하고 실제 출력, 계획 대비 변경, 설계 결정을 기록한 뒤 active 파일을 유지한 상태로 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌 없이는 진행할 수 없을 때만 review stub의 `사용자 리뷰 요청` 섹션에 근거를 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행으로 메울 수 있는 증거 공백은 사용자 리뷰 요청 사유가 아니다. finalization, `complete.log`, archive 이동은 code-review-skill 전용이다.

## 배경

다운로드/staging 작업이 verified local APK path를 만들면 그 경로를 device 선택과 USB 설치 흐름으로 넘겨야 한다. 이 plan은 artifact-flow에서 나온 산출물을 app shell/device UI 경계로 전달하는 것까지만 다룬다. 실제 `adb install -r` 실행의 완성은 USB 설치 Milestone 책임으로 남긴다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 해당 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`에서 복사한 형식이며, 구현 에이전트는 직접 사용자 프롬프트를 만들지 않는다. code-review가 차단의 정당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/usable-mvp/milestones/artifact-browser.md`
- Task ids:
  - `handoff`: 다운로드된 APK 경로를 device 선택과 USB 설치 흐름으로 넘긴다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/update-roadmap/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-ops/rules/project/domain/artifact-flow/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/artifact-flow-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/usable-mvp/PHASE.md`
- `agent-roadmap/phase/usable-mvp/milestones/artifact-browser.md`
- `agent-task/archive/2026/06/m-artifact-browser/03+02_build_list_ui/complete.log`
- `lib/src/features/builds/builds_page.dart`
- `lib/src/features/app_shell.dart`
- `lib/src/features/devices/devices_page.dart`
- `lib/src/models/jenkins_build.dart`
- `lib/src/services/adb_service.dart`
- `test/builds_page_test.dart`
- `test/widget_test.dart`

### 테스트 환경 규칙

- 선택 환경: `local`.
- `agent-test/local/rules.md`가 존재하며 읽었다. Flutter/macOS/ADB/runtime evidence는 standard remote Mac runner 기준이다.
- matched profile: `agent-test/local/artifact-flow-smoke.md`를 읽었다. Handoff는 artifact-flow 산출물을 넘기는 작업이며 실제 ADB install은 이 plan에서 제외한다.
- 적용 명령:
  - local preflight: `git diff --check`, `flutter test test/widget_test.dart`, `flutter test test/builds_page_test.dart`
  - remote required: `ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc '\''cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter analyze'\'''`
  - remote required: `ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc '\''cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter test'\'''`
- 구조적 blank/skeleton 또는 `<확인 필요>` 값은 없었다.

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

- `AppSokShell`은 [lib/src/features/app_shell.dart](/config/workspace/appsok/lib/src/features/app_shell.dart:18)에서 page list가 `const`이고, BuildsPage callback이 shell state로 올라가지 않는다.
- `DevicesPage`는 [lib/src/features/devices/devices_page.dart](/config/workspace/appsok/lib/src/features/devices/devices_page.dart:9)에서 sample device만 렌더링하고 pending APK path 입력이 없다.
- Shell/widget tests는 install handoff state나 device page pending install banner를 검증하지 않는다.

### 심볼 참조

- 이름 변경 또는 제거 예정 심볼: 없음.
- 확장 대상:
  - `BuildsPage.onInstallRequested`: 현재 build/artifact callback이며 `05+04` 구현 시 `04+03`의 verified staged APK callback과 연결해야 한다.
  - `DevicesPage`: 현재 생성자 인자가 없다. `rg --sort path -n "DevicesPage"` 결과 `lib/src/features/app_shell.dart`와 `lib/src/features/devices/devices_page.dart`가 call site다.

### 분할 판단

split decision policy를 계획 파일 선택 전에 평가했다. 공유 task group은 `agent-task/m-artifact-browser/`다.

- `04+03_download_staging`: 먼저 verified APK path를 만들어야 한다.
- `05+04_install_handoff`: predecessor `04`가 완료되어야 시작한다. 현재 active/archive `complete.log`는 없으므로 구현 시작 전 `agent-task/m-artifact-browser/04+03_download_staging/complete.log` 또는 archive의 matching complete.log가 필요하다.

이 plan은 device-console 경계를 건드리므로 다운로드 구현과 분리했다. 디렉터리명 `05+04_install_handoff`가 runtime dependency source of truth다.

### 범위 결정 근거

- 포함: verified APK handoff state, shell page 전환 또는 pending install 표시, device page가 APK path/build/artifact 요약을 받는 UI, widget test.
- 제외: 실제 `AdbService.installApk` 호출, device discovery 구현, logcat 연동, Jenkins credential 변경, staging cleanup 완료 트리거. 실제 설치 실행은 USB 설치 Milestone에서 다룬다.
- `lib/src/services/adb_service.dart`는 경계 확인을 위해 읽었지만 이 plan의 기본 수정 대상은 아니다.

### 빌드 등급

- `local-G06`: shell/device UI state 연결과 widget coverage 중심이며, predecessor만 충족되면 변경 범위가 작고 review-detectable하다.

## 구현 체크리스트

- [ ] [INSTALL_HANDOFF-1] `04+03_download_staging` 완료 산출물의 verified APK path를 받을 handoff model/callback을 정의한다.
- [ ] [INSTALL_HANDOFF-2] `AppSokShell`에서 handoff state를 보관하고 device page로 전달하거나 이동한다.
- [ ] [INSTALL_HANDOFF-3] `DevicesPage`가 pending APK 요약과 USB 설치 흐름 진입 상태를 표시하도록 widget test를 추가한다.
- [ ] 최종 검증 명령을 실행하고 remote runner 결과를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [INSTALL_HANDOFF-1] Handoff Contract

### 문제

`BuildsPage`는 [lib/src/features/builds/builds_page.dart](/config/workspace/appsok/lib/src/features/builds/builds_page.dart:11)의 `InstallRequestHandler`로 build/artifact만 넘긴다. `04+03_download_staging` 이후에는 verified local APK path와 metadata를 함께 넘기는 계약이 필요하다.

Before:

```dart
// lib/src/features/builds/builds_page.dart:11
typedef InstallRequestHandler =
    void Function(JenkinsBuild build, BuildArtifact artifact);
```

### 해결 방법

- `04+03_download_staging`의 complete.log와 코드 상태를 먼저 확인한다.
- verified staged APK 결과 타입이 이미 있으면 재사용한다. 없으면 artifact-flow 안에 작은 handoff value object를 둔다.
- build number, job name, artifact file name, local path, byte size를 포함한다.
- 기존 `onInstallRequested`의 의미가 다운로드 시작 callback으로 남았는지, verified handoff callback으로 바뀌었는지 코드 상태에 맞춰 이름을 정리한다. 이름 변경 시 모든 call site를 `rg --sort path`로 갱신한다.

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

- [ ] `lib/src/features/builds/builds_page.dart` 또는 `lib/src/models/*`: verified APK handoff 계약을 정의/재사용한다.
- [ ] `test/builds_page_test.dart`: verified APK callback이 shell로 올라갈 수 있는 형태인지 확인한다.

### 테스트 작성

- 작성: `test/builds_page_test.dart`
- test name: `notifies verified apk handoff after download is verified`

### 중간 검증

```bash
flutter test test/builds_page_test.dart
```

예상: build page widget test 통과.

## [INSTALL_HANDOFF-2] Shell State Routing

### 문제

`AppSokShell`은 [lib/src/features/app_shell.dart](/config/workspace/appsok/lib/src/features/app_shell.dart:18)에서 `_pages`를 `const` list로 보관한다. 이 구조에서는 BuildsPage에서 올라온 verified APK path를 DevicesPage로 전달하거나 디바이스 화면으로 전환할 수 없다.

Before:

```dart
// lib/src/features/app_shell.dart:18
final _pages = const [
  _ShellPage(
    label: '빌드',
    icon: Icons.inventory_2_outlined,
    selectedIcon: Icons.inventory_2,
    child: BuildsPage(),
  ),
```

### 해결 방법

- `_pages`를 build-time getter 또는 method로 바꾸어 shell state를 child에 주입할 수 있게 한다.
- shell state에 pending install request를 보관한다.
- verified APK handoff를 받으면 `_selectedIndex`를 device page index로 바꾸거나, 최소한 DevicesPage에 pending install banner를 표시한다. UX는 "바로 설치 흐름으로 넘김"에 맞춰 device page 전환을 우선한다.

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

- [ ] `lib/src/features/app_shell.dart`: pending install state와 page construction을 추가한다.
- [ ] `test/widget_test.dart`: build에서 verified handoff 발생 시 device page가 pending install 상태를 받는지 검증한다.

### 테스트 작성

- 작성: `test/widget_test.dart`
- test name: `routes verified apk handoff to devices page`

### 중간 검증

```bash
flutter test test/widget_test.dart
```

예상: shell widget test 통과.

## [INSTALL_HANDOFF-3] Device Page Pending Install UI

### 문제

`DevicesPage`는 [lib/src/features/devices/devices_page.dart](/config/workspace/appsok/lib/src/features/devices/devices_page.dart:9)에서 sample devices만 보여준다. 다운로드된 APK path가 넘어와도 사용자가 어떤 APK를 설치 대상으로 보고 있는지 알 수 없다.

Before:

```dart
// lib/src/features/devices/devices_page.dart:9
Widget build(BuildContext context) {
  final devices = _sampleDevices;
```

### 해결 방법

- `DevicesPage`에 optional pending install model을 주입한다.
- 상단에 APK filename, build number, local path 요약을 표시한다.
- device tile의 "설치 대상" 버튼은 pending install이 없으면 disabled 또는 기존 no-op 상태를 유지하고, pending install이 있으면 다음 USB 설치 Milestone이 받을 수 있는 callback을 호출한다.
- 실제 `AdbService.installApk` 호출은 하지 않는다.

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

- [ ] `lib/src/features/devices/devices_page.dart`: pending install banner와 button state를 추가한다.
- [ ] `test/widget_test.dart` 또는 신규 `test/devices_page_test.dart`: pending install 표시와 overflow를 검증한다.

### 테스트 작성

- 작성: `test/widget_test.dart` 또는 `test/devices_page_test.dart`
- test names:
  - `shows pending apk install summary on devices page`
  - `keeps device page compact viewport free of overflow with pending apk`

### 중간 검증

```bash
flutter test test/widget_test.dart
```

예상: shell/device widget test 통과.

## 의존 관계 및 구현 순서

`05+04_install_handoff`는 predecessor `04` 완료 후 시작한다. 현재 `04` complete.log는 없다. 구현 시작 전 다음 중 하나가 있어야 한다.

- active: `agent-task/m-artifact-browser/04+03_download_staging/complete.log`
- archive: `agent-task/archive/*/*/m-artifact-browser/04+03_download_staging/complete.log`

구현 순서:

1. predecessor `04` complete.log 확인
2. `INSTALL_HANDOFF-1`
3. `INSTALL_HANDOFF-2`
4. `INSTALL_HANDOFF-3`
5. 최종 검증과 review stub 작성

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/src/features/builds/builds_page.dart` | INSTALL_HANDOFF-1 |
| `lib/src/features/app_shell.dart` | INSTALL_HANDOFF-2 |
| `lib/src/features/devices/devices_page.dart` | INSTALL_HANDOFF-3 |
| `test/builds_page_test.dart` | INSTALL_HANDOFF-1 |
| `test/widget_test.dart` | INSTALL_HANDOFF-2, INSTALL_HANDOFF-3 |

## 최종 검증

```bash
git diff --check
flutter test test/builds_page_test.dart
flutter test test/widget_test.dart
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc '\''cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter analyze'\'''
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com 'zsh -lc '\''cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter test'\'''
```

예상: diff whitespace 문제 없음, local focused tests 통과, remote `flutter analyze`에서 `No issues found!`, remote `flutter test`에서 `All tests passed!`.

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