# S-CANVAS UI/UX 전면 재설계 (Phase 0 — 디자인)

> **사용자 피드백 #4 인용**
> "느리고, 조작이 어렵게 느껴지므로, UI/UX를 전면 수정할 필요가 있음(기존 구조에 로그는 백엔드로 빼고, 프로세스를 클릭할 때마다 새로운 창이 뜨는 것이 아니라 한 화면에서 바로 구동되게끔 적용)"
>
> 요지: (1) 인라인 로그 패널 제거 → 백엔드 파일 (2) 단계마다 새 팝업창 → 단일 창 인스펙터.

본 문서는 **Phase 0(읽기 전용 분석 + 청사진)**. 이번 라운드에서는 코드 한 줄도 수정하지 않는다.

---

## 1. 현재 UI 진단

### 1.1 메인 셸 구조 (`scanvas_maker.py`)
- **클래스**: `SCanvasApp(ctk.CTk)` — 단일 창. line 160.
- **창 크기**: 1200x900, light theme, blue color theme. line 165–167.
- **메인 레이아웃**: 2-column grid.
  - **Left (col 0)**: `sidebar_container` (270px 고정) + `sidebar_frame` (CTkScrollableFrame 250px). line 274–283.
    - 섹션: 로고 → SETTINGS (위성 소스/Vworld 키/AI 엔진/GCP/Vertex Loc/CRS) → WORKFLOW (Step1, 1.5, 2, 3, 4, 구조물 빌드, 상세 치수, 3D 다시 열기) → OPTIONS (와이어프레임, 뷰 버퍼 %, DEM 확장 m, 테마) → SAMAN 푸터.
  - **Right (col 1)**: `main_frame` (corner_radius=15, transparent). line 558–562.
    - row 0 weight=3 → `map_frame` (TkinterMapView, 위성 지도 미리보기).
    - row 1 weight=1 → `textbox` (CTkTextbox, **height=120, 인라인 로그 패널**). line 582–583.
    - row 2 → `status_bar` (28px, ● READY 인디케이터 + status_text). line 586–593.

### 1.2 인라인 로그 (제거 대상)
- 표면화 위치: `main_frame.row=1`, `self.textbox` 한 위젯.
- 호출지점: `self.log(message)` — **180회**. line 691–696.
- 동작: `datetime` timestamp prefix → `textbox.insert("end", ...)` → auto-scroll.
- 별도로 `self._diag(...)` (구조물 분류 진단)와 `harness.logger.setup_logging(log_file=harness_log_path())`는 **이미 백엔드 파일**로 흘러가고 있음 (line 228, 698–710). 즉 인프라 절반은 이미 존재.
- 결론: `harness_log_path()` (`%LOCALAPPDATA%\S-CANVAS\scanvas_harness.log`) 표준에 `self.log()`도 포함시키면 됨. 새 파일 만들 필요 없음.

### 1.3 팝업 다이얼로그 카탈로그 (제거/이식 대상)
**`CTkToplevel(...)` 12개** + **`messagebox.*` 63회** + **`filedialog.*` 다수**.

| # | 위치 (line) | 트리거 | 제목 | 크기 | 이식 우선순위 |
|---|---|---|---|---|---|
| T1 | 766 | Step 1 DXF 로드 후 자동 | `DXF 레이어 분류` | 900×650 | **HIGH** — 첫 인상 |
| T2 | 1419 | 사이드바 `구조물 상세 3D 빌드` | `구조물 상세 3D 빌드 (템플릿)` | 1100×650 | HIGH |
| T3 | 1596 | T2 내부 `상세 빌드` 버튼 | 빌드 진행 다이얼로그 | — | MED |
| T4 | 1889 | T3 내부 옵션 | `렌더 옵션` (서브) | — | LOW (T3 흡수) |
| T5 | 2044 | T3 내부 VLM 결과 | AI 검증 결과 | — | LOW (인라인 토스트) |
| T6 | 2366 | 사이드바 `간단 치수 추가` | 상세도면 업로드 | — | MED |
| T7 | 2486 | T6 후속 | 치수 확인/편집 | 650×500 | MED |
| T8 | 2723 | Step 1 후속 | `계획선 고도 설정` | 1280×560 | HIGH — 워크플로 핵심 |
| T9 | 4624 | 사이드바 `🎯 TIN 이용 범위` | TIN core 선택 (matplotlib 내장) | 1100×920 | **CRITICAL** — interactive canvas |
| T10 | 6537 | Step 4 시작 | `렌더링 옵션` (시간대/화질) | 380×360 | HIGH |
| T11 | 6897 | Blender 렌더 결과 | 결과 이미지 뷰어 | 동적 | MED |
| T12 | 6970 | AI 렌더 결과 | 결과 이미지 뷰어 | 동적 | MED |

