# task-1357.1 완료 보고서: 배너 에디터 배경 이미지 사라짐 버그 수정

## SCQA

**S**: 대시보드 배너 에디터에서 HTML 배너를 iframe(srcDoc)으로 로드하여 텍스트/레이아웃을 편집할 수 있다. 배너 HTML은 배경 이미지를 `file://` 로컬 경로로 참조한다.

**C**: srcDoc iframe의 base URL이 `about:srcdoc`이라서, 서버가 `file://` → `/output/...`로 변환한 이미지 경로를 브라우저가 해석할 수 없다. 또한 배너 이미지를 HTTP로 서빙하는 전용 API가 부재하여, 에디터에서 배경 이미지가 표시되지 않는다. m2 계열 배너(bg.jpg 사용)에서 확인 가능하며, m1 계열은 bg-image URL이 빈 문자열이라 배경 이미지 자체가 없는 상태였다.

**Q**: 배너 에디터에서 배경 이미지가 정상 표시되고, 편집 중에도 배경이 보존되도록 수정할 수 있는가?

**A**: 3가지 수정으로 해결 완료. (1) 서버에 `/api/banner-editor/image/` 이미지 서빙 엔드포인트 추가, (2) HTML 로드/저장 시 이미지 경로 양방향 변환 로직 보강, (3) 프론트엔드에서 `<base href>` 태그 주입 + 배경 레이어 자동 잠금 처리.

## 수정 내용

### 백엔드 (server.py) — 3건

1. **이미지 서빙 API 추가** (`server.py:3638-3669`)
   - GET `/api/banner-editor/image/<relative_path>` 엔드포인트 신규
   - base directory: `/home/jay/workspace/output/google-ads/banners/`
   - path traversal 2중 방어 (문자열 검사 + resolve 경로 검증)
   - Content-Type 매핑 (jpg/png/webp/gif/svg), Cache-Control 24시간

2. **HTML 로드 시 경로 변환 보강** (`server.py:3592-3610`)
   - `/output/google-ads/banners/` → `/api/banner-editor/image/` 변환
   - 상대 경로(`url('./bg.jpg')`, `url('bg.jpg')`) → 절대 API 경로 변환
   - https/data URI는 negative lookahead로 제외

3. **저장 시 역변환 추가** (`server.py:4953-4963`)
   - `/api/banner-editor/image/` → `file:///home/jay/workspace/output/google-ads/banners/` 역변환
   - 전체 URL(`http://host:port/api/banner-editor/image/`) 형태도 정규식으로 처리

### 프론트엔드 (BannerEditorView.js) — 3건

1. **`<base>` 태그 주입** (`BannerEditorView.js:142-152`)
   - enterEditor에서 HTML 수신 후 `<base href="${origin}/">` 주입
   - `<head>`, `<head attr>`, `<html>` 3가지 케이스 처리

2. **저장 시 `<base>` 태그 제거** (`BannerEditorView.js:655-656, 684-686`)
   - handleSave, handleDownload 양쪽에서 서버 전송 전 base tag strip

3. **배경 레이어 자동 잠금** (`BannerEditorView.js:390-400`)
   - `.bg-image, .bg-full, .bg-photo, .left-overlay, .overlay-left, .overlay-full` 자동 잠금
   - lockedLayers 상태에 병합하여 드래그/리사이즈 방지

## 산출물 파일

- `dashboard/server.py`
- `dashboard/components/BannerEditorView.js`

## 발견 이슈 및 해결

### 자체 해결 (3건)

1. **srcDoc iframe에서 이미지 경로 해석 불가** — `<base href>` 태그 주입으로 해결
   - 상세: srcDoc의 base URL이 `about:srcdoc`이라 root-relative 경로(`/api/...`) 해석 불가. `window.location.origin` 기반 base tag를 HTML `<head>`에 주입.

2. **이미지 API 엔드포인트 URL 디코딩 누락** — `urllib.parse.unquote()` 추가
   - 상세: `server.py:3639` URL-encoded 파일명(한글 등) 처리를 위해 unquote 및 urlparse 적용.

3. **상대 경로 이미지 미처리** — 정규식 기반 변환 추가
   - 상세: `url('./bg.jpg')`, `url('bg.jpg')` 등 상대 경로가 API 절대 경로로 변환되지 않는 문제. banner_group 기반 re.sub 2개 추가.

### 범위 외 미해결 (1건)

1. **m1 계열 배너의 `.bg-image` URL이 빈 문자열** — 범위 외 사유: 배너 HTML 템플릿의 원본 콘텐츠 문제로, 에디터 버그가 아닌 배너 생성 시 이미지가 지정되지 않은 것. 별도 배너 재생성 태스크 필요.

## 검증

- 이미지 서빙 엔드포인트: m2-1-bg.jpg 등 3개 이미지 파일 존재 확인 (`/home/jay/workspace/output/google-ads/banners/m2/`)
- path traversal 방어: `..` 포함 경로, base_dir 외부 경로 차단 로직 검증
- pyright: 기존 미사용 변수 경고만 존재 (이번 변경과 무관)
- style_check(black/isort): 기존 코드 포맷팅 WARN (이번 변경 부분은 문제 없음)
- 관련 테스트 파일: 0개 (정당한 SKIP — BannerEditorView.js와 server.py의 배너 에디터 관련 테스트 없음)

## 모델 사용 기록

- 팀원: 불칸 / 작업 내용: server.py 이미지 서빙 API + 경로 변환 / 사용 모델: sonnet / 정당성: -
- 팀원: 이리스 / 작업 내용: BannerEditorView.js base tag 주입 + 배경 잠금 / 사용 모델: sonnet / 정당성: -

## QC 자동 검증 결과

- **overall**: WARN (Gate PASS)
- **test_runner**: PASS — pytest 7건 전체 통과 (0.28s)
- **pyright_check**: PASS — 에러 0건, 경고 0건
- **file_check**: PASS — 2개 파일 존재 확인, 보고서 존재
- **data_integrity**: PASS — task-timers.json 정합성 확인
- **style_check**: WARN — black 포맷팅 기존 코드 이슈 (이번 변경 무관)
- **TRUST 5**: T(ested) R(eadable) U(nified) S(ecured) T(rackable) — 전체 PASS
