<!-- task=m-artifact-browser/04+03_download_staging plan=2 tag=REVIEW2_DOWNLOAD_STAGING -->

# Plan - REVIEW2_DOWNLOAD_STAGING

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

이 계획은 `code_review_local_G08_1.log`의 FAIL Required 항목만 닫는 follow-up이다. 구현의 마지막 단계는 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이다. 검증 명령을 실행하고 실제 출력, 계획 대비 변경, 설계 결정을 기록한 뒤 active 파일을 유지한 상태로 리뷰 준비를 보고한다.

사용자 결정, 사용자 소유 외부 환경, 범위 충돌 없이는 진행할 수 없을 때만 review stub의 `사용자 리뷰 요청` 섹션에 근거를 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행으로 메울 수 있는 증거 공백은 사용자 리뷰 요청 사유가 아니다. finalization, `complete.log`, archive 이동은 code-review-skill 전용이다.

## 배경

G08 구현은 staging filename, streaming event, verified handoff를 대부분 보완했지만, active download 중 `_InstallCta`가 dispose되는 경로에서 byte stream을 정상 종료한다. 일부 bytes가 이미 staging file에 쓰인 뒤 정상 close가 전달되면 `ArtifactStagingService.stageApk`가 partial APK를 성공 staging처럼 verify할 수 있다. 이 follow-up은 dispose/route-away cancellation을 버튼 cancel과 같은 cleanup 경로로 통합한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/usable-mvp/milestones/artifact-browser.md`
- Task ids:
  - `download-progress`: artifact 다운로드 진행률과 취소 상태를 표시한다.
  - `temp-download`: 다운로드 파일을 앱 임시/staging 경로에 저장하고 설치 완료 또는 취소 후 삭제한다.
  - `artifact-verify`: 다운로드 완료 후 파일 존재, 확장자, 크기 정보를 확인한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-roadmap/current.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/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-task/m-artifact-browser/04+03_download_staging/plan_local_G08_1.log`
- `agent-task/m-artifact-browser/04+03_download_staging/code_review_local_G08_1.log`
- `lib/src/features/builds/builds_page.dart`
- `lib/src/services/artifact_staging_service.dart`
- `lib/src/services/jenkins_client.dart`
- `test/builds_page_test.dart`
- `test/artifact_staging_service_test.dart`
- `test/jenkins_client_test.dart`

### 리뷰 근거

- `git diff --check`: 통과.
- `flutter analyze`: 최초 병렬 실행은 Flutter startup lock/ephemeral cleanup 충돌로 실패했으나, 직렬 재실행은 `No issues found!`.
- `flutter test test/jenkins_client_test.dart test/artifact_staging_service_test.dart test/builds_page_test.dart`: `All tests passed!`.
- 통과한 tests는 explicit cancel button 경로를 검증하지만, 위젯 dispose/route-away 중 partial staging cleanup 경로는 검증하지 않는다.

### 범위 결정 근거

- 포함: active download 중 `_InstallCtaState.dispose()` 또는 route-away로 발생하는 cancellation cleanup, 해당 widget test.
- 제외: streaming event 계약 추가 변경, `ArtifactStagingService` verify 정책 변경, 실제 `adb install`, device 선택 UI.
- 새 패키지는 추가하지 않는다.

### 빌드 등급

- `local-G08`: 단일 UI lifecycle/cancellation bug이지만 비동기 stream, file cleanup, widget disposal이 얽혀 있어 focused widget test와 소스 리뷰가 함께 필요하다.

## 구현 체크리스트

- [ ] [REVIEW2_DOWNLOAD_STAGING-1] active download 중 widget dispose가 partial staging을 성공 완료로 처리하지 않도록 cancellation cleanup 경로를 보강한다.
- [ ] [REVIEW2_DOWNLOAD_STAGING-2] dispose/route-away 중 partial byte stream이 stager cleanup 경로로 들어가는 widget test를 추가한다.
- [ ] 최종 검증 명령을 실행하고 remote runner 결과를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [REVIEW2_DOWNLOAD_STAGING-1] Dispose Cancellation Cleanup

### 문제

`_InstallCtaState.dispose()`는 `_task?.cancel()` 호출 후 `_byteController?.close()`를 정상 종료로 보낸다. 이미 partial bytes가 stager에 전달된 경우 `ArtifactStagingService.stageApk`는 정상 종료로 간주해 verify를 실행하고, non-empty `.apk` partial file을 성공 staging처럼 남길 수 있다.

### 해결 방법

- 버튼 cancel과 dispose cancel이 같은 helper를 사용하게 한다.
- dispose에서는 UI `setState` 없이 `_task.cancel()`, event subscription cancel, `_byteController.addError(_ArtifactDownloadCancelledException())`, controller close를 수행한다.
- `_byteController`가 이미 closed인 경우에는 중복 add/close를 피한다.
- `_startDownload`의 catch/finally가 dispose 후에도 unhandled error를 만들지 않도록 mounted/cancelled state를 명확히 유지한다.

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

- [ ] `lib/src/features/builds/builds_page.dart`: dispose cancellation이 stager에 cancellation error를 전달하도록 보강한다.
- [ ] `test/builds_page_test.dart`: active download 중 뒤로 가기 또는 widget 교체로 `_InstallCta`가 dispose되면 stager가 cancellation error를 받고 partial success callback이 발생하지 않는지 검증한다.

### 중간 검증

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

## [REVIEW2_DOWNLOAD_STAGING-2] Verification Evidence

### 문제

G08 검증은 버튼 cancel, determinate progress, handoff는 확인했지만 route-away/dispose cleanup을 직접 검증하지 않았다.

### 해결 방법

- 새 widget test 이름 예시: `disposes active download as cancellation and does not stage partial apk`.
- test 구성:
  - 첫 chunk를 emit하고 두 번째 chunk 전 pause하는 `DownloadTask`를 만든다.
  - stager는 byte stream을 drain하고, error를 별도 completer에 기록한다.
  - 다운로드 시작 후 뒤로가기 버튼을 탭하거나 다른 widget으로 repump해 `_InstallCta`를 dispose한다.
  - stager error가 cancellation을 포함하는지 확인한다.
  - verified label/install callback이 발생하지 않았는지 확인한다.

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

- [ ] `test/builds_page_test.dart`: dispose cancellation widget test를 추가한다.

### 중간 검증

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

## 의존 관계 및 구현 순서

1. `REVIEW2_DOWNLOAD_STAGING-1`
2. `REVIEW2_DOWNLOAD_STAGING-2`
3. 최종 검증과 review stub 작성

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/src/features/builds/builds_page.dart` | REVIEW2_DOWNLOAD_STAGING-1 |
| `test/builds_page_test.dart` | REVIEW2_DOWNLOAD_STAGING-2 |

## 최종 검증

```bash
git diff --check
flutter analyze
flutter test test/builds_page_test.dart
flutter test test/jenkins_client_test.dart test/artifact_staging_service_test.dart test/builds_page_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!`.
