refactor: update app shell navigation and archive task files

This commit is contained in:
toki 2026-06-09 06:23:25 +09:00
parent 5af717d432
commit a3933707a6
13 changed files with 340 additions and 79 deletions

View file

@ -35,7 +35,7 @@ AppSok의 Flutter macOS scaffold를 실제 기능 구현이 가능한 기준선
빌드, 디바이스, 콘솔, 설정 화면으로 바로 진입할 수 있는 macOS 업무 앱 구조를 만든다.
- [ ] [nav-shell] 좌측 navigation과 상단 Jenkins/ADB 상태 영역을 실제 상태 연결 전에도 안정적으로 표시한다. 검증: `flutter test`에서 app shell 렌더링과 navigation 전환이 통과한다.
- [x] [nav-shell] 좌측 navigation과 상단 Jenkins/ADB 상태 영역을 실제 상태 연결 전에도 안정적으로 표시한다. 검증: `flutter test`에서 app shell 렌더링과 navigation 전환이 통과한다.
- [ ] [responsive-ui] 기본 테스트 viewport에서 텍스트/버튼 overflow가 없도록 card, badge, toolbar 크기를 안정화한다. 검증: `flutter test`가 overflow exception 없이 통과한다.
- [x] [theme] AppSok 이름과 업무 앱 성격에 맞는 Material theme 기준선을 유지한다.

View file

