<!-- task=flutter_first_portal_migration/03+01_web_delete_flutter_deploy plan=0 tag=WEB_DEPLOY -->

# Plan - WEB_DEPLOY

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

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

## 의존 관계 및 구현 순서

이 subtask directory는 `03+01_web_delete_flutter_deploy`이므로 같은 task group의 `01_flutter_portal_scaffold`에 `complete.log`가 생긴 뒤 시작한다. 추가 의존성은 없다.

## 배경

사용자는 TypeScript 기반 웹을 삭제하고 기존 Docker 배포도 Flutter Web 흐름으로 고치는지 확인했다. `apps/web`의 Next.js scaffold와 npm/standalone Docker는 Flutter-first 기준과 충돌한다. 이 작업은 Flutter 앱이 생긴 뒤 legacy Web Portal 제품 경로를 제거하고 compose `web` 서비스를 Flutter Web static serving으로 바꾼다.

## 분석 결과

### 읽은 파일

- `agent-ops/roadmap/milestones/flutter-first-portal-migration.md`
- `README.md`
- `docs/architecture.md`
- `.gitignore`
- `Makefile`
- `bin/web.sh`
- `docker-compose.yml`
- `apps/web/README.md`
- `apps/web/Dockerfile`
- `apps/web/.dockerignore`
- `apps/web/package.json`
- `apps/web/next.config.ts`
- `apps/web/tsconfig.json`
- `apps/web/postcss.config.mjs`
- `apps/web/components.json`
- `apps/web/next-env.d.ts`
- `apps/web/src/app/page.tsx`
- `apps/web/src/app/layout.tsx`
- `apps/web/src/app/globals.css`
- `apps/web/src/components/ui/button.tsx`
- `apps/web/src/lib/iop-client/client.ts`
- `apps/web/src/lib/iop-client/connection.ts`
- `apps/web/src/lib/iop-client/types.ts`
- `apps/web/src/lib/utils.ts`

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

- Next.js 삭제: 기존 npm verify는 삭제 대상이다. 잔여 TypeScript/Next/npm path를 `rg --sort path`로 검증해야 한다.
- Docker Flutter Web build: Docker CLI가 현재 환경에 없을 수 있다. `command -v docker`를 먼저 기록하고, 사용 가능하면 `docker compose config`와 `docker compose build web`을 실행한다. 없으면 정적 파일 검증과 Flutter build까지만 기록하고 blocker로 남긴다.
- `bin/web.sh` Flutter dev flow: `flutter build web`은 검증하지만 장시간 dev server 실행은 이번 plan의 pass 조건에서 제외한다.

### 심볼 참조

- 삭제 대상 Next/npm references:
  - `bin/web.sh:11` `CONTROL_PLANE_INTERNAL_HTTP_URL`
  - `bin/web.sh:12` `NEXT_PUBLIC_CONTROL_PLANE_HTTP_URL`
  - `bin/web.sh:13` `NEXT_PUBLIC_CONTROL_PLANE_WIRE_URL`
  - `bin/web.sh:28` `npm run dev`
  - `docker-compose.yml:48` `context: ./apps/web`
  - `docker-compose.yml:52` `NEXT_PUBLIC_CONTROL_PLANE_HTTP_URL`
  - `docker-compose.yml:53` `NEXT_PUBLIC_CONTROL_PLANE_WIRE_URL`
  - `apps/web/package.json:8` `next dev`
  - `apps/web/next.config.ts:4` `output: "standalone"`

### 분할 판단

분할 정책을 먼저 평가했다. 이 작업은 `01_flutter_portal_scaffold`의 산출물을 배포 대상으로 삼는 후속 작업이다. proto-socket browser transport나 Control Plane endpoint와 독립이므로 `03+01_web_delete_flutter_deploy`로 분리한다.

### 범위 결정 근거

