<!-- task=m-control-plane-client/02+01_client_lifecycle_ui plan=0 tag=CLIENTOPS -->

# Client Lifecycle UI Plan

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

구현 후 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 실제 구현 내용, 검증 명령, 출력 요약으로 채운다. 검증을 실행하고 active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 최종 판정, log rename, `complete.log` 작성은 code-review 스킬 책임이다. 사용자만 결정할 수 있는 blocker가 있으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정, 증거, 재개 조건을 채우고 멈춘다.

## 배경

Client는 Control Plane wire hello badge와 endpoint 표시만 제공하고, Edge/Node lifecycle 상태를 실제로 조회하지 않는다. `01_control_plane_status_api`가 HTTP JSON contract를 만든 뒤, Client는 그 API를 소비해 Edges, Nodes, Execution & Logs rail에 얇은 운영 상태를 표시해야 한다.

## 사용자 리뷰 요청 흐름

구현 중 사용자 결정이 필요한 blocker는 `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 정당성을 검증해 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/control-plane-portal-ops/milestones/control-plane-client.md`
- Task ids:
  - `client-status`: Client는 Edge/Node 설정 요약, registry, 연결 상태, 로그/이벤트를 lifecycle 중심으로 얇게 보여줄 수 있다.
  - `client-via-cp`: Client는 Control Plane을 통해 Edge/Node 운영 lifecycle 상태를 조회한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/control-plane-portal-ops/PHASE.md`
- `agent-roadmap/phase/control-plane-portal-ops/milestones/control-plane-client.md`
- `agent-ops/rules/project/domain/control-plane/rules.md`
- `agent-ops/rules/project/domain/client/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/control-plane-smoke.md`
- `agent-test/local/client-smoke.md`
- `apps/control-plane/cmd/control-plane/main.go`
- `apps/control-plane/cmd/control-plane/main_test.go`
- `apps/client/lib/main.dart`
- `apps/client/test/widget_test.dart`
- `apps/client/pubspec.yaml`
- `apps/client/lib/client_config.dart`
- `packages/flutter/iop_console/lib/src/iop_console_overview.dart`
- `packages/flutter/iop_console/lib/src/iop_console_shell.dart`
- `packages/flutter/iop_console/lib/src/iop_console_contract.dart`
- `packages/flutter/iop_console/test/iop_console_shell_test.dart`

### 테스트 환경 규칙

- `test_env`: local.
- `agent-test/local/rules.md`, `agent-test/local/client-smoke.md`, `agent-test/local/control-plane-smoke.md`를 읽었다.
- Client 변경 필수 검증: 원격 runner 기준 `cd apps/client && flutter test`.
- `packages/flutter/iop_console` API/widget 구조를 바꾸면 원격 runner 기준 `cd packages/flutter/iop_console && flutter test`.
- 현재 작업 컨테이너 변경분이 원격 checkout에 동기화되지 않았으면 원격 검증 완료로 보지 않는다.

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

- Client HTTP 상태 fetcher/model: 현재 없음.
- Edges/Nodes/Logs rail 실제 상태 표시: 현재 placeholder라 widget coverage 없음.
- Wire hello badge: `apps/client/test/widget_test.dart`가 success/error를 검증한다.
- `IopConsoleShell` injection slots: package test가 injected execution logs를 검증하므로 app-level injected widgets로 확장 가능하다.

### 심볼 참조

- renamed/removed symbols: none.
- `IopConsoleShell` constructor는 `edges`, `nodes`, `executionLogs` slot을 이미 제공한다.

### 분할 판단

- split decision policy를 평가했다.
- 이 subtask는 `02+01_client_lifecycle_ui`이며 predecessor `01_control_plane_status_api` 완료가 필요하다.
- predecessor 확인: 현재 active `agent-task/m-control-plane-client/01_control_plane_status_api/complete.log` 없음. 이 plan은 대기 상태로 두고, runtime은 01 완료 후 실행해야 한다.
- API contract가 확정되기 전 Client JSON model을 구현하면 재작업 위험이 있으므로 의존 split이 맞다.

### 범위 결정 근거

- `packages/flutter/iop_console`는 가능하면 변경하지 않고 `IopConsoleShell` slot에 app-owned widgets를 주입한다.
- NomadCode workspace/diff/PR UX, 설정 변경, 명령 실행, 권한 조작 UX는 제외한다.
- Web build, Docker compose, Firebase/Mattermost integration은 상태 화면 구현 범위가 아니므로 건드리지 않는다.

### 빌드 등급

- lane/GNN: `cloud-G07`.
- 이유: Flutter UI, HTTP integration, 원격 runner 검증 조건이 결합되어 있고 predecessor API contract에 의존한다.

## 의존 관계 및 구현 순서

1. `agent-task/m-control-plane-client/01_control_plane_status_api/complete.log`가 생긴 뒤 이 plan을 구현한다.
2. 01의 JSON contract와 test output을 확인하고 Client model/parser를 맞춘다.
3. app-level widgets를 만들고 `IopConsoleShell` slots에 연결한다.

## 구현 체크리스트

- [ ] `01_control_plane_status_api`의 `complete.log`와 HTTP JSON contract를 확인한다.
- [ ] `apps/client/lib/main.dart` 또는 작은 app-owned 파일에 Control Plane HTTP client/model을 추가하고 `/edges`, `/edges/{id}/status`, `/edges/{id}/events`를 조회한다.
- [ ] `IopConsoleShell`의 `edges`, `nodes`, `executionLogs` slot에 실제 lifecycle widgets를 주입하고 refresh가 wire reconnect와 HTTP reload를 함께 수행하게 한다. `client-via-cp` 검증: Client가 Control Plane HTTP URL을 통해 Edge/Node 상태를 조회한다.
- [ ] `apps/client/test/widget_test.dart`에 fake HTTP data 또는 injectable repository를 사용해 Edge 연결 상태, Node registry/config summary, event/log 표시를 검증한다. `client-status` 검증: Client 상태 화면 또는 상태 표시 경로를 확인한다.
- [ ] 원격 runner 또는 동기화된 환경에서 `cd apps/client && flutter test`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [CLIENTOPS-1] HTTP Model And Repository

문제: [main.dart](/config/workspace/iop/apps/client/lib/main.dart:154)는 wire hello만 수행하고 HTTP 상태 fetch가 없다.

해결 방법: `ControlPlaneStatusClient` 같은 app-owned class를 만들고 `http` dependency를 사용해 01에서 확정된 endpoints를 fetch한다. 테스트 가능하도록 `ClientHomePage`에 optional repository/client 주입점을 둔다.

수정 파일 및 체크리스트:

- [ ] `apps/client/lib/main.dart` 또는 `apps/client/lib/control_plane_status_client.dart`
- [ ] `apps/client/test/widget_test.dart`

테스트 작성: 작성한다. fake client/repository로 JSON 없이 widget state를 안정적으로 검증한다.

중간 검증: `cd apps/client && flutter test test/widget_test.dart`.

### [CLIENTOPS-2] Lifecycle Panels

문제: [iop_console_shell.dart](/config/workspace/iop/packages/flutter/iop_console/lib/src/iop_console_shell.dart:90)의 Edges/Nodes/Execution panels는 app에서 주입하지 않으면 placeholder이고, 현재 [main.dart](/config/workspace/iop/apps/client/lib/main.dart:205)는 `overview`와 `agent`만 주입한다.

해결 방법: app-owned Edge, Node, Logs widgets를 작성해 `IopConsoleShell(edges: ..., nodes: ..., executionLogs: ...)`에 주입한다. 화면은 조회 중심으로 유지하고 설정 변경/명령 버튼은 넣지 않는다.

수정 파일 및 체크리스트:

- [ ] `apps/client/lib/main.dart` 또는 분리된 widget 파일
- [ ] `apps/client/test/widget_test.dart`

테스트 작성: 작성한다. rail tap 후 Edge/Node/event 텍스트가 표시되는지 확인한다.

중간 검증: `cd apps/client && flutter test test/widget_test.dart`.

### [CLIENTOPS-3] Refresh And State Handling

문제: [iop_console_overview.dart](/config/workspace/iop/packages/flutter/iop_console/lib/src/iop_console_overview.dart:158)는 wire status만 요약하고 HTTP fetch loading/error/empty 상태를 모른다.

해결 방법: package overview 변경 없이 app-level panel에 loading/error/empty state를 넣고, overview refresh callback에서 wire reconnect와 HTTP reload를 함께 호출한다.

수정 파일 및 체크리스트:

- [ ] `apps/client/lib/main.dart` 또는 status controller 파일
- [ ] `apps/client/test/widget_test.dart`

테스트 작성: 작성한다. success/error/empty 상태를 fake repository로 검증한다.

중간 검증: `cd apps/client && flutter test test/widget_test.dart`.

## 수정 파일 요약

| 파일 | 항목 |
| --- | --- |
| `apps/client/lib/main.dart` | CLIENTOPS-1, CLIENTOPS-2, CLIENTOPS-3 |
| `apps/client/test/widget_test.dart` | CLIENTOPS-1, CLIENTOPS-2, CLIENTOPS-3 |
| `apps/client/lib/control_plane_status_client.dart` | CLIENTOPS-1 |
| `apps/client/lib/control_plane_status_widgets.dart` | CLIENTOPS-2, CLIENTOPS-3 |

## 최종 검증

```bash
cd apps/client && flutter test
```

필요 시 package slot 계약 변경이 생긴 경우:

```bash
cd packages/flutter/iop_console && flutter test
```

예상 결과: Flutter tests가 모두 통과하고 Edge/Node/events 상태 표시 widget assertions가 성공한다.

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