<!-- task=flutter_first_portal_migration/02_proto_socket_browser_transport plan=0 tag=PROTO_WS_WEB -->

# Plan - PROTO_WS_WEB

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

구현 마지막 단계에서 `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션을 반드시 실제 변경 내용과 검증 출력으로 채운다. 검증 명령을 실행하고 실제 stdout/stderr를 기록한 뒤 active 파일은 그대로 두고 리뷰 준비 상태만 보고한다. `complete.log` 작성, `.md` 파일 로그화, archive 이동은 code-review-skill 전용이다.

## 배경

proto-socket은 Go와 Dart 모두 WebSocket API를 갖고 있지만 Dart `WsProtobufClient`는 현재 `dart:io` 기반이다. Flutter Web 브라우저 target은 `dart:io`를 쓸 수 없으므로 Portal을 Flutter-first로 전환하려면 browser WebSocket transport가 필요하다. 이 작업은 기존 VM/mobile/desktop API와 테스트를 보존하면서 web compile 경로를 추가한다.

## 분석 결과

### 읽은 파일

- `agent-ops/roadmap/milestones/flutter-first-portal-migration.md`
- `../proto-socket/dart/lib/proto_socket.dart`
- `../proto-socket/dart/lib/src/ws_protobuf_client.dart`
- `../proto-socket/dart/lib/src/transport.dart`
- `../proto-socket/dart/lib/src/base_client.dart`
- `../proto-socket/dart/lib/src/communicator.dart`
- `../proto-socket/dart/lib/src/heartbeat_mixin.dart`
- `../proto-socket/dart/pubspec.yaml`
- `../proto-socket/dart/analysis_options.yaml`
- `../proto-socket/dart/test/socket_test.dart`
- `../proto-socket/dart/test/communicator_test.dart`

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

- VM/mobile/desktop WS: `../proto-socket/dart/test/socket_test.dart:510`부터 plain WS, `:652`부터 WSS, `:763`부터 WS request-response를 검증한다.
- Browser compile: 기존 테스트 없음. 새 compile fixture를 추가해 `package:proto_socket/proto_socket.dart`가 web target에서 `dart:io` 없이 import되는지 `dart compile js`로 검증한다.
- Browser runtime WebSocket binary frame: 실제 브라우저 서버 통신 테스트는 이번 범위에서 제외한다. `05+01,02,04_portal_wire_contract`가 Flutter Web build와 Portal client compile을 검증한다.

### 심볼 참조

- `WsProtobufClient`: `../proto-socket/dart/lib/proto_socket.dart:9`, `../proto-socket/dart/lib/src/ws_protobuf_client.dart:12`, `../proto-socket/dart/test/socket_test.dart:52`, `:61`, `:66`, `:80`, `:103`, `:137`, `:144`, `:159`, `:381`, `:517`, `:587`, `:664`, `:770`.
- `WsProtobufClient.connect`: `../proto-socket/dart/lib/src/ws_protobuf_client.dart:17`, `../proto-socket/dart/test/socket_test.dart:381`, `:517`, `:587`, `:770`.
- `WsProtobufClient.connectSecure`: `../proto-socket/dart/lib/src/ws_protobuf_client.dart:22`, `../proto-socket/dart/test/socket_test.dart:664`.

### 분할 판단

분할 정책을 먼저 평가했다. 이 작업은 shared library API/foundation 변경이고 Flutter app/Control Plane/Docker와 독립 검증된다. 따라서 `flutter_first_portal_migration/02_proto_socket_browser_transport`로 분리한다. 후속 `05+01,02,04_portal_wire_contract`가 이 public API를 소비한다.

### 범위 결정 근거

- IOP 앱 코드와 `apps/portal`은 수정하지 않는다.
- Go proto-socket 구현은 이미 `NewWsServer`, `DialWs`, `DialWss`가 있으므로 수정하지 않는다.
- Dart `WsProtobufServer`는 `dart:io` 서버 API가 필요하므로 web target export에서 제외하거나 IO 전용으로 유지한다. Browser server 구현은 범위 밖이다.

### 빌드 등급

Build lane `cloud-G08`, review lane `cloud-G08`. Public library export와 platform conditional import를 건드리는 protocol/browser compatibility 작업이며 기존 browser runtime 테스트가 약하다.

## 구현 체크리스트

- [x] [PROTO_WS_WEB-1] Dart `WsProtobufClient`를 IO 구현과 Web 구현으로 분리하고 conditional export를 적용한다.
- [x] [PROTO_WS_WEB-2] Browser import compile fixture와 기존 VM WS 회귀 검증을 추가한다.
- [x] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [PROTO_WS_WEB-1] Conditional client 구현

문제: `../proto-socket/dart/lib/src/ws_protobuf_client.dart:3`이 `dart:io`를 직접 import하고, `proto_socket.dart:9`가 이를 무조건 export한다. Flutter Web에서 package import만 해도 `dart:io` 때문에 compile이 깨질 수 있다.

해결 방법: 기존 파일의 IO 구현을 `ws_protobuf_client_io.dart`로 옮기고, web 구현 `ws_protobuf_client_web.dart`를 추가한다. `proto_socket.dart`는 conditional export를 사용한다. 양쪽 모두 public class name은 `WsProtobufClient`로 유지하고, `connect`, `connectSecure`, constructor 기반 subclass 패턴을 기존 테스트가 쓰는 형태로 보존한다.

Before:

```dart
../proto-socket/dart/lib/proto_socket.dart:9
export 'src/ws_protobuf_client.dart';