**`messagebox` 63회 분포**: 거의 모두 (a) 모듈 미설치 경고, (b) 전제조건 안내("먼저 Step N을 수행하세요"), (c) 최종 완료/실패 메시지. 진짜 **위험한 결정**(askyesno) 은 line 2191/2213/2223/2238 (구조물 빌드 시 덮어쓰기 확인) — 4건.

### 1.4 PyVista 3D 뷰포트 — 가장 큰 함정
**현재**: `pv.Plotter(title=...).show()` 를 호출 → **별도 OS 윈도우** 가 뜸 (VTK render window). line 1769, 5463, 5567, 5888, 6228 등 6개 호출지.
- 메인 CustomTkinter 창과 **물리적으로 다른 창**. 사용자가 "한 화면에서" 보길 원하는 핵심 위반.
- `pyvistaqt`/`QtInteractor` 임포트는 코드베이스에 **없음** (전체 검색 0건).
- Tk + VTK 임베딩 표준 경로: `vtkmodules.tk.vtkTkRenderWidget` (Python 3.13 + PyVista 0.43+ 환경에서 동작 확인 필요) **OR** PyQt 의존성 추가 + `pyvistaqt.QtInteractor`.

### 1.5 사용자 워크플로 막힘 지점 (5분 룰 위반)
1. **첫 화면이 위성지도** — 사용자는 DXF 작업이 목적인데 첫 화면에 빈 한국 지도가 떠 있음. 다음 단계가 사이드바 `1. TIN 생성 (DXF)` 임이 아이콘/색으로 강조되지 않음.
2. **각 Step 후 새 창 강제** — Step 1 → T1 팝업(레이어 분류) → T8 팝업(고도 설정) → 닫힘 → 사이드바 클릭 → Step 2 → 위성 지도 갱신 → Step 3 → T9-likely 팝업 → Step 4 → T10 팝업. **창 4–5개를 ALT-TAB** 으로 오가야 한다.
3. **3D 뷰가 별도 창** — 위성지도(메인)와 3D(VTK 별창)가 분리. 사용자는 두 창 사이에서 컨텍스트 잃음.
4. **로그 패널 차지 면적** — 메인 영역 25% (`main_frame` row 1 weight=1, weight=3 vs 1) 가 로그. 사용자가 보지도 않는 텍스트가 차지.
5. **상태바가 정보 부족** — `● READY` + 짧은 텍스트 1줄. 진행률 게이지 없음 (단순 set_status 호출).
6. **에러 = messagebox 63회** — 매 실패마다 모달 창. 사용자 흐름 차단.

---

## 2. 새 레이아웃 (text mockup)

### 2.1 메인 셸 (단일 창)

```
┌──────────────────────────────────────────────────────────────────────┐
│  S-CANVAS                                                  ─  □  ✕   │
├────────┬─────────────────────────────────────────┬───────────────────┤
│ Sidebar│              Main Canvas                │     Inspector     │
│ (240)  │             (flex, weight=1)            │     (340)         │
│        │                                         │                   │
│ ┌────┐ │  ┌───────────────────────────────────┐  │ ┌───────────────┐ │
│ │ S  │ │  │                                   │  │ │ Step 1: DXF   │ │
│ │CANVAS│ │  │                                   │  │ │ 로드          │ │
│ └────┘ │  │     [PyVista 3D Viewport]         │  │ ├───────────────┤ │
│        │  │       (or fallback                │  │ │               │ │
│ Pipeline│  │        TkinterMapView when no    │  │ │ DXF 파일:     │ │
│        │  │        TIN yet)                   │  │ │ [filepath...] │ │
│ ① DXF  │  │                                   │  │ │ [📂 찾아보기] │ │
│ ② GeoR │  │                                   │  │ │               │ │
│ ③ TIN  │  │                                   │  │ │ CRS: ▼ EPSG.. │ │
│ ④ Strct│  │                                   │  │ │               │ │
│ ⑤ Rndr │  │                                   │  │ │ ┌──────────┐  │ │
│        │  └───────────────────────────────────┘  │ │ │ 시작 →   │  │ │
│ ──     │  ┌─Tab strip──────────────────────────┐ │ │ └──────────┘  │ │
│ Settings│  │ [3D] [위성지도] [DXF 미리보기]    │ │ │               │ │
│ • Theme│  └───────────────────────────────────┘  │ │ ── 도움말 ─── │ │
│ • API  │                                         │ │ Step 1은 ...  │ │
│ • CRS  │                                         │ └───────────────┘ │
├────────┴─────────────────────────────────────────┴───────────────────┤
│  ● Ready    Step 1/5: DXF 로드 대기      [▮▮▮▮▮▯▯▯ 50%]  [📋 log]  │
└──────────────────────────────────────────────────────────────────────┘
```