- Flutter 앱 코드 자체 구현은 `01`의 산출물을 사용하고, 여기서는 dev/deploy entrypoint만 조정한다.
- Portal wire client 구현은 `05+01,02,04_portal_wire_contract`에서 한다.
- Docker production image의 TLS/ingress/CDN 세부는 범위 밖이다. compose local static serving까지만 다룬다.

### 빌드 등급

Build lane `cloud-G07`, review lane `cloud-G07`. bin script, Docker/compose, 삭제 작업이 포함된 terminal-agent 성격의 변경이다.

## 구현 체크리스트

- [ ] [WEB_DEPLOY-1] `apps/web` TypeScript/Next.js 제품 경로와 npm 배포 파일을 삭제한다.
- [ ] [WEB_DEPLOY-2] `bin/web.sh`, compose `web` service, Dockerfile을 Flutter Web 산출물 서빙 흐름으로 교체한다.
- [ ] [WEB_DEPLOY-3] 문서와 정적 검색 검증을 Flutter-first 경로로 갱신한다.
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [WEB_DEPLOY-1] Next.js 제품 경로 삭제

문제: Milestone 완료 기준은 `apps/web`에 TypeScript/Next.js 제품 UI 코드와 npm 기반 배포 경로가 남지 않는 것이다. 현재 `apps/web/package.json:8`은 Next dev/build/start를 정의하고 `apps/web/src/app/page.tsx:44`는 Next scaffold임을 직접 표시한다.

해결 방법: `apps/web`의 Next.js/TypeScript source, package manifest, lockfile, config를 삭제한다. `apps/web` directory는 비워지면 제거한다. legacy 설명이 필요하면 루트 README와 `apps/portal/README.md`로 옮긴다.

Before:

```json
apps/web/package.json:7
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "node .next/standalone/server.js"
}
```

After:

```text
apps/web/ is absent, or contains no TypeScript/Next.js/npm product path.
```

수정 파일 및 체크리스트:

- [ ] `apps/web/package.json`, `package-lock.json`, `next.config.ts`, `tsconfig.json`, `postcss.config.mjs`, `components.json`, `next-env.d.ts`를 삭제한다.
- [ ] `apps/web/src/**`와 `apps/web/public/**`의 Next UI scaffold를 삭제한다.
- [ ] `apps/web/Dockerfile`과 `.dockerignore`를 삭제하거나 더 이상 compose에서 참조하지 않는다.
- [ ] 루트 README의 앱 구성 표를 `apps/portal` 기준으로 바꾼다.

테스트 작성: 별도 unit test는 작성하지 않는다. 삭제 검증은 deterministic `rg --sort path` 검색으로 한다.

중간 검증:

```bash
rg --sort path "next|react|typescript|NEXT_PUBLIC|npm run|apps/web" README.md docs apps bin docker-compose.yml Makefile
```

기대 결과: 의도적으로 남긴 migration 설명 외에는 Next/React/TypeScript/npm 제품 경로가 없다. 남기는 항목은 `CODE_REVIEW-cloud-G07.md`에 예외로 기록한다.

### [WEB_DEPLOY-2] Flutter Web deploy flow

문제: `bin/web.sh:15`는 `apps/web/node_modules`를 요구하고 `docker-compose.yml:48`은 `./apps/web` Docker context를 사용한다. `apps/web/Dockerfile:3`은 Node image와 Next standalone server를 사용한다.

해결 방법: `apps/portal/Dockerfile`을 추가한다. Docker build context는 repo parent `..`, dockerfile은 `iop/apps/portal/Dockerfile`로 두어 sibling `../proto-socket/dart` path dependency가 생겨도 접근 가능하게 한다. final stage는 static server를 사용하고 port 3000을 노출한다. `bin/web.sh`는 `apps/portal`에서 `flutter run -d web-server`를 실행한다.

Before:

```yaml
docker-compose.yml:48
build:
  context: ./apps/web
  dockerfile: Dockerfile
```

After:

