<!-- task=m-proto-socket-infrastructure-communication-rail/03+02_workspace_integration plan=0 tag=API -->

# Plan - API Workspace Task Integration

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

이 plan의 구현이 끝나면 active `CODE_REVIEW-*-G??.md`에서 구현 에이전트 소유 섹션을 실제 구현 내용, 검증 출력, 계획 대비 변경 사항으로 채워야 한다. 최종 archive, `complete.log`, 리뷰 판정은 code-review-skill 전용이다. 구현 중 사용자만 결정할 수 있는 범위 변경, 사용자 소유 외부 환경/secret 준비, 또는 안전한 진행을 막는 scope 충돌이 있으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정과 근거를 남기고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 메울 수 있는 검증 공백만으로는 사용자 리뷰 요청을 만들지 않는다.

## 배경

Workspace UI still renders mock task rows from `tasksCount`. After the transport and task service exist, the app should consume Core proto-socket task channel through a thin service boundary while keeping mock fallback for disabled config and widget tests.

## 사용자 리뷰 요청 흐름

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

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/workflow-core/PHASE.md`
- `agent-roadmap/phase/workflow-core/milestones/proto-socket-infrastructure-communication-rail.md`
- `agent-ops/rules/project/domain/mobile/rules.md`
- `agent-test/local/mobile-smoke.md`
- `apps/client/lib/src/app/bootstrap.dart`
- `apps/client/lib/src/app/nomadcode_client_app.dart`
- `apps/client/lib/src/features/workspaces/domain/project_workspace.dart`
- `apps/client/lib/src/features/workspaces/presentation/workspace_home_page.dart`
- `apps/client/test/widget_test.dart`

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

- Existing widget tests assert mock workspace names and launcher callback only.
- No widget test covers task loading, empty task state from a service, failed task load, or enabled proto-socket lifecycle connection.

### 심볼 참조

- `WorkspaceHomePage`: constructed in `apps/client/lib/src/app/nomadcode_client_app.dart:93-96` and `apps/client/test/widget_test.dart:23-31`; update both call sites if constructor adds service dependencies.
- `mockProjectWorkspaces`: consumed in `apps/client/lib/src/features/workspaces/presentation/workspace_home_page.dart:20-21` and `146-150`; keep as default fallback or move behind a repository/fallback argument.

### 분할 판단

Split policy was evaluated before writing files. This subtask depends on `02+01_task_service` because UI should consume `ProtoSocketTaskService` and `WorkspaceTask`. It is separate from service parsing so UI state regressions can be reviewed with widget tests.

### 범위 결정 근거

This task does not change transport internals, task channel parsing, Core Go code, platform runner files, Firebase, or Mattermost notification behavior. It only wires app/page state to the service boundary and adds widget tests.

### 빌드 등급

Build lane `local-G05`, review lane `cloud-G05`: mostly Flutter state/widget integration with existing testable boundaries; review remains cloud because it depends on prior protocol subtasks.

## 구현 체크리스트

- [ ] [API-1] Wire `NomadCodeClientApp` and home state to connect proto-socket only when config is non-null and enabled.
- [ ] [API-2] Update `WorkspaceHomePage` to accept a task loader/service boundary and render loaded `WorkspaceTask` rows instead of generated mock task rows when service data is available.
- [ ] [API-3] Add widget tests for task service rendering, empty state, failed load state, and existing launcher callback.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [API-1] App Lifecycle Wiring

### 문제

`NomadCodeClientApp` receives `protoSocketLifecycle` and `protoSocketConfig` at `apps/client/lib/src/app/nomadcode_client_app.dart:15-23`, but `_NomadCodeClientHome` drops them at `apps/client/lib/src/app/nomadcode_client_app.dart:38` and never connects.

### 해결 방법

Pass lifecycle/config into `_NomadCodeClientHome`, connect only when `config.enabled`, and create task service from `lifecycle.transport` after connection.

Before (`apps/client/lib/src/app/nomadcode_client_app.dart:28-39`):

```dart
return MaterialApp(
  title: 'nomadcode-app',
  navigatorKey: navigatorKey,
  theme: ThemeData(...),
  home: _NomadCodeClientHome(mattermostHost: mattermostHost),
);
```

After:

```dart
return MaterialApp(
  title: 'nomadcode-app',
  navigatorKey: navigatorKey,
  theme: ThemeData(...),
  home: _NomadCodeClientHome(
    mattermostHost: mattermostHost,
    protoSocketLifecycle: protoSocketLifecycle,
    protoSocketConfig: protoSocketConfig,
  ),
);
```

In `initState`, call an async `_connectProtoSocketIfEnabled()` that catches errors and leaves the page usable with mock fallback.

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

- [ ] Update `apps/client/lib/src/app/nomadcode_client_app.dart`.
- [ ] Do not connect when `protoSocketConfig == null` or `enabled == false`.
- [ ] Cancel subscriptions and disconnect lifecycle only if this widget initiated the connection.

### 테스트 작성

Add widget tests with fake lifecycle or injectable task loader. Avoid Firebase/bootstrap tests.

### 중간 검증

```bash
cd apps/client && flutter test test/widget_test.dart
```

Expected: widget tests pass.

## [API-2] Workspace Task Rendering

### 문제

Workspace page uses mock count and generated task labels at `apps/client/lib/src/features/workspaces/presentation/workspace_home_page.dart:356-416`, so it cannot show Core task channel data.

### 해결 방법

Add a task loader dependency to `WorkspaceHomePage`.

Before (`apps/client/lib/src/features/workspaces/presentation/workspace_home_page.dart:4-7`):

```dart
class WorkspaceHomePage extends StatefulWidget {
  final Future<bool> Function(ProjectWorkspace)? onLaunchCodeServer;

