- **근거**: 접근성(`aria-label`), 검색/필터링 등에 텍스트가 필요할 수 있음
- **렌더링**: 아이콘 모드에서는 `text`를 `aria-label` 용도로만 보존
- **아이콘+텍스트 모드**: `text`가 아이콘 오른쪽에 함께 렌더링됨
### 10. 아이콘-텍스트 간격 설정 추가
- **결정**: 아이콘+텍스트 모드에서 아이콘과 텍스트 사이 간격을 관리자가 조절 가능 (`iconGap`)
- **근거**: 고정 `gap-1.5`(6px)로는 다양한 버튼 크기/디자인에 대응 불가. 간격이 좁으면 답답하고, 넓으면 분리되어 보이는 경우가 있어 관리자에게 조절 권한 제공
- **기본값**: 6px (기존 `gap-1.5`와 동일)
- **UI**: 슬라이더(0~32px) + 숫자 직접 입력(최댓값 제한 없음)
- **저장**: `componentConfig.iconGap` (숫자)
### 11. 키보드 단축키 입력 필드 충돌 해결
- **결정**: `ScreenDesigner`의 글로벌 키보드 핸들러에서 입력 필드 포커스 시 앱 단축키를 무시하도록 수정
- **근거**: SVG 붙여넣기 textarea에서 Ctrl+V/A/C/Z가 작동하지 않는 치명적 UX 문제 발견. 글로벌 `keydown` 핸들러가 `{ capture: true }`로 모든 키보드 이벤트를 가로채고 있었음
- **수정**: `browserShortcuts` 일괄 차단과 앱 전용 단축키 처리 앞에 `e.target`/`document.activeElement` 기반 입력 필드 감지 가드 추가
- **영향**: input, textarea, select, contentEditable 요소에서 텍스트 편집 단축키가 정상 동작
### 12. noIconAction에서 커스텀 아이콘 추가 허용
- **결정**: 추천 아이콘이 없는 deprecated 액션에서도 커스텀 아이콘(lucide 검색, SVG 붙여넣기) 추가 가능
- **근거**: "적절한 아이콘이 없습니다" 문구만 표시하고 아이콘 추가를 완전 차단하면 관리자가 필요한 아이콘을 직접 등록할 방법이 없음. 추천은 없지만 직접 추가는 허용해야 유연성 확보
- **안내 문구**: "적절한 추천 아이콘이 없습니다. 텍스트 모드를 사용하거나 아래에서 아이콘을 직접 추가하세요."
### 13. 아이콘 모드 레이아웃 안내 문구
- **결정**: 아이콘 모드(`"icon"`) 선택 시 "버튼 영역의 가로 폭을 줄여 정사각형에 가깝게 만들면 더 깔끔합니다" 안내 표시
- **근거**: 아이콘 자체는 항상 정사각형(24x24 viewBox)이지만, 디자이너에서 버튼 컨테이너는 가로로 넓은 직사각형이 기본. 아이콘만 넣으면 좌우 여백이 과다해 보이므로 버튼 영역을 줄이라는 안내가 필요. 자동 크기 조정은 기존 레이아웃을 깨뜨릴 위험이 있어 도입하지 않되, 관리자에게 팁을 제공하면 스스로 최적화할 수 있음
- **표시 조건**: `displayMode === "icon"`일 때만 (아이콘+텍스트 모드는 가로 공간이 필요하므로 해당 안내 불필요)
- **대안 검토**: 자동 정사각형 조정 → 기각 (관리자 수동 레이아웃 파괴 위험)
### 14. 디폴트 아이콘 자동 부여
- **결정**: 아이콘/아이콘+텍스트 모드 전환 시 아이콘이 미선택이면 디폴트 아이콘을 자동으로 부여. 커스텀 아이콘 삭제 시에도 텍스트 모드로 빠지지 않고 디폴트 아이콘으로 복귀
- **근거**: 아이콘 모드로 전환했는데 아무것도 안 보이면 "기능이 작동하지 않는다"는 착각을 유발. 또한 커스텀 아이콘을 삭제했을 때 갑자기 텍스트로 빠지면 관리자가 의도치 않은 모드 변경을 경험하게 됨
- **디폴트 선택 기준**: 해당 액션의 첫 번째 추천 아이콘 (예: save → Check). 추천 아이콘이 없는 액션은 범용 폴백 `SquareMousePointer` 사용
- **표시 조건**: 아이콘+텍스트 모드에서만 표시 (아이콘 모드, 텍스트 모드에서는 숨김)
### 16. 버튼 컴포넌트 테두리 이중 적용 문제 해결
- **결정**: `RealtimePreviewDynamic`의 position wrapper에서 버튼 컴포넌트의 border 속성을 분리(strip)
- **근거**: StyleEditor에서 설정한 border가 (1) position wrapper와 (2) 내부 버튼 요소 두 곳에 모두 적용되어 이중 테두리 발생. border는 내부 버튼(`buttonElementStyle`)에서만 렌더링해야 함
- **수정 파일**: `RealtimePreviewDynamic.tsx` — `isButtonComponent` 조건에 `v2-button-primary` 추가하여 border strip 대상에 포함
- **수정 파일**: `ButtonPrimaryComponent.tsx` — 외부 wrapper(`componentStyle`)에서 border 속성 destructure로 제거, `border: "none"` shorthand 대신 개별 longhand 속성으로 변경 (borderStyle 미설정 시 기본 `"solid"` 적용)
### 17. 커스텀 아이콘 검색은 lucide 전체 목록 기반
- **결정**: lucide-react에서 export되는 전체 아이콘 이름 목록을 검색 가능
- **저장**: `custom_icon_registry` 테이블 (company_code 멀티테넌시), lucide 이름 또는 SVG 데이터 저장
- **하위 호환**: 기존 `componentConfig.customIcons` 데이터는 병합 표시 후 점진적 마이그레이션
### 19. 동적 아이콘 로딩 (getLucideIcon fallback)
- **결정**: `getLucideIcon(name)`이 `iconMap`에 없는 아이콘을 `lucide-react`의 `icons` 전체 객체에서 동적으로 조회 후 캐싱
- **근거**: 화면 관리에서 커스텀 lucide 아이콘을 선택하면 `componentConfig.customIcons`에 이름만 저장됨. 디자이너 세션에서는 `addToIconMap()`으로 런타임에 등록되지만, 실제 화면(뷰어) 로드 시에는 `iconMap`에 해당 아이콘이 없어 렌더링 실패. `icons` fallback을 추가하면 **어떤 lucide 아이콘이든 이름만으로 자동 렌더링**
- **구현**: `button-icon-map.tsx`에 `import { icons as allLucideIcons } from "lucide-react"` 추가, `getLucideIcon()`에서 `iconMap` miss 시 `allLucideIcons[name]` 조회 후 `iconMap`에 캐싱
- **번들 영향**: `icons` 전체 객체 import로 번들 크기 증가 (~100-200KB). ERP 애플리케이션 특성상 수용 가능한 수준이며, 관리자가 선택한 모든 아이콘이 실제 화면에서 동작하는 것이 더 중요
- **대안 검토**: 뷰어 로드 시 `customIcons`를 순회하여 개별 등록 → 기각 (모든 뷰어 컴포넌트에 로직 추가 필요, 누락 위험)