### 2.2 영역 정의

| 영역 | 폭 | 책임 | 구현 위젯 |
|---|---|---|---|
| **Sidebar** (left) | 240px 고정 | 네비게이션 (5단계 rail) + 글로벌 settings 토글 | `CTkScrollableFrame` 분리: 위 = pipeline rail, 아래 = settings (collapsed by default) |
| **Main Canvas** (center) | flex | 항상 켜진 3D 뷰포트 + 보조 탭 (지도/DXF) | top: VTK 임베디드 위젯, bottom: 탭 strip |
| **Inspector** (right) | 340px | **활성 step 의 폼**. step 바뀌면 내용만 swap (창 X) | `CTkFrame` + step 별 `_build_inspector_step{N}()` 메서드 |
| **Status Bar** (bottom) | 32px | ● 인디케이터 + 현재 단계 + 진행률 + log 버튼 | 기존 `status_bar` 확장 |

### 2.3 사이드바 — 5단계 rail (텍스트만)

```
PIPELINE
─────────
✓ ① DXF 로드          ← 완료 (체크, 녹)
▶ ② GeoRef            ← 활성 (▶, 청록 강조)
○ ③ TIN + DEM         ← 미진행 (회색)
○ ④ Structures
○ ⑤ Render (AI)
─────────
SETTINGS  ▼ (collapsible)
  • API Key
  • CRS
  • Theme
─────────
SAMAN © Footer
```

- 클릭 시: inspector 내용을 해당 step 폼으로 swap. 창 안 띄움.
- 진행 상태 아이콘: ✓ (완료) / ▶ (활성) / ○ (대기) / ✕ (실패) — 일관된 4가지.

### 2.4 Inspector — step 별 폼

각 step 의 inspector 는 다음 표준을 따른다 (5분 룰 강제):

```
┌───────────────────────┐
│ ① DXF 로드            │  ← h1 (18pt bold)
├───────────────────────┤
│                       │
│ [현재 step 의 입력]   │  ← 결정 ≤ 3개
│                       │
│ ┌─ 시작 → ──────┐     │  ← primary button (#16A085, height=40)
│ └────────────────┘    │
│                       │
│ ── 도움말 ──          │  ← caption (10pt gray)
│ "DXF 파일을 골라      │
│  CRS 를 확인하세요"   │
└───────────────────────┘
```

**크리티컬 케이스**: step 9 (TIN core 선택) 의 matplotlib interactive canvas — 이건 inspector 가 아니라 **메인 canvas 영역에 임시 오버레이** 로 띄워야 함 (창 신설 금지). PyVista 뷰포트 위에 matplotlib FigureCanvasTkAgg 를 잠시 덮고, 확정 시 다시 PyVista 로 복귀.

### 2.5 Status Bar — 미니 로그 인디케이터

```
● Running   Step 3/5: 제어맵 추출 중       [▮▮▮▮▮▮▯▯ 70%]   📋 log (3 new)
```