  const WorkspaceHomePage({super.key, this.onLaunchCodeServer});
}
```

After:

```dart
typedef WorkspaceTaskLoader = Future<List<WorkspaceTask>> Function(
  ProjectWorkspace project,
);

class WorkspaceHomePage extends StatefulWidget {
  final Future<bool> Function(ProjectWorkspace)? onLaunchCodeServer;
  final WorkspaceTaskLoader? loadTasks;

  const WorkspaceHomePage({super.key, this.onLaunchCodeServer, this.loadTasks});
}
```

When `loadTasks` is null, keep current mock behavior. When present, load selected project tasks, render title/status from `WorkspaceTask`, and show deterministic loading/error/empty states.

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

- [ ] Update `apps/client/lib/src/features/workspaces/presentation/workspace_home_page.dart`.
- [ ] Import `WorkspaceTask`.
- [ ] Preserve current launcher button behavior.
- [ ] Keep text within existing compact rows; do not redesign the page.

### 테스트 작성

Add widget tests for:

- Service returns two tasks and page renders their titles/statuses.
- Service returns empty list and page renders the existing empty state text or an equivalent task-empty state.
- Service throws and page renders a non-secret error state with retry affordance.

### 중간 검증

```bash
cd apps/client && flutter test test/widget_test.dart
```

Expected: widget tests pass.

## [API-3] Widget Test Coverage

### 문제

`apps/client/test/widget_test.dart:7-42` only covers the dashboard title and launcher callback.

### 해결 방법

Extend `widget_test.dart` with fake task loaders. Keep existing tests updated to pass any new constructor parameters.

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

- [ ] Update `apps/client/test/widget_test.dart`.
- [ ] Keep existing launcher callback test green.
- [ ] Assert selected default project still starts with NomadCode Core.

### 테스트 작성

This item is the widget test implementation for API-1 and API-2.

### 중간 검증

```bash
cd apps/client && flutter test test/widget_test.dart
```

Expected: widget tests pass.

## 의존 관계 및 구현 순서

Directory dependency `03+02_workspace_integration` means implementation must start only after `agent-task/m-proto-socket-infrastructure-communication-rail/02+01_task_service/complete.log` exists.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/client/lib/src/app/nomadcode_client_app.dart` | API-1 |
| `apps/client/lib/src/features/workspaces/presentation/workspace_home_page.dart` | API-2 |
| `apps/client/test/widget_test.dart` | API-1, API-2, API-3 |

## 최종 검증

```bash
test -f agent-task/m-proto-socket-infrastructure-communication-rail/02+01_task_service/complete.log
cd apps/client && flutter test test/widget_test.dart
cd apps/client && flutter test
cd apps/client && flutter analyze --no-fatal-infos
```

Expected: predecessor complete.log exists; widget tests and full Flutter tests pass; analyzer has no fatal issues. Cached Flutter test output is acceptable only if no implementation files changed after the run.

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