```yaml
build:
  context: ..
  dockerfile: iop/apps/portal/Dockerfile
  args:
    IOP_CONTROL_PLANE_HTTP_URL: "http://localhost:${IOP_CONTROL_PLANE_HTTP_PORT:-9080}"
    IOP_CONTROL_PLANE_WIRE_URL: "ws://localhost:${IOP_CONTROL_PLANE_WIRE_PORT:-19080}/portal"
```

수정 파일 및 체크리스트:

- [ ] `apps/portal/Dockerfile`: Flutter Web build stage와 static server stage를 추가한다.
- [ ] `docker-compose.yml`: `web` service build context/dockerfile/env/ports를 Flutter 기준으로 바꾼다.
- [ ] `bin/web.sh`: `apps/portal`을 실행 경로로 사용하고 `IOP_WEB_HOST`, `IOP_WEB_PORT`, `IOP_CONTROL_PLANE_HTTP_URL`, `IOP_CONTROL_PLANE_WIRE_URL`을 `--dart-define`으로 전달한다.
- [ ] `.gitignore`: 남은 `apps/web` artifact ignore를 제거하거나 `apps/portal` 기준과 중복되지 않게 정리한다.

테스트 작성: shell script unit test는 작성하지 않는다. 실제 검증은 Flutter build, compose config/build로 한다.

중간 검증:

```bash
make portal-build-web
```

기대 결과: Flutter Web 산출물이 생성된다.

### [WEB_DEPLOY-3] 문서와 잔여 검색

문제: `README.md:230`, `docs/architecture.md:137`, `apps/web/README.md:9`는 migration 방향을 말하지만 실제 구조 변경 후에는 `apps/portal`과 Flutter Web Docker 경로를 기준으로 바뀌어야 한다.

해결 방법: 루트 README, architecture, control-plane README의 앱 경로와 실행 명령을 `apps/portal` 기준으로 갱신한다. `apps/web/README.md`는 directory 제거 시 함께 삭제한다.

수정 파일 및 체크리스트:

- [ ] `README.md`: 앱 구성 표를 `apps/portal` 기준으로 수정한다.
- [ ] `docs/architecture.md`: compose web 설명을 Flutter Web static serving으로 유지한다.
- [ ] `apps/control-plane/README.md`: Portal 실행/배포 문장을 `apps/portal` 기준으로 수정한다.
- [ ] `apps/web/README.md`: `apps/web` directory 제거 시 삭제한다.

테스트 작성: 문서 테스트는 작성하지 않는다. 정적 검색으로 검증한다.

중간 검증:

```bash
rg --sort path "Next\\.js|React|TypeScript|NEXT_PUBLIC|npm|apps/web" README.md docs apps bin docker-compose.yml Makefile
```

기대 결과: 완료된 migration의 historical mention 외에는 남지 않는다. 예외는 review file에 기록한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/web/**` | WEB_DEPLOY-1 |
| `apps/portal/Dockerfile` | WEB_DEPLOY-2 |
| `bin/web.sh` | WEB_DEPLOY-2 |
| `docker-compose.yml` | WEB_DEPLOY-2 |
| `.gitignore` | WEB_DEPLOY-2 |
| `README.md` | WEB_DEPLOY-1, WEB_DEPLOY-3 |
| `docs/architecture.md` | WEB_DEPLOY-3 |
| `apps/control-plane/README.md` | WEB_DEPLOY-3 |

## 최종 검증

```bash
make portal-test
make portal-build-web
rg --sort path "Next\\.js|React|TypeScript|NEXT_PUBLIC|npm|apps/web" README.md docs apps bin docker-compose.yml Makefile
command -v docker
docker compose config
docker compose build web
git diff --check
```

기대 결과: Flutter test/build 통과, 검색 결과의 모든 잔여 항목이 의도된 예외로 설명 가능, Docker가 있는 환경에서 compose config/build web 통과. `command -v docker` 실패 시 Docker 실행 검증은 blocker로 기록한다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