- ● 색: ready=녹(#2ECC71), running=청(#3498DB), warn=주황(#D35400), error=빨(#E74C3C).
- 진행률: 8칸 ASCII bar, 단계별 percent.
- `📋 log (N new)` 버튼 클릭 → **log drawer** (창 아님, 메인 canvas 위로 슬라이드 다운, 350px) 열림. 닫기 ✕ 로 즉시 사라짐. 내용은 `harness_log_path()` 의 tail 200줄.

---

## 3. 컴포넌트 매핑 (현재 → 신규)

| 현재 (popup) | 신규 (single-window) | 처리 방식 |
|---|---|---|
| T1 `DXF 레이어 분류` (900×650) | Inspector — Step 1 의 sub-form (스크롤) | 탭 또는 expandable section |
| T2 `구조물 상세 3D 빌드` (1100×650) | Inspector — Step 4 main form | sidebar `④ Structures` 클릭 시 swap |
| T3 빌드 다이얼로그 | Inspector 내부 진행 영역 + status bar 진행률 | inline progress |
| T4 옵션 서브창 | T3 폼의 expandable "고급" 섹션 | 같은 inspector 안 |
| T5 AI 검증 결과 | **inline toast** (canvas 우상단 8s) | 비모달 |
| T6 상세도면 업로드 | Step 4 sub-form 내 파일 슬롯 | Drag&drop or browse |
| T7 치수 확인 (650×500) | Step 4 sub-form 내 confirm panel | 같은 inspector 폼 안 |
| T8 `계획선 고도 설정` (1280×560) | Inspector — Step 3 sub-form (Step 2.5 스타일) | scrollable form |
| T9 TIN core 선택 (interactive) | **Main canvas overlay** (matplotlib) | 임시 mode swap, ESC=취소 |
| T10 렌더링 옵션 (380×360) | Inspector — Step 5 main form | sidebar `⑤ Render` 클릭 |
| T11/T12 결과 이미지 뷰어 | Main canvas 의 신규 탭 `[결과]` | tab swap, 우측에 메타 |

| 현재 (messagebox) | 신규 |
|---|---|
| `showerror` (≈30회) | inline toast (빨, 8s) + status bar 빨 |
| `showwarning` (≈18회) | inline toast (주황, 6s) |
| `showinfo` (≈12회) | inline toast (청, 4s) |
| `askyesno` 4건 (덮어쓰기 확인) | **유지** (모달, 진짜 데이터 손실 위험 시만) |

| 현재 (log) | 신규 |
|---|---|
| `self.log()` 180회 → CTkTextbox | `self.log()` API 유지 + 내부에서 `logging.getLogger("scanvas").info(...)` 로 routing → `harness_log_path()` |
| `main_frame` row 1 (CTkTextbox) | **삭제**. 그 공간을 main canvas 가 차지 |
| status bar 의 `📋 log` 버튼 | log drawer (slide-down panel) — 개발자만 클릭 |

---

## 4. 마이그레이션 단계 (4 phase)

### Phase A — Backend log 일원화 (1 session, 코드 ≈ 60 lines)
- **목표**: `self.log()` 가 textbox 가 아니라 logger 로 흐르게. UI 구조는 그대로.
- **변경**:
  1. `self.log(msg)` 내부 → `logging.getLogger("scanvas").info(msg)` 호출 추가 (textbox 도 유지 — 안전망).
  2. `setup_logging(log_file=harness_log_path())` 가 이미 line 228 에 있음. 이걸 `HARNESS_AVAILABLE=False` 분기에서도 fallback 으로 작동하게 보강.
  3. status bar 의 `● READY` 옆에 `📋 log` 버튼 prototype 추가 (클릭 → 별도 창으로 textbox 띄움 — Phase D 에서 drawer 로 교체).
- **위험**: 거의 없음. log drawer 가 Phase D 에 있어서 백엔드 라우팅만 먼저.
- **결과 게이트**: `harness_log_path()` 에 모든 메시지가 쌓이는지 grep 으로 확인.

### Phase B — Single-window shell (2 sessions, 코드 ≈ 350 lines)
- **목표**: 3-column grid (sidebar | main canvas | inspector) + status bar 골격. 기능은 placeholder.
- **변경**:
  1. `__init__` 의 grid 를 `grid_columnconfigure(0, weight=0); (1, weight=1); (2, weight=0)` 3-column 으로.
  2. 신규 `self.inspector_frame` (340px, 우측). 현재 사이드바 의 WORKFLOW 섹션을 inspector 의 step1 placeholder 로 옮김.
  3. 사이드바를 **rail** 모드로 단순화: 5단계 rail + collapsible SETTINGS.
  4. `main_frame` 에서 textbox 삭제 → 그 자리에 PyVista placeholder (지금은 빈 frame, Phase C 에서 채움).
  5. `_show_inspector(step_id)` 메서드 추가: step_id 로 해당 폼 swap.
- **위험**: window resize 깨짐 — `grid_rowconfigure(0, weight=1)` 와 `weight=0` 분리 정확히 해야. inspector 폭 340 고정 + sidebar 240 고정 + canvas weight=1.
- **결과 게이트**: 빈 inspector 가 step 클릭에 따라 라벨만 바꾸는 데모. 기능 미작동.

### Phase C — Step inspector forms 이식 (3 sessions, 코드 ≈ 800 lines)
- **목표**: T1, T2, T8, T10 (HIGH 4개) 의 내용을 inspector 폼으로 옮김. 팝업은 아직 코드에 남겨둠 (병행 동작).
- **변경**:
  1. **Step 1 inspector** ← T1 `DXF 레이어 분류` 의 scroll_frame + dropdown 을 inspector 폼으로 복제.
  2. **Step 3 inspector** ← T8 `계획선 고도 설정` 의 10-column scroll 을 inspector 폼으로. 폭이 좁으니 (340 < 1280) 한 row 당 2-line 으로 wrap.
  3. **Step 4 inspector** ← T2 `구조물 상세 3D 빌드` 의 row-per-structure 를 inspector accordion 으로.
  4. **Step 5 inspector** ← T10 `렌더링 옵션` (시간대/화질).
  5. inline toast 위젯 (CTkFrame 우상단 절대 배치, after(N, destroy)) — messagebox 대체용.
- **위험**: T8 의 10-column 이 340px 에 안 들어감 → row 별 expandable section 으로 디자인 변경 필요. T9 (interactive matplotlib) 는 Phase D 까지 팝업 유지.
- **결과 게이트**: 사이드바 5단계 모두 클릭 가능 + inspector 가 step 마다 정확한 폼 표시 + Step 1~5 회귀 테스트 통과.

### Phase D — 팝업 제거 + log drawer + 회귀 (1 session, 코드 ≈ 200 lines)
- **목표**: 모든 `CTkToplevel(...)` 12개 제거 (T9 main canvas overlay 로 교체) + log drawer 완성.
- **변경**:
  1. T1, T2, T8, T10 의 `CTkToplevel` 호출 코드 삭제 (Phase C 에서 inspector 가 이미 동작 중).
  2. T3, T4, T5, T6, T7 의 호출지 → inspector 의 sub-state 로 바뀜.
  3. T9 → main canvas 위에 matplotlib FigureCanvasTkAgg 를 띄우고 ESC/확정 시 사라짐. PyVista 모드 ↔ matplotlib 모드 swap.
  4. T11, T12 → main canvas 의 신규 `[결과]` 탭 으로.
  5. log drawer: status bar `📋 log` 클릭 → 메인 canvas 위에 slide-down (높이 350) panel + 닫기 ✕.
  6. messagebox 63회 → inline toast (showerror/warning/info) + askyesno 4건만 유지.
- **위험**: T9 의 matplotlib pick event 가 main canvas 에서 동작 안 할 수 있음 — pyvista 와 matplotlib 가 같은 Tk frame 안에서 mode swap 시 grab 충돌.
- **결과 게이트**: `Grep CTkToplevel` 결과 0개, `Grep "messagebox.show"` 결과 4개 미만 (askyesno 만), full Step 1~5 시나리오 manual 테스트.

### 누적 일정
- Phase A: 1 session
- Phase B: 2 sessions
- Phase C: 3 sessions
- Phase D: 1 session
- **총 7 sessions**.

---

## 5. 5축 / Cities / Workflow 영향

### 5.1 5축 비전 영향
- **축 1 (AI 물리 조감도)**: ✅ 영향 중립. AI 렌더 트랙(D003) 손대지 않음 — Step 5 inspector 는 기존 T10 폼의 lift-and-shift.
- **축 2 (Cities-Skylines like, 5분 룰)**: ✅✅ 강한 개선. 단일 창에서 사이드바 → inspector → canvas 가 한눈에. 새 사용자가 ALT-TAB 안 해도 됨.
- **축 3 (심미성)**: ✅ 일관성 향상. 12개 팝업의 제각각 폰트/패딩이 1개 inspector 표준으로 통일. 8px 그리드 강제.
- **축 4 (라이브러리)**: ⚠️ 중립. STRUCTURE_REGISTRY 패턴 유지. 단, inspector 폼이 새 구조물 타입 추가 시 자동 확장돼야 함 (data-driven, 코드 수정 X).
- **축 5 (성능)**: ✅ 팝업 생성/파괴 오버헤드 제거 (window create/destroy 는 비용). 단, **PyVista 임베딩이 Tk 에서 60FPS 유지될지 검증 필요** — 이게 Phase B 의 게이트.

### 5.2 두 워크플로 호환
- **Workflow A (Engineering, DXF 기반)**: ✅ Step 1~5 이 그대로 사이드바 rail. 기존 사용자 학습곡선 단발성.
- **Workflow B (Cities, 도면 없이)**: ✅ `cities_placement_widget.py` 가 별도 위젯 — Step 1 inspector 의 toggle 또는 사이드바 별도 모드 버튼으로 attach. 메인 canvas 공유 가능.

---

## 6. 위험 / 트레이드오프

| 위험 | 영향 | 완화 |
|---|---|---|
| **PyVista Tk 임베딩 미검증** | 치명. Phase B 의존. | Phase B 진입 전 5-line 프로토타입으로 `vtkTkRenderWidget` 동작 확인. 실패 시 Plan B = 메인 canvas 는 BackgroundPlotter 별창 유지하되 status bar 위에 thumbnail 동기화. |
| **사용자 학습 곡선** | 중. 12개 팝업 다 사라짐 → "어디로 갔지?" | 첫 실행 시 5초 onboarding tooltip ("기존 팝업이 우측 inspector 로 이동했습니다"). |
| **window resize 깨짐** | 중. CustomTkinter 약점. | `grid_columnconfigure(1, weight=1)` 만 flex, 0/2 는 fixed. 최소 폭 900px (사이드바 240+canvas 320+inspector 340). |
| **백엔드 log 의 디버깅 불편** | 저. inline log 가 사라져 개발자 불편. | log drawer (status bar 버튼 1클릭, 350px) 가 즉시 표시. 파일 위치 status bar 우클릭 메뉴에 노출. |
| **T9 matplotlib mode swap 복잡도** | 중. PyVista ↔ matplotlib 같은 frame 안 swap. | Phase D 까지 T9 만 팝업 유지하는 안전 fallback. 사용자에게 "1회만 별창" 양해. |
| **Phase B-C 사이 코드 중복** | 저. 팝업 + inspector 병존. | Phase D 끝에 일괄 삭제. 중간 git tag 유지. |

---

## 7. 디자인 토큰 (ux-designer.md 기준 강제)

```python
# 색상 (현재 사용 중 + 신규 0개)
COLOR_PRIMARY        = "#16A085"  # rail 활성, primary button
COLOR_PRIMARY_HOVER  = "#117A65"
COLOR_SUCCESS        = "#2ECC71"  # ● ready, ✓ 완료
COLOR_INFO           = "#3498DB"  # ● running
COLOR_WARN           = "#D35400"  # toast warning
COLOR_DANGER         = "#E74C3C"  # ● error, ✕ 실패
COLOR_NEUTRAL_LIGHT  = "#F4F6F7"  # canvas bg (light)
COLOR_NEUTRAL_MID    = "#95A5A6"  # caption text
COLOR_NEUTRAL_DARK   = "#2C3E50"  # body text

# 폰트 (5단계만)
FONT_H1     = ctk.CTkFont(size=18, weight="bold")  # inspector 제목
FONT_H2     = ctk.CTkFont(size=14, weight="bold")  # inspector 섹션 헤더, sidebar PIPELINE 헤더
FONT_BODY   = ctk.CTkFont(size=12)                 # form 라벨, status text
FONT_BUTTON = ctk.CTkFont(size=11, weight="bold")  # buttons
FONT_MICRO  = ctk.CTkFont(size=10)                 # caption, log timestamp

# 8px 그리드
PAD_XS = 8
PAD_SM = 16
PAD_MD = 24
PAD_LG = 32

# 폭/높이
SIDEBAR_W   = 240
INSPECTOR_W = 340
STATUS_BAR_H = 32
LOG_DRAWER_H = 350
MAIN_MIN_W  = 320  # window 최소 폭 = 240+320+340 = 900

# CustomTkinter 한계 인지 → 하지 말 것
# (1) 부드러운 애니메이션 (slide-down log drawer 는 즉각 show/hide)
# (2) 그라디언트
# (3) drop shadow (border 색 변화로 흉내)
```

---

## 8. 영향 받는 파일 (Phase 별 예상)

### Phase A (백엔드 log)
- `D:\2026\PROGRAM\1_S-CANVAS\scanvas_maker.py` (line 691–696, 228, 595)
- `D:\2026\PROGRAM\1_S-CANVAS\harness\logger.py` (이미 작성됨, 변경 없음 검증만)

### Phase B (single-window shell)
- `D:\2026\PROGRAM\1_S-CANVAS\scanvas_maker.py` (line 268–600 layout 전면)
- 신규 메서드 `_build_sidebar_rail()`, `_build_main_canvas()`, `_build_inspector()`, `_build_status_bar()`, `_show_inspector(step_id)`
- PyVista 임베딩 프로토타입 (별도 5-line 파일에서 검증 후 본 코드 통합)

### Phase C (inspector forms)
- `D:\2026\PROGRAM\1_S-CANVAS\scanvas_maker.py` (T1/T2/T8/T10 본문을 `_inspector_step{N}` 로 이전)
- 신규: `_inline_toast(level, msg)` 헬퍼 (≈ 30 lines)

### Phase D (팝업 제거)
- `D:\2026\PROGRAM\1_S-CANVAS\scanvas_maker.py` (line 766, 1419, 1596, 1889, 2044, 2366, 2486, 2723, 4624, 6537, 6897, 6970 의 `CTkToplevel(self)` 12 개소 정리)
- 신규: `_log_drawer.py` (slide-down panel) — 또는 inline class

### 무영향 (보호 영역)
- `harness/`, `gemini_renderer.py`, `blender_renderer.py` — AI 렌더 백엔드 (D003)
- `dem_extender.py`, `geo_referencing.py`, `dxf_geometry.py` — 데이터 레이어
- `structure_templates.py`, `structure_placement.py` — 라이브러리 (축 4)
- `cities_placement_widget.py` — Workflow B (별도 트랙)

---

## 9. 결정 게이트 (사용자/검수자 확인 요청 항목)

다음은 **사용자 결정** 이 필요한 사항. 코드 작성 전 확정:

1. **PyVista 임베딩**: `vtkmodules.tk.vtkTkRenderWidget` (Tk 네이티브, 외부 의존성 없음) vs `pyvistaqt.QtInteractor` (Qt 의존성 추가) — 어느 쪽?
   - 권장: **vtkTkRenderWidget** (D001 결정 정신: 외부 의존성 최소).
2. **inspector 폭 340px**: 1280×560 폭의 T8 폼이 row 당 2-line wrap 으로 들어가는 게 OK 인가, 아니면 inspector 폭을 480px 까지 늘릴 것인가?
3. **사이드바 SETTINGS 접기**: 기본 collapsed 인가 expanded 인가? (5분 룰 = collapsed 권장 — 첫 사용자에게 옵션 폭격 X)
4. **Workflow B 통합**: cities_placement_widget 을 사이드바 별도 toggle 로 둘 건지, 아니면 Step 1 inspector 의 sub-tab 인지?

---

## 10. 결론

**디자인 권장**: 진행 가능. 7 sessions 분량.

**핵심 가치 제안**:
- 팝업 12개 → inspector 1개 (사용자가 보는 창의 수: 5+ → 1).
- 인라인 로그 패널 차지 면적 (메인 25%) → log drawer (필요 시만, 0%).
- messagebox 63회 → inline toast (비모달, 흐름 끊김 X).

**가장 큰 위험**: PyVista 임베딩의 기술적 미검증. Phase B 의 게이트로 5-line 프로토타입 먼저.

**가장 큰 보상**: 5분 룰 합격. 사용자가 ALT-TAB 없이 한 화면에서 워크플로 완주.

— Phase 0 끝. 구현은 사용자 GO 사인 후 Phase A 부터.