../proto-socket/dart/lib/src/ws_protobuf_client.dart:3
import 'dart:io';
```

After:

```dart
export 'src/ws_protobuf_client_io.dart'
    if (dart.library.html) 'src/ws_protobuf_client_web.dart';
```

수정 파일 및 체크리스트:

- [x] `../proto-socket/dart/lib/proto_socket.dart`: conditional export로 교체한다.
- [x] `../proto-socket/dart/lib/src/ws_protobuf_client_io.dart`: 기존 구현을 이동하고 VM 테스트 API를 보존한다.
- [x] `../proto-socket/dart/lib/src/ws_protobuf_client_web.dart`: `dart:html` WebSocket 기반 binary transport를 구현한다.
- [x] `../proto-socket/dart/lib/src/ws_protobuf_client.dart`: 남기지 않거나 compatibility shim으로만 남긴다. 무조건 `dart:io`를 import하지 않는다.
- [x] web 구현은 `ByteBuffer`, `Uint8List`, `Blob` 메시지를 bytes로 변환하고, 송신은 `PacketBase.writeToBuffer()`의 bytes를 binary frame으로 보낸다.

테스트 작성: browser runtime 테스트는 skip한다. 대신 compile fixture와 기존 VM WS tests를 사용한다. Browser WebSocket 서버/런타임 검증은 실제 Flutter Portal 연결 작업인 `05+01,02,04_portal_wire_contract`로 넘긴다.

중간 검증:

```bash
cd ../proto-socket/dart && dart analyze
```

기대 결과: analyzer 경고/오류 없음.

### [PROTO_WS_WEB-2] Browser compile fixture

문제: `../proto-socket/dart/test/socket_test.dart:1`은 `dart:io` VM 테스트라 browser import 가능성을 검증하지 않는다.

해결 방법: `../proto-socket/dart/test/browser_ws_import_compile.dart`를 추가한다. 이 fixture는 `package:proto_socket/proto_socket.dart`를 import하고 `WsProtobufClient`, `Transport`, `PacketBase`를 type-level로 참조한다. `dart compile js`로 web compile 가능성을 검증한다.

Before:

```dart
../proto-socket/dart/test/socket_test.dart:1
import 'dart:async';
import 'dart:io';
```

After:

```dart
import 'package:proto_socket/proto_socket.dart';

void main() {
  final types = <Type>[WsProtobufClient, PacketBase, Transport];
  if (types.isEmpty) {
    throw StateError('unreachable');
  }
}
```

수정 파일 및 체크리스트:

- [x] `../proto-socket/dart/test/browser_ws_import_compile.dart`: web compile fixture를 추가한다.
- [x] `../proto-socket/dart/test/socket_test.dart`: 기존 imports와 VM tests가 새 IO 파일에서도 그대로 통과하도록 필요시 import/type inference만 조정한다. (조정 불필요. conditional export가 VM target에서 IO 파일로 분기되므로 기존 imports/types 그대로 통과)
- [x] `../proto-socket/dart/README.md`가 있으면 browser support와 server IO-only 경계를 짧게 갱신한다. 없으면 새 문서를 만들지 않는다. (해당 파일 없음, skip)

테스트 작성: 작성한다. `browser_ws_import_compile.dart` 자체가 compile fixture다.

중간 검증:

```bash
cd ../proto-socket/dart && dart compile js -O1 -o /tmp/proto_socket_browser_compile.js test/browser_ws_import_compile.dart
```

기대 결과: `dart:io` compile 오류 없이 JS 산출물이 `/tmp`에 생성된다. `/tmp` 산출물은 repo에 남기지 않는다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `../proto-socket/dart/lib/proto_socket.dart` | PROTO_WS_WEB-1 |
| `../proto-socket/dart/lib/src/ws_protobuf_client_io.dart` | PROTO_WS_WEB-1 |
| `../proto-socket/dart/lib/src/ws_protobuf_client_web.dart` | PROTO_WS_WEB-1 |
| `../proto-socket/dart/lib/src/ws_protobuf_client.dart` | PROTO_WS_WEB-1 |
| `../proto-socket/dart/test/browser_ws_import_compile.dart` | PROTO_WS_WEB-2 |
| `../proto-socket/dart/test/socket_test.dart` | PROTO_WS_WEB-2 |

## 최종 검증

```bash
cd ../proto-socket/dart && dart format --set-exit-if-changed lib test
cd ../proto-socket/dart && dart analyze
cd ../proto-socket/dart && dart test -r compact test/socket_test.dart test/communicator_test.dart
cd ../proto-socket/dart && dart compile js -O1 -o /tmp/proto_socket_browser_compile.js test/browser_ws_import_compile.dart
git -C ../proto-socket diff --check
```

기대 결과: format/analyze/test/browser compile 모두 통과한다. Dart test cache는 허용하지 않는다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
