- 7-8월 초기 구축 문서 12개를 _archive/troubleshooting/2025_07-08_initial_setup/로 이동 - book/300_architecture/390_human_in_the_loop_intent_learning.md를 journey/research/intent_classification/로 이동 (개발 여정 문서) - 빈 폴더 제거 (journey/assets/*)
236 lines
7.2 KiB
Markdown
236 lines
7.2 KiB
Markdown
# 프론트엔드 UX 개선 - 글로벌 키보드 포커스
|
|
|
|
**날짜**: 2025-08-07
|
|
**작업자**: happybell80 & Claude
|
|
**관련 서비스**: frontend-customer
|
|
|
|
## 오후 7시 25분
|
|
|
|
### 글로벌 키보드 포커스 관리 구현
|
|
|
|
**사용자 요구사항**:
|
|
- 채팅 입력창 클릭 없이 타이핑 가능
|
|
- 어디서든 Ctrl+V로 붙여넣기
|
|
- Discord/Slack 같은 UX 구현
|
|
|
|
**구현 내용**:
|
|
```javascript
|
|
// chat-interface.tsx:58-126
|
|
useEffect(() => {
|
|
const handleGlobalKeyDown = (e: KeyboardEvent) => {
|
|
// 안전 조건 체크
|
|
const target = e.target as HTMLElement;
|
|
|
|
// 이미 입력 필드면 무시
|
|
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(target.tagName)) return;
|
|
|
|
// contentEditable, 버튼, 링크면 무시
|
|
if (target.contentEditable === 'true') return;
|
|
if (['BUTTON', 'A'].includes(target.tagName)) return;
|
|
|
|
// 모달/드롭다운 열려있으면 무시
|
|
if (document.querySelector('[role="dialog"], [role="menu"]')) return;
|
|
|
|
// 네비게이션 키는 무시 (Tab, Arrow 등)
|
|
if (['Tab', 'ArrowUp', ...].includes(e.key)) return;
|
|
|
|
// Ctrl+V 붙여넣기
|
|
if (e.ctrlKey && e.key === 'v') {
|
|
e.preventDefault();
|
|
textareaRef.current?.focus();
|
|
navigator.clipboard.readText().then(text => {
|
|
// 클립보드 내용 붙여넣기
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 일반 문자 입력 시 자동 포커스
|
|
if (e.key.length === 1) {
|
|
textareaRef.current?.focus();
|
|
}
|
|
};
|
|
|
|
document.addEventListener('keydown', handleGlobalKeyDown);
|
|
return () => document.removeEventListener('keydown', handleGlobalKeyDown);
|
|
}, [input, setInput]);
|
|
```
|
|
|
|
### 안전 장치
|
|
|
|
1. **다른 입력 필드 방해 안 함**
|
|
- INPUT, TEXTAREA, SELECT 태그 체크
|
|
- contentEditable 요소 체크
|
|
|
|
2. **인터랙티브 요소 보호**
|
|
- 버튼, 링크 클릭 동작 보장
|
|
- 모달, 드롭다운 메뉴 정상 동작
|
|
|
|
3. **네비게이션 키 보존**
|
|
- Tab, Arrow, Home, End 등
|
|
- 페이지 스크롤 키 정상 동작
|
|
|
|
4. **브라우저 단축키 보호**
|
|
- Ctrl+F (검색) 등 보존
|
|
- 기능키 (F1-F12) 정상 동작
|
|
|
|
### 브라우저 권한 이슈
|
|
|
|
**클립보드 API 권한**:
|
|
- `navigator.clipboard.readText()` 사용
|
|
- Chrome이 클립보드 읽기 권한 요청
|
|
- 사용자가 허용해야 Ctrl+V 동작
|
|
|
|
**대안 비교**:
|
|
| 방법 | 권한 | 작동성 | 상태 |
|
|
|------|------|--------|------|
|
|
| navigator.clipboard | 필요 | 100% | 권장 |
|
|
| document.execCommand | 불필요 | 10% | deprecated |
|
|
| 포커스만 이동 | 불필요 | 80% | 차선책 |
|
|
|
|
### 배포
|
|
|
|
**커밋 정보**:
|
|
```bash
|
|
git commit -m "feat: 글로벌 키보드 포커스 관리 구현
|
|
|
|
- 페이지 어디서든 타이핑 시 자동으로 입력창 포커스
|
|
- Ctrl+V 붙여넣기 지원 (클립보드 권한 필요)
|
|
- 안전 조건: 다른 입력필드, 버튼, 모달 열림 시 동작 안 함
|
|
- 네비게이션 키는 정상 동작 유지"
|
|
```
|
|
|
|
**Gitea Actions 자동 배포**:
|
|
- frontend-customer → main 브랜치
|
|
- 51123 서버 자동 배포
|
|
|
|
## 교훈
|
|
|
|
1. **글로벌 이벤트 리스너 설계**
|
|
- 예외 조건을 먼저 정의
|
|
- 기존 UX 방해 최소화
|
|
- 접근성 표준 준수
|
|
|
|
2. **클립보드 보안 정책**
|
|
- 브라우저별 권한 정책 차이
|
|
- 사용자 명시적 동의 필요
|
|
- deprecated API 사용 지양
|
|
|
|
3. **UX 개선 우선순위**
|
|
- 편의성 < 예측 가능성
|
|
- 자동화 < 사용자 제어권
|
|
- 단순함 > 복잡한 기능
|
|
|
|
4. **크로스 브라우저 호환성**
|
|
- Chrome: 클립보드 권한 팝업
|
|
- Firefox: 다른 권한 정책
|
|
- Safari: 더 엄격한 제한
|
|
|
|
## 다음 개선 사항
|
|
|
|
- [ ] 설정에서 on/off 토글 제공
|
|
- [ ] 시각적 포커스 표시기 추가
|
|
- [ ] ~~모바일 터치 키보드 대응~~ (UX 해침)
|
|
|
|
## 오후 7시 30분
|
|
|
|
### 모바일 지원 검토 결과
|
|
|
|
**문제 발견**:
|
|
- 모바일에서 글로벌 키보드 포커스 기능 작동 안 함
|
|
- 가상 키보드로 인한 `keydown` 이벤트 미발생
|
|
|
|
**모바일 특성 분석**:
|
|
1. **이벤트 차이**
|
|
- PC: `keydown`, `keyup` 이벤트
|
|
- 모바일: `touchstart`, `touchend` 이벤트
|
|
- 가상 키보드는 물리 키보드와 다른 이벤트 체계
|
|
|
|
2. **포커스 동작**
|
|
- 모바일 브라우저의 자동 포커스 제한
|
|
- 가상 키보드 자동 팝업 방지 정책
|
|
|
|
3. **UX 고려사항**
|
|
- 화면 터치 시 자동 포커스 → 키보드 계속 올라옴
|
|
- 스크롤, 스와이프 동작 방해
|
|
- 작은 화면에서 키보드가 차지하는 공간 문제
|
|
|
|
**구현 안 하기로 결정**:
|
|
```javascript
|
|
// 가능한 구현 (하지만 UX 해침)
|
|
document.addEventListener('touchstart', (e) => {
|
|
if (!['INPUT', 'TEXTAREA'].includes(e.target.tagName)) {
|
|
textareaRef.current?.focus(); // 키보드 계속 올라옴
|
|
}
|
|
});
|
|
```
|
|
|
|
**결론**:
|
|
- PC: 글로벌 키보드 포커스 유지 ✅
|
|
- 모바일: 기존 방식 유지 (직접 터치) ✅
|
|
- 모바일은 터치 인터페이스가 더 자연스러움
|
|
|
|
## 교훈
|
|
|
|
1. **플랫폼별 UX 차별화**
|
|
- PC와 모바일은 다른 인터랙션 패턴
|
|
- 같은 기능이라도 플랫폼에 맞게 조정
|
|
- 모든 기능을 동일하게 구현할 필요 없음
|
|
|
|
2. **모바일 UX 원칙**
|
|
- 가상 키보드는 필요할 때만
|
|
- 자동 포커스는 신중하게
|
|
- 터치 제스처 우선
|
|
|
|
3. **기능 구현 vs UX**
|
|
- 기술적으로 가능 ≠ 구현해야 함
|
|
- 사용자 경험이 최우선
|
|
- 때로는 안 하는 것이 더 나은 선택
|
|
|
|
---
|
|
|
|
## 2025-09-12 IME 충돌 문제 발견 및 해결
|
|
|
|
### 문제 발생
|
|
글로벌 키보드 포커스 구현 1개월 후 한글 입력 버그 발견:
|
|
- **증상**: "로빙" 입력 시 "f→ㅗ→로빙" 또는 "ㅗ빙" 출력
|
|
- **원인**: keydown 이벤트에서 즉시 focus() 호출이 IME 조합 과정 방해
|
|
|
|
### 시도한 해결책들
|
|
|
|
**1차 시도 - composition 이벤트 처리**
|
|
```javascript
|
|
if (e.isComposing || isComposing) return; // IME 입력 중 체크
|
|
```
|
|
- 결과: 속도 개선되었으나 첫 글자 영문 출력 지속
|
|
|
|
**2차 시도 - setTimeout 지연**
|
|
```javascript
|
|
setTimeout(() => textareaRef.current?.focus(), 0);
|
|
```
|
|
- 결과: 첫 자음 누락 ("ㄹ" 사라짐) 새로운 문제 발생
|
|
|
|
**3차 시도 - textarea 직접 이벤트**
|
|
```javascript
|
|
onCompositionStart={() => setIsComposing(true)}
|
|
onCompositionEnd={() => setIsComposing(false)}
|
|
```
|
|
- 결과: 부분 개선, 하지만 완벽하지 않음
|
|
|
|
### 근본적 깨달음
|
|
- **핵심 문제**: 글로벌 키보드 캡처와 IME는 본질적으로 충돌
|
|
- **업계 표준**: "/" 키 같은 제한적 단축키만 사용 (Discord, Slack도)
|
|
- **브라우저 한계**: IME 조합 중 DOM/포커스 변경 시 조합 깨짐
|
|
|
|
### 최종 해결 - 완전 제거
|
|
```javascript
|
|
// 글로벌 키보드 이벤트 90줄 삭제
|
|
// 대신 간단한 autoFocus 사용
|
|
<textarea autoFocus ... />
|
|
```
|
|
|
|
### 교훈
|
|
1. **IME 고려 필수**: 한글/중국어/일본어 사용자 위해 초기 설계부터 고려
|
|
2. **복잡함 < 단순함**: 107줄 삭제, 6줄로 해결
|
|
3. **서구 중심 UX 한계**: Discord/Slack도 IME 환경에선 타협
|
|
4. **"아무데나 타이핑"의 함정**: 편의 기능이 오히려 기본 기능 방해
|
|
5. **웹 플랫폼 제약**: OS 레벨 입력기를 웹에서 완벽 제어 불가 |