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

# Code Review Reference - PROTO_WS_WEB

> **[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.
> 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-05-24
task=flutter_first_portal_migration/02_proto_socket_browser_transport, plan=0, tag=PROTO_WS_WEB

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G08.md` → `code_review_cloud_G08_N.log`, `PLAN-cloud-G08.md` → `plan_cloud_G08_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/flutter_first_portal_migration/02_proto_socket_browser_transport/`로 이동한다. WARN/FAIL이면 다음 active plan/review 파일을 즉시 작성한다.
4. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [PROTO_WS_WEB-1] Dart `WsProtobufClient`를 IO 구현과 Web 구현으로 분리하고 conditional export를 적용한다. | [x] |
| [PROTO_WS_WEB-2] Browser import compile fixture와 기존 VM WS 회귀 검증을 추가한다. | [x] |

## 구현 체크리스트

- [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의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G08_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G08_M.log`로 아카이브한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/flutter_first_portal_migration/02_proto_socket_browser_transport/`를 `agent-task/archive/YYYY/MM/flutter_first_portal_migration/02_proto_socket_browser_transport/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/flutter_first_portal_migration/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이면 다음 active `PLAN-cloud-G08.md`와 `CODE_REVIEW-cloud-G08.md`를 작성하고 `complete.log`를 작성하지 않는다.

## 계획 대비 변경 사항

1. **TCP/WS 서버 web stub 추가 (계획 미명시)**. 계획은 `WsProtobufClient`만 conditional export 대상으로 명시했지만, `lib/proto_socket.dart`가 기존에 `protobuf_client.dart`, `protobuf_server.dart`, `ws_protobuf_server.dart`도 무조건 export하고 있고 세 파일 모두 `dart:io`를 import한다. Web 컴파일에서 `package:proto_socket/proto_socket.dart`를 import만 해도 이 IO-only 파일들이 transitively 끌려와 컴파일이 깨진다. 따라서 다음 stub 3개를 추가하고 conditional export를 적용했다.
   - `lib/src/protobuf_client_web.dart`
   - `lib/src/protobuf_server_web.dart`
   - `lib/src/ws_protobuf_server_web.dart`
   계획 자체에 "Dart `WsProtobufServer`는 ... web target export에서 제외하거나 IO 전용으로 유지한다"는 문구가 있으므로 의도와 충돌하지 않으며, browser 서버 구현은 그대로 범위 밖이다. VM target에서는 conditional export가 IO 파일로 분기되므로 기존 socket_test.dart의 `ProtobufClient`, `ProtobufServer`, `WsProtobufServer` 사용에 영향이 없다.

2. **`ws_protobuf_client.dart`는 compatibility shim을 남기지 않고 완전히 삭제**했다. 계획이 "남기지 않거나 compatibility shim으로만 남긴다"는 두 옵션을 허용했고, shim을 두면 `dart:io`를 transitively 끌어오므로 web 분리 의도와 어긋난다. 유일한 in-package consumer였던 `lib/src/ws_protobuf_server.dart`의 import를 `ws_protobuf_client_io.dart`로 직접 변경했다.

3. **최종 검증 `dart format` 명령을 파일 화이트리스트 형태로 실행**했다. 계획의 `dart format --set-exit-if-changed lib test`를 그대로 실행하면 `lib/src/packets/` 하위 protobuf-generated 4개 파일이 현 Dart SDK 기준으로 포맷 변경 대상이 되어 exit 1을 반환한다. 이 4개 파일은 헤더에 `// Generated code. Do not modify.`를 명시한 자동 생성 산출물이고 본 작업 범위(WsProtobufClient web 분리) 밖이라 변경하지 않는 것이 옳다. 따라서 본 작업이 추가/수정한 파일과 기존 hand-written 소스만 명시하는 형태로 화이트리스트를 만들어 실행했다. 실행 결과 17개 파일 모두 0 changes이다. 자세한 명령은 `최종 검증` 절에 기록한다.

4. **`ws_protobuf_client_web.dart`의 ignore_for_file에 `deprecated_member_use` 추가**. 계획이 명시한 `dart:html` 사용은 현 Dart SDK에서 deprecated info를 띄우지만 plan 지시가 명확하므로 그대로 사용하면서 analyzer 출력만 깨끗하게 유지하기 위해 file-level ignore를 적용했다.

## 주요 설계 결정

1. **Conditional export는 라이브러리 진입 파일 `lib/proto_socket.dart` 한 곳에서만 한다**. consumer는 항상 `package:proto_socket/proto_socket.dart` 하나만 import하면 되고, IO/web 분기는 빌드 target이 알아서 처리한다.

2. **IO/Web 양쪽 모두 public class 이름은 `WsProtobufClient`로 통일**. `socket_test.dart`의 `_TestWsClient extends WsProtobufClient`와 Flutter Web target Portal 코드가 동일한 심볼을 참조할 수 있다. 각 파일이 정의하는 `WsProtobufClient`는 platform-specific WebSocket 타입을 다루지만, 한 컴파일 단위에서는 하나만 보이므로 충돌하지 않는다.

3. **Web `connect`/`connectSecure`는 SecurityContext를 받지 않는다**. 브라우저는 user agent의 trust store가 TLS를 처리하므로 SecurityContext API를 노출할 의미가 없다. IO 측 시그니처와 정확히 동일할 필요는 없고, 각 target이 자기 platform의 statics를 호출한다.

4. **Web client는 binary frame만 사용**한다. 생성 시 `ws.binaryType = 'arraybuffer'`를 세팅하고 `MessageEvent.data`에서 `ByteBuffer`/`Uint8List`/`List<int>`/`Blob`을 받아 `List<int>`로 정규화한 뒤 `PacketBase.fromBuffer`에 넘긴다. Blob은 user agent가 fallback으로 줄 가능성에 대비해 `FileReader.readAsArrayBuffer`로 비동기 변환을 처리한다.

5. **Web `_open`은 `onOpen`/`onError`를 race**시켜 두 subscription을 모두 cleanup한다. `Future.any`를 쓰지 않고 명시적으로 cleanup하는 이유는 race-lose 측 stream이 leak되지 않도록 하기 위해서다.

6. **Web side `close` 트리거는 `onClose` 스트림**으로 한다. IO 쪽은 `_ws.listen(onDone: close)`로 단일 listener에서 처리되지만, `dart:html`은 message/close가 별도 스트림이므로 `onClose.listen((_) => close())`로 명시 연결한다.

7. **Web stub 파일들은 어떤 심볼도 export하지 않는 단순 주석 파일**로 둔다. 빈 라이브러리도 valid Dart이고, web target에서 `ProtobufClient`/`ProtobufServer`/`WsProtobufServer` 심볼은 의도적으로 사용 불가다. 사용을 시도하는 web 코드는 컴파일 단계에서 명확히 실패한다.

## 리뷰어를 위한 체크포인트

- `proto_socket.dart`가 web target에서 `dart:io` 구현을 무조건 export하지 않는지 확인한다.
- 기존 VM `socket_test.dart`의 subclass/constructor 패턴이 깨지지 않았는지 확인한다.
- web compile fixture가 `/tmp` 산출물만 만들고 repo에 verification artifact를 남기지 않는지 확인한다.
- `WsProtobufServer`가 IO-only로 남는 경계가 명확한지 확인한다.

## 검증 결과

_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.

### PROTO_WS_WEB-1 중간 검증
```bash
$ cd ../proto-socket/dart && dart analyze
Analyzing dart...
No issues found!
```

### PROTO_WS_WEB-2 중간 검증
```bash
$ cd ../proto-socket/dart && dart compile js -O1 -o /tmp/proto_socket_browser_compile.js test/browser_ws_import_compile.dart
Compiled 10,582,086 input bytes (5,583,313 characters source) to 11,682 characters JavaScript in 0.38 seconds
```

### 최종 검증

> 계획의 `dart format --set-exit-if-changed lib test`는 `lib/src/packets/` 하위 protobuf-generated 4개 파일의 포맷 차이로 exit 1을 낸다. 이 파일들은 헤더에 `Generated code. Do not modify.`가 명시되어 있고 본 작업 범위 밖이므로 손대지 않는다. 대신 본 작업이 추가/수정한 모든 파일과 기존 hand-written 소스를 화이트리스트로 명시해 동일한 의도의 포맷 검증을 수행했다. 자세한 사유는 위 `계획 대비 변경 사항` 3번 참고.

```bash
$ cd ../proto-socket/dart && dart format --set-exit-if-changed \
    lib/proto_socket.dart \
    lib/src/base_client.dart \
    lib/src/communicator.dart \
    lib/src/heartbeat_mixin.dart \
    lib/src/protobuf_client.dart \
    lib/src/protobuf_client_web.dart \
    lib/src/protobuf_server.dart \
    lib/src/protobuf_server_web.dart \
    lib/src/response_checker.dart \
    lib/src/transport.dart \
    lib/src/ws_protobuf_client_io.dart \
    lib/src/ws_protobuf_client_web.dart \
    lib/src/ws_protobuf_server.dart \
    lib/src/ws_protobuf_server_web.dart \
    test/communicator_test.dart \
    test/socket_test.dart \
    test/browser_ws_import_compile.dart
Formatted 17 files (0 changed) in 0.03 seconds.

$ cd ../proto-socket/dart && dart analyze
Analyzing dart...
No issues found!

$ cd ../proto-socket/dart && dart test -r compact test/socket_test.dart test/communicator_test.dart
00:00 +0: loading test/socket_test.dart
00:00 +0: test/socket_test.dart: ProtobufServer (plain) 서버가 정상 시작된다
...
00:15 +47: Request-Response (WS plain) WS sendRequest로 서버 응답을 받는다
00:15 +48: Request-Response (WS plain) WS 여러 sendRequest가 각각 올바른 응답을 받는다
00:15 +48: All tests passed!

# 전체 48 tests 통과. 분기별 그룹: ProtobufServer/Client (plain), Heartbeat timeout (TCP+WS),
# ProtobufServer/Client (SSL), WsProtobufServer/Client (plain), WsProtobufServer/Client (SSL),
# Request-Response (TCP plain), Request-Response (WS plain), Communicator protocol guards.

$ cd ../proto-socket/dart && dart compile js -O1 -o /tmp/proto_socket_browser_compile.js test/browser_ws_import_compile.dart
Compiled 10,582,086 input bytes (5,583,313 characters source) to 11,682 characters JavaScript in 0.39 seconds

$ git -C ../proto-socket diff --check
(no output, exit 0)
```

`/tmp/proto_socket_browser_compile.js` 및 `.map` 산출물은 검증 후 즉시 삭제했다(repo 외부 `/tmp` 하위로만 발생).

---

> **[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.

## Section Ownership

| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` to `[x]` only |
| 구현 체크리스트 | Implementing agent | Check `[ ]` to `[x]` only |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholders with actual content |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus areas |
| 검증 결과 | Implementing agent | Paste actual stdout/stderr only |
| 코드리뷰 결과 | Review agent appends | Not included in stub |

## 코드리뷰 결과

- `종합 판정`: PASS
- `차원별 평가`
  - correctness: Pass
  - completeness: Pass
  - test coverage: Pass
  - API contract: Pass
  - code quality: Pass
  - plan deviation: Pass
  - verification trust: Pass
- `발견된 문제`: 없음
- `다음 단계`: PASS - `complete.log` 작성 후 `agent-task/archive/2026/05/flutter_first_portal_migration/02_proto_socket_browser_transport/`로 이동한다.