@ -42,41 +42,46 @@ task=m-product-baseline/01_shell_nav_status, plan=0, tag=SHELL_NAV
| 항목 | 완료 여부 |
|------|---------|
| [SHELL_NAV-1] Shell navigation/status baseline | [ ] |
| [SHELL_NAV-1] Shell navigation/status baseline | [x] |
## 구현 체크리스트
- [ ] 좌측 navigation destination 4개와 상단 `Jenkins 대기`/`ADB 대기` 상태 pill이 실제 상태 연결 전에도 안정적으로 렌더링되도록 `AppSokShell` 구조를 정리한다. 검증: `flutter test`에서 app shell 렌더링과 navigation 전환이 통과한다.
- [ ] `test/widget_test.dart`에 빌드, 디바이스, 콘솔, 설정 전환 및 top status 표시 assertion을 추가한다.
- [ ] `dart format lib/src/features/app_shell.dart test/widget_test.dart`를 실행한다.
- [ ] `flutter analyze`와 `flutter test`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
- [x] 좌측 navigation destination 4개와 상단 `Jenkins 대기`/`ADB 대기` 상태 pill이 실제 상태 연결 전에도 안정적으로 렌더링되도록 `AppSokShell` 구조를 정리한다. 검증: `flutter test`에서 app shell 렌더링과 navigation 전환이 통과한다.
- [x] `test/widget_test.dart`에 빌드, 디바이스, 콘솔, 설정 전환 및 top status 표시 assertion을 추가한다.
- [x] `dart format lib/src/features/app_shell.dart test/widget_test.dart`를 실행한다.
- [x] `flutter analyze`와 `flutter test`를 실행한다.
- [x] 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-product-baseline/01_shell_nav_status/`를 `agent-task/archive/YYYY/MM/m-product-baseline/01_shell_nav_status/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-product-baseline/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G05_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_local_G05_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-product-baseline/01_shell_nav_status/`를 `agent-task/archive/YYYY/MM/m-product-baseline/01_shell_nav_status/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-product-baseline/`를 제거하거나, 남은 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로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
- `NavigationRailDestination`은 widget tree에서 직접 찾히는 위젯 타입이 아니므로, 테스트 탭 대상은 `NavigationRail` 내부 label 텍스트를 찾는 방식으로 구현했습니다.
- 상단 상태 pill에 `ValueKey`와 tooltip 문구를 추가해 테스트에서 `Jenkins 대기`, `ADB 대기` placeholder 표시를 안정적으로 조회할 수 있게 정리했습니다.
- 콘솔 화면 marker는 로그 리스트 하단 항목 대신 즉시 렌더링되는 `패키지 또는 태그` 필드 label로 검증했습니다.
- 테스트는 한 건의 통합 test로 4개 destination 전환을 순회하며, 페이지별 대표 텍스트와 top status pill 노출을 동시에 검증하도록 구성했습니다.
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
- shell에는 Jenkins API, Keychain, ADB Process 호출을 추가하지 않고 route wiring과 상태 placeholder 표시만 유지했습니다.
- status pill은 `ValueKey('status-jenkins')`, `ValueKey('status-adb')`로 고정해 실제 상태 연결 전에도 테스트가 안정적으로 확인할 수 있게 했습니다.
- navigation 전환 테스트는 navigation rail의 label을 사용자 탭 대상처럼 사용해 destination 4개를 모두 검증합니다.
## 사용자 리뷰 요청
@ -110,19 +115,59 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후
### SHELL_NAV-1 중간 검증
```bash
$ flutter test
(output)
Waiting for another flutter command to release the startup lock...
00:00 +0: loading /config/workspace/appsok/test/widget_test.dart
00:00 +0: keeps the AppSok work app theme baseline
00:00 +1: renders the app shell
00:00 +2: switches between all shell destinations with status placeholders
00:01 +3: All tests passed!
```
### 최종 검증
```bash
$ dart format lib/src/features/app_shell.dart test/widget_test.dart
(output)
Formatted test/widget_test.dart
Formatted 2 files (1 changed) in 0.03 seconds.
$ flutter analyze
(output)
Analyzing appsok...
No issues found! (ran in 3.3s)
$ flutter test
(output)
Waiting for another flutter command to release the startup lock...
00:00 +0: loading /config/workspace/appsok/test/widget_test.dart
00:00 +0: keeps the AppSok work app theme baseline
00:00 +1: renders the app shell
00:00 +2: switches between all shell destinations with status placeholders
00:01 +3: All tests passed!
```
Remote Mac runner 최종 evidence:
```bash
$ zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter analyze'
Resolving dependencies...
Downloading packages...
app_links 7.0.0 (7.1.1 available)
flutter_secure_storage_darwin 0.3.2 (0.4.0 available)
matcher 0.12.19 (0.12.20 available)
meta 1.18.0 (1.18.3 available)
test_api 0.7.11 (0.7.12 available)
vector_math 2.2.0 (2.4.0 available)
webview_flutter_android 4.12.0 (4.13.0 available)
webview_flutter_wkwebview 3.25.1 (3.26.0 available)
Got dependencies!
8 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
Analyzing appsok...
No issues found! (ran in 1.7s)
$ zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter test'
00:00 +0: loading /Users/toki/docker/services/code-server/data/volume/workspace/appsok/test/widget_test.dart
00:00 +0: keeps the AppSok work app theme baseline
00:00 +1: renders the app shell
00:00 +2: switches between all shell destinations with status placeholders
00:01 +3: All tests passed!
```
---
@ -145,3 +190,22 @@ $ flutter test
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan. |
| 검증 결과 | Implementing agent | Fill command output only; command changes require a `계획 대비 변경 사항` entry. |
| 코드리뷰 결과 | Review agent appends | Not included until review. |
## 코드리뷰 결과
- 종합 판정: PASS
- 차원별 평가:
- correctness: Pass
- completeness: Pass
- test coverage: Pass
- API contract: Pass
- code quality: Pass
- plan deviation: Pass
- verification trust: Pass
- 발견된 문제: 없음
- 다음 단계: PASS - `complete.log` 작성 후 active task directory를 archive로 이동한다.
- 리뷰어 검증:
- `dart format --output=none --set-exit-if-changed lib/src/features/app_shell.dart test/widget_test.dart` - PASS; `Formatted 2 files (0 changed) in 0.02 seconds.`
- `flutter analyze` - PASS; `No issues found!`
- `flutter test` - PASS; `All tests passed!`
- `git diff --check` - PASS; 출력 없음

View file

@ -0,0 +1,44 @@
# Complete - m-product-baseline/01_shell_nav_status
## 완료 일시
2026-06-09
## 요약
SHELL_NAV plan 0 first review completed with PASS; app shell navigation/status baseline and widget coverage are in place.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_local_G05_0.log` | `code_review_local_G05_0.log` | PASS | Navigation/status baseline matched the plan; no Required/Suggested issues found. |
## 구현/정리 내용
- Added stable keys and tooltips to the Jenkins/ADB status placeholders in `AppSokShell`.
- Expanded widget coverage to cycle through build, device, console, and settings destinations while asserting the top status placeholders remain visible.
## 최종 검증
- `dart format --output=none --set-exit-if-changed lib/src/features/app_shell.dart test/widget_test.dart` - PASS; `Formatted 2 files (0 changed) in 0.02 seconds.`
- `flutter analyze` - PASS; `No issues found!`
- `flutter test` - PASS; `All tests passed!`
- `git diff --check` - PASS; output was empty.
- `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter analyze'` - PASS; implementation evidence recorded `No issues found!`.
- `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter test'` - PASS; implementation evidence recorded `All tests passed!`.
## Roadmap Completion
- Milestone: `agent-roadmap/phase/usable-mvp/milestones/product-baseline.md`
- Completed task ids:
- `nav-shell`: PASS; evidence=`agent-task/archive/2026/06/m-product-baseline/01_shell_nav_status/plan_local_G05_0.log`, `agent-task/archive/2026/06/m-product-baseline/01_shell_nav_status/code_review_local_G05_0.log`; verification=`flutter analyze`, `flutter test`
- Not completed task ids: 없음
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -42,42 +42,46 @@ task=m-product-baseline/02+01_responsive_ui, plan=0, tag=RESPONSIVE_UI
| 항목 | 완료 여부 |
|------|---------|
| [RESPONSIVE_UI-1] Overflow-safe shell and placeholder screens | [ ] |
| [RESPONSIVE_UI-1] Overflow-safe shell and placeholder screens | [x] |
## 구현 체크리스트
- [ ] `agent-task/m-product-baseline/01_shell_nav_status/complete.log`가 존재하는지 확인하고 없으면 이 plan 구현을 시작하지 않는다.
- [ ] top bar, toolbar, card, badge, button layout이 기본 테스트 viewport에서 overflow를 내지 않도록 `AppSokShell`과 placeholder page layout을 안정화한다. 검증: `flutter test`가 overflow exception 없이 통과한다.
- [ ] `test/widget_test.dart`에 작은 viewport smoke test를 추가하고 build/device/console/settings 화면을 순회해 overflow exception이 없음을 검증한다.
- [ ] `dart format lib/src/features/app_shell.dart lib/src/features/builds/builds_page.dart lib/src/features/devices/devices_page.dart lib/src/features/console/console_page.dart lib/src/features/settings/settings_page.dart test/widget_test.dart`를 실행한다.
- [ ] `flutter analyze`와 `flutter test`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
- [x] `agent-task/m-product-baseline/01_shell_nav_status/complete.log`가 존재하는지 확인하고 없으면 이 plan 구현을 시작하지 않는다.
- [x] top bar, toolbar, card, badge, button layout이 기본 테스트 viewport에서 overflow를 내지 않도록 `AppSokShell`과 placeholder page layout을 안정화한다. 검증: `flutter test`가 overflow exception 없이 통과한다.
- [x] `test/widget_test.dart`에 작은 viewport smoke test를 추가하고 build/device/console/settings 화면을 순회해 overflow exception이 없음을 검증한다.
- [x] `dart format lib/src/features/app_shell.dart lib/src/features/builds/builds_page.dart lib/src/features/devices/devices_page.dart lib/src/features/console/console_page.dart lib/src/features/settings/settings_page.dart test/widget_test.dart`를 실행한다.
- [x] `flutter analyze`와 `flutter test`를 실행한다.
- [x] 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-product-baseline/02+01_responsive_ui/`를 `agent-task/archive/YYYY/MM/m-product-baseline/02+01_responsive_ui/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-product-baseline/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G05_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_local_G05_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-product-baseline/02+01_responsive_ui/`를 `agent-task/archive/YYYY/MM/m-product-baseline/02+01_responsive_ui/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-product-baseline/`를 제거하거나, 남은 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로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
- `settings_page.dart`를 수정 대상 파일 목록에 추가했다. 계획의 수정 파일 요약에는 포함되었으나 수정 체크리스트에서는 명시적으로 다루지 않았다. compact viewport(600×400)에서 `SettingsPage`의 outer `Column`이 `Expanded` 없이 두 Material 카드(~384px)를 나열해 300px 제약을 116px 초과하는 수직 overflow가 발생했다. `Padding` → `SingleChildScrollView(padding:...)` 교체로 해소했다.
- 선행 의존성 확인에서 `agent-task/m-product-baseline/01_shell_nav_status/complete.log` 원래 경로가 없고 해당 파일들이 `agent-task/archive/`로 이동된 상태였다. git status와 archive 디렉터리 생성으로 완료를 간접 확인했다.
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
- **toolbar Row → Wrap**: toolbar에서 `Row(Expanded+Expanded+Button)` 패턴을 `Wrap(spacing:12, runSpacing:8/10)` + `ConstrainedBox(min/max)`로 교체했다. TextField에 `Expanded`가 없는 Wrap에서는 `ConstrainedBox`로 최소/최대 폭을 지정해 좁은 폭에서 자연스럽게 줄바꿈되게 했다.
- **`_TopBar` title**: `Text(title)` + `Spacer()` → `Expanded(Text(title, maxLines:1, overflow:ellipsis))`로 교체해 title이 status pill과의 공간을 flex로 공유하며 ellipsis 처리되도록 했다.
- **`SettingsPage` 스크롤화**: settings는 카드 높이가 고정되어 좁은 viewport에서 수직 overflow 발생. `SingleChildScrollView`로 전환했다. 향후 Jenkins/ADB 설정 항목이 늘어도 스크롤로 대응 가능하다.
- **console toolbar icon buttons**: Wrap 안에서 두 `IconButton`을 `Row(mainAxisSize: MainAxisSize.min)` 으로 묶어 항상 같은 행에 위치하도록 했다.
## 사용자 리뷰 요청
@ -111,19 +115,31 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후
### RESPONSIVE_UI-1 중간 검증
```bash
$ flutter test
(output)
00:00 +0: loading /config/workspace/appsok/test/widget_test.dart
00:00 +0: keeps the AppSok work app theme baseline
00:00 +1: renders the app shell
00:00 +2: renders shell pages without overflow at compact viewport
00:00 +3: switches between all shell destinations with status placeholders
00:01 +4: All tests passed!
```
### 최종 검증
```bash
$ dart format lib/src/features/app_shell.dart lib/src/features/builds/builds_page.dart lib/src/features/devices/devices_page.dart lib/src/features/console/console_page.dart lib/src/features/settings/settings_page.dart test/widget_test.dart
(output)
Formatted test/widget_test.dart
Formatted 6 files (1 changed) in 0.05 seconds.
$ flutter analyze
(output)
Analyzing appsok...
No issues found! (ran in 4.9s)
$ flutter test
(output)
00:00 +0: loading /config/workspace/appsok/test/widget_test.dart
00:00 +0: keeps the AppSok work app theme baseline
00:00 +1: renders the app shell
00:00 +2: renders shell pages without overflow at compact viewport
00:00 +3: switches between all shell destinations with status placeholders
00:01 +4: All tests passed!
```
---
@ -146,3 +162,17 @@ $ flutter test
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Pre-filled from plan. |
| 검증 결과 | Implementing agent | Fill command output only; command changes require a `계획 대비 변경 사항` entry. |
| 코드리뷰 결과 | Review agent appends | Not included until review. |
## 코드리뷰 결과
- 종합 판정: PASS
- 차원별 평가:
- correctness: Pass
- completeness: Pass
- test coverage: Pass
- API contract: Pass
- code quality: Pass
- plan deviation: Pass
- verification trust: Pass
- 발견된 문제: 없음
- 다음 단계: PASS이므로 `complete.log`를 작성하고 task directory를 archive로 이동한다. `m-product-baseline` 완료 이벤트 메타데이터를 보고하며 roadmap은 code-review에서 직접 수정하지 않는다.

View file

@ -0,0 +1,46 @@
# Complete - m-product-baseline/02+01_responsive_ui
## 완료 일시
2026-06-09
## 요약
RESPONSIVE_UI plan 0 first review completed with PASS; compact viewport overflow coverage and responsive placeholder layout updates are in place.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_local_G05_0.log` | `code_review_local_G05_0.log` | PASS | Overflow-safe shell/page layout matched the plan; no Required/Suggested issues found. |
## 구현/정리 내용
- Made the shell top bar title/status area shrink safely with ellipsis and stable status keys.
- Replaced fixed toolbar rows in build/device/console placeholder pages with wrap-based compact layouts.
- Made the settings placeholder scroll vertically at compact heights.
- Added a compact viewport widget smoke test that cycles build, device, console, and settings pages and checks for overflow exceptions.
## 최종 검증
- `dart format --output=none --set-exit-if-changed lib/src/features/app_shell.dart lib/src/features/builds/builds_page.dart lib/src/features/devices/devices_page.dart lib/src/features/console/console_page.dart lib/src/features/settings/settings_page.dart test/widget_test.dart` - PASS; `Formatted 6 files (0 changed) in 0.05 seconds.`
- `git diff --check` - PASS; output was empty.
- `flutter analyze` - PASS; `No issues found!`
- `flutter test` - PASS; `All tests passed!`
- `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter analyze'` on remote Mac runner - PASS; `No issues found!`
- `zsh -lc 'cd "$HOME/docker/services/code-server/data/volume/workspace/appsok" && flutter test'` on remote Mac runner - PASS; `All tests passed!`
## Roadmap Completion
- Milestone: `agent-roadmap/phase/usable-mvp/milestones/product-baseline.md`
- Completed task ids:
- `responsive-ui`: PASS; evidence=`agent-task/archive/2026/06/m-product-baseline/02+01_responsive_ui/plan_local_G05_0.log`, `agent-task/archive/2026/06/m-product-baseline/02+01_responsive_ui/code_review_local_G05_0.log`; verification=`flutter analyze`, `flutter test`, remote Mac runner `flutter analyze`, remote Mac runner `flutter test`
- Not completed task ids: 없음
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -154,11 +154,27 @@ class _TopBar extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Row(
children: [
Text(title, style: Theme.of(context).textTheme.titleLarge),
const Spacer(),
const _StatusPill(icon: Icons.key_outlined, label: 'Jenkins 대기'),
Expanded(
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.titleLarge,
),
),
const _StatusPill(
key: ValueKey('status-jenkins'),
icon: Icons.key_outlined,
label: 'Jenkins 대기',
tooltip: 'Jenkins 상태 연결 대기',
),
const SizedBox(width: 8),
const _StatusPill(icon: Icons.usb_outlined, label: 'ADB 대기'),
const _StatusPill(
key: ValueKey('status-adb'),
icon: Icons.usb_outlined,
label: 'ADB 대기',
tooltip: 'ADB 상태 연결 대기',
),
],
),
),
@ -167,10 +183,16 @@ class _TopBar extends StatelessWidget {
}
class _StatusPill extends StatelessWidget {
const _StatusPill({required this.icon, required this.label});
const _StatusPill({
super.key,
required this.icon,
required this.label,
required this.tooltip,
});
final IconData icon;
final String label;
final String tooltip;
@override
Widget build(BuildContext context) {
@ -187,7 +209,10 @@ class _StatusPill extends StatelessWidget {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 16, color: colorScheme.onSurfaceVariant),
Tooltip(
message: tooltip,
child: Icon(icon, size: 16, color: colorScheme.onSurfaceVariant),
),
const SizedBox(width: 6),
Text(
label,

View file

@ -14,10 +14,13 @@ class BuildsPage extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
Wrap(
spacing: 12,
runSpacing: 10,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Expanded(
flex: 3,
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 180, maxWidth: 360),
child: TextField(
decoration: InputDecoration(
labelText: 'Jenkins job URL',
@ -30,9 +33,8 @@ class BuildsPage extends StatelessWidget {
),
),
),
const SizedBox(width: 12),
Expanded(
flex: 2,
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 160, maxWidth: 260),
child: TextField(
decoration: InputDecoration(
labelText: '빌드 검색',
@ -45,7 +47,6 @@ class BuildsPage extends StatelessWidget {
),
),
),
const SizedBox(width: 12),
FilledButton.icon(
onPressed: () {},
icon: const Icon(Icons.login),

View file

@ -12,9 +12,13 @@ class ConsolePage extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
Wrap(
spacing: 12,
runSpacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Expanded(
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 160, maxWidth: 320),
child: TextField(
decoration: InputDecoration(
labelText: '패키지 또는 태그',
@ -27,7 +31,6 @@ class ConsolePage extends StatelessWidget {
),
),
),
const SizedBox(width: 12),
SegmentedButton<String>(
segments: const [
ButtonSegment(value: 'all', label: Text('ALL')),
@ -37,17 +40,21 @@ class ConsolePage extends StatelessWidget {
selected: const {'all'},
onSelectionChanged: (_) {},
),
const SizedBox(width: 12),
IconButton.filledTonal(
tooltip: '일시정지',
onPressed: () {},
icon: const Icon(Icons.pause),
),
const SizedBox(width: 8),
IconButton.outlined(
tooltip: '지우기',
onPressed: () {},
icon: const Icon(Icons.delete_outline),
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton.filledTonal(
tooltip: '일시정지',
onPressed: () {},
icon: const Icon(Icons.pause),
),
const SizedBox(width: 8),
IconButton.outlined(
tooltip: '지우기',
onPressed: () {},
icon: const Icon(Icons.delete_outline),
),
],
),
],
),

View file

@ -14,7 +14,10 @@ class DevicesPage extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
Wrap(
spacing: 12,
runSpacing: 8,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
SegmentedButton<String>(
segments: const [
@ -37,7 +40,6 @@ class DevicesPage extends StatelessWidget {
selected: const {'all'},
onSelectionChanged: (_) {},
),
const Spacer(),
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.refresh),

View file

@ -7,7 +7,7 @@ class SettingsPage extends StatelessWidget {
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return Padding(
return SingleChildScrollView(
padding: const EdgeInsets.fromLTRB(28, 0, 28, 28),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,

View file

@ -26,12 +26,54 @@ void main() {
expect(find.text('설정'), findsOneWidget);
});
testWidgets('switches to devices page', (WidgetTester tester) async {
testWidgets('renders shell pages without overflow at compact viewport', (
WidgetTester tester,
) async {
tester.view.physicalSize = const Size(600, 400);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.reset);
await tester.pumpWidget(const AppSokApp());
await tester.tap(find.text('디바이스'));
await tester.pump();
expect(find.text('R5CT90A1B2C'), findsOneWidget);
final navTargets = ['빌드', '디바이스', '콘솔', '설정'];
for (final label in navTargets) {
final navLabel = find.descendant(
of: find.byType(NavigationRail),
matching: find.text(label),
);
await tester.tap(navLabel);
await tester.pumpAndSettle();
expect(tester.takeException(), isNull);
}
});
testWidgets(
'switches between all shell destinations with status placeholders',
(WidgetTester tester) async {
await tester.pumpWidget(const AppSokApp());
final navTargets = <Map<String, String>>[
{'label': '빌드', 'marker': 'Jenkins job URL'},
{'label': '디바이스', 'marker': 'R5CT90A1B2C'},
{'label': '콘솔', 'marker': '패키지 또는 태그'},
{'label': '설정', 'marker': 'Base URL'},
];
for (final target in navTargets) {
final navLabel = find.descendant(
of: find.byType(NavigationRail),
matching: find.text(target['label']!),
);
await tester.tap(navLabel);
await tester.pumpAndSettle();
expect(find.byKey(const ValueKey('status-jenkins')), findsOneWidget);
expect(find.byKey(const ValueKey('status-adb')), findsOneWidget);
expect(find.text('Jenkins 대기'), findsOneWidget);
expect(find.text('ADB 대기'), findsOneWidget);
expect(find.text(target['marker']!), findsOneWidget);
}
},
);
}