# [Lv.4] PR-E2 — 웹앱 CompositeDesign/CrmCustomers V-path 재배선 (조회/뷰)

## allowed_resources
```yaml
allowed_resources:
  paths:
    - "src/pages/CompositeDesign.tsx"
    - "src/pages/CrmCustomers.tsx"
    - "src/components/composite/**"
    - "src/**/__tests__/**"
    - "tests/e2e/**"
  forbidden_paths:
    - "server/**"
    - "extension/**"
    - ".github/**"
    - "src/config/api.ts"
    - "src/integrations/**"
  commands:
    - "npx vitest"
    - "npx playwright"
    - "npx tsc"
    - "git"
  merge_policy: "none"
  ttl_hours: 24
```
> ★ merge_policy=none — PR OPEN 유지(자동머지 금지). base=현 origin/main(a1551c8). 신규 브랜치. **서버 변경 0**(PR-E1에서 이미 완료).

## 배경·단일소스 (반드시 읽어라)
- 설계: `/home/jay/workspace/memory/plans/insuro-composite-design/integration-design-260721.md`.
- 아키텍처 X: 확장이 캡처→매칭→동의→ingest(PR-D1/D2 머지 완료), **웹앱은 나중 조회/뷰**. 이 PR-E2 = 웹앱 두 페이지를 **V 경로(상담이력 조회)로 재배선**. Codex 설계 조율 확정.
- 서버 V 읽기 엔드포인트 **이미 존재**(PR-E1 머지):
  - **LIST**: `GET ${INSURO_API_BASE}/api/insuro/consultation-history/v1` → `{records:[...], count}`. per-FA·OK-only 뷰·`created_at desc`·limit 100.
  - **LIST(고객필터)**: `GET .../consultation-history/v1?reference_id=<customer UUID>` → 그 고객의 OK 레코드만.
  - **DETAIL**: `GET .../consultation-history/v1/{record_id}`.
  - **응답 레코드 필드**: `id, reference_type, reference_id, query_condition, selected_coverages, per_insurer, validation, analysis_matrix, consent_granted_at/version/scope/source, record_status, rollout_stage, rollout_batch_id, created_at, updated_at`.
  - **feature flag `INSURO_CONSULTATION_HISTORY_V1_ENABLED` 기본 OFF** → flag off 시 **403(feature-disabled)** 반환. pilot allowlist 게이트도 있음.

## 현 구조 (재배선 대상 — origin/main a1551c8)
- `src/pages/CompositeDesign.tsx`: 레거시 `GET /api/insuro/ohmy-capture-history`(~:353-372) 읽어 히스토리 표시. `CustomerMatchDialog`(~:1314) + `handleConfirmMatch/New`(~:576-626)가 레거시 `POST /api/insuro/ohmy-capture/{id}/link` 호출. 분석은 `composite-calculate-from-matrix` 계산 카드. `getAuthHeader()`(~:22-26).
- `src/pages/CrmCustomers.tsx`: 고객별 이력 다이얼로그가 레거시 `GET /api/insuro/customers/{id}/ohmy-history`(~:253) 읽음. 고객 CRUD는 Supabase 직접. `getAuthHeader()`(~:66-70).

## 작업 (Codex 확정 스코프)
### A. CompositeDesign.tsx — V 조회/표시로 재배선
1. 페이지의 **상담이력 조회 소스를 V LIST 로 교체**: `GET /api/insuro/consultation-history/v1` 읽어 레코드 리스트 표시.
2. 각 레코드 **분석 표시**: **`analysis_matrix` 우선 렌더**(있으면), 없으면 **`per_insurer` fallback**. 추가로 `selected_coverages`·`query_condition`(나이/성별/보종/플랜) 표시. (기존 ResultCard/컴포넌트 재사용 가능하면 재사용, 아니면 간결한 표시.)
3. **V 모드에서 레거시 match/link 흐름 미호출**: `CustomerMatchDialog`·`handleConfirmMatch/New`·`ohmy-capture/{id}/link` POST **호출 경로 제거/차단**(코드 삭제 아니라 V 뷰에서 unreachable 로). 매칭은 이제 확장이 담당 — 웹앱은 조회만.
4. **flag OFF / 빈 결과 graceful**: 403(feature-disabled) 또는 `count=0` → **크래시 없이** 한국어 빈 상태("상담이력이 아직 없습니다" / 기능 준비중). 레거시 화면으로 폴백하지 마라(dual-source 금지).

### B. CrmCustomers.tsx — 고객별 상담이력 V 재배선
1. 고객별 이력 다이얼로그를 **`GET /api/insuro/consultation-history/v1?reference_id=<customer.id>`** 로 교체(레거시 ohmy-history 대체). 그 고객의 OK 레코드만 표시(analysis_matrix/per_insurer + selected_coverages/query_condition).
2. **graceful empty/disabled**: 403·빈 결과 → 한국어 안내, 크래시 0.
3. **phone 필수 규칙은 이 PR 에서 변경하지 마라**(Codex C — scope 밖).

### 공통 제약
- **서버 변경 0**. `src/config/api.ts`·`src/integrations/**` 변경 0(auth/base 재사용). 기존 `getAuthHeader()` 패턴 그대로.
- **`consultation_history_v1` 을 Supabase 클라이언트로 직접 읽지 마라**(Codex E — REQUIRES_REVIEW 누출 위험). 반드시 서버 V GET 엔드포인트만.
- 레거시 코드(ohmy-capture-history·CustomerMatchDialog)는 **삭제하지 말고 V 뷰에서 미도달**로. 다른 페이지가 참조할 수 있음.

## 검증 (전부 통과)
- `npx tsc` 타입 클린.
- **단위/컴포넌트 테스트**: V LIST/DETAIL 응답 mock → analysis_matrix 우선·per_insurer fallback 렌더 / 403 → graceful empty / count=0 → empty / CrmCustomers reference_id 필터 호출 정확.
- **e2e**(가능하면): flag 를 **test env 에서 ON**(또는 엔드포인트 mock)으로 V 조회→표시 경로 렌더. flag OFF 시 empty 안내. 레거시 link POST 미발생 확인.
- V 모드에서 `ohmy-capture/{id}/link` POST **0회**(레거시 미호출) 단언.
- 기존 CompositeDesign/CrmCustomers 관련 테스트 회귀 0.

## 완료
- 변경 = `src/pages/CompositeDesign.tsx`·`src/pages/CrmCustomers.tsx`(+ `src/components/composite/**` 필요시)·테스트·e2e 만 (`git diff --stat` — server/·extension/ 등장 시 실패).
- **dev6 금지** — dev1. worktree finish(STRICT+정확base+worktree cwd) → PR(**OPEN 유지·머지 금지** merge_policy none) → ANU 독립검증.
- **ANU callback**(UTF-8 ≤3900 bytes, envelope). ⚠️ 세션 사망 대비: 완료 즉시 finish-task(push) 우선. scope-guard 공유브랜치 오탐 대비 base=origin/main(a1551c8) 명시.
