<!-- task=m-logcat-console/03+02_auto_scroll plan=0 tag=LOGCAT_SCROLL -->

# LOGCAT_SCROLL 계획

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것은 필수입니다. 구현 후 검증을 실행하고 실제 내용과 출력을 기록한 뒤 active 파일을 그대로 두고 리뷰 준비를 보고하세요. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용입니다. 구현이 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 중단하세요. 직접 질문, 채팅 선택지, `request_user_input`, `USER_REVIEW.md` 작성, archive, `complete.log` 작성은 금지입니다. 후속 에이전트가 재실행이나 산출물 수집으로 닫을 수 있는 증거 공백은 사용자 리뷰 요청이 아닙니다.

## 배경

Live logcat stream은 새 로그가 계속 들어오기 때문에 console의 scroll 정책이 명확해야 합니다. 사용자가 pause한 상태에서 자동으로 바닥으로 끌려가면 디버깅 중 읽던 위치를 잃습니다. `02+01_console_stream`으로 live append가 들어온 뒤, pause-aware auto-scroll을 별도 리뷰 단위로 구현합니다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록합니다. 구현 에이전트는 사용자에게 직접 질문하지 않으며, code-review가 blocker 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정합니다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/usable-mvp/milestones/logcat-console.md`
- Task ids:
  - `auto-scroll`: 새 로그가 들어올 때 자동 스크롤하되 pause 상태에서는 사용자의 위치를 유지한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/usable-mvp/PHASE.md`
- `agent-roadmap/phase/usable-mvp/milestones/logcat-console.md`
- `agent-ops/rules/project/domain/device-console/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/device-console-smoke.md`
- `lib/src/services/adb_service.dart`
- `lib/src/features/console/console_page.dart`
- `lib/src/features/app_shell.dart`
- `lib/src/app.dart`
- `lib/src/features/devices/devices_page.dart`
- `lib/src/models/adb_device.dart`
- `pubspec.yaml`
- `test/adb_service_test.dart`
- `test/widget_test.dart`
- `test/console_page_test.dart`

### 테스트 환경 규칙

- test_env: `local`
- `agent-test/local/rules.md` 존재 및 읽음.
- 매칭 profile: `agent-test/local/device-console-smoke.md`
- 적용 명령: remote runner 기준 `flutter analyze`, `flutter test`.
- 이 plan은 widget-level scroll behavior가 핵심이므로 `test/console_page_test.dart`를 중간 검증으로 둔다.

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

- 새 로그 append 시 bottom auto-scroll test 없음.
- pause 상태에서 scroll offset이 유지되는 test 없음.
- clear/export/filter는 이 plan의 대상이 아니므로 coverage 공백으로 남긴다.

### 심볼 참조

- renamed/removed symbol: none.
- pause button 현재 위치: `lib/src/features/console/console_page.dart:46-50`.
- log list 현재 위치: `lib/src/features/console/console_page.dart:69-88`.

### 분할 판단

- split decision policy를 먼저 평가했다.
- 공유 task group: `m-logcat-console`
- 이 subtask는 `03+02_auto_scroll`이며 predecessor `02`는 `agent-task/m-logcat-console/02+01_console_stream/complete.log` 또는 archive matching `complete.log`가 필요하다.
- 현재 확인 결과 predecessor complete.log는 없음. 구현 시작 전 `02+01_console_stream` PASS 완료가 필요하다.

### 범위 결정 근거

- `stream`, `lifecycle`은 `02+01_console_stream` 책임이며 여기서는 이미 live append가 있다고 가정한다.
- query/level filter, clear, export는 `filter` 에픽 소속이므로 제외한다.
- 실제 ADB device smoke는 scroll logic 검증의 필수 조건이 아니므로 widget test로 판단한다.

### 빌드 등급

- build/review: `local-G05`. UI state와 scroll controller 동작만 다루며 fake stream 기반 widget test로 검증 가능하다.

## 구현 체크리스트

- [ ] `ConsolePage`에 `ScrollController`와 pause 상태를 추가하고 새 로그 append 후 unpaused 상태에서 bottom으로 이동한다.
- [ ] pause/resume 버튼이 auto-scroll 정책을 제어하고 pause 중 사용자 scroll offset을 유지한다.
- [ ] `test/console_page_test.dart`에서 unpaused auto-scroll과 paused offset 유지 동작을 검증한다.
- [ ] `flutter test test/console_page_test.dart`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- Directory dependency: `03+02_auto_scroll` -> `02+01_console_stream`
- 구현 전 `agent-task/m-logcat-console/02+01_console_stream/complete.log` 또는 archive `agent-task/archive/*/*/m-logcat-console/02+01_console_stream/complete.log`가 있어야 한다. 현재는 없음.

## LOGCAT_SCROLL-1 Pause-Aware Auto Scroll

### 문제

[console_page.dart](/config/workspace/appsok/lib/src/features/console/console_page.dart:46)의 pause 버튼은 no-op이고 [console_page.dart](/config/workspace/appsok/lib/src/features/console/console_page.dart:69)의 log list는 controller 없이 렌더링된다.

```dart
46  IconButton.filledTonal(
47    tooltip: '일시정지',
48    onPressed: () {},
49    icon: const Icon(Icons.pause),
50  ),
```

```dart
69  child: ListView.builder(
70    padding: const EdgeInsets.all(16),
71    itemCount: _logLines.length,
72    itemBuilder: (context, index) {
```

### 해결 방법

`ScrollController`와 `_isPaused`를 도입한다. stream line append 후 `WidgetsBinding.instance.addPostFrameCallback`에서 unpaused일 때만 `animateTo/maxScrollExtent` 또는 `jumpTo`를 실행한다. pause 중에는 기존 offset을 건드리지 않는다.

```dart
void _appendLine(String line) {
  setState(() => _lines.add(line));
  if (!_isPaused) {
    WidgetsBinding.instance.addPostFrameCallback((_) => _scrollToBottom());
  }
}
```

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

- [ ] `lib/src/features/console/console_page.dart`: `ScrollController`, `_isPaused`, pause/resume icon/tooltip 상태 추가.
- [ ] `lib/src/features/console/console_page.dart`: append 후 unpaused auto-scroll과 paused no-scroll 적용.
- [ ] `test/console_page_test.dart`: fake stream으로 scroll behavior 검증 추가.

### 테스트 작성

- 작성: `test/console_page_test.dart`
- 테스트 이름 후보:
  - `auto-scrolls to the newest log while running`
  - `keeps user scroll position while paused`
- assertion: unpaused append 후 list bottom marker가 보이고, pause 후 추가 line이 들어와도 pre-pause scroll offset이 유지된다.

### 중간 검증

```bash
flutter test test/console_page_test.dart
```

예상: console focused widget tests 통과.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/src/features/console/console_page.dart` | LOGCAT_SCROLL-1 |
| `test/console_page_test.dart` | LOGCAT_SCROLL-1 |

## 최종 검증

```bash
flutter analyze
flutter test test/console_page_test.dart
```

예상: analyzer issue 없음, targeted widget tests 통과.

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