221 lines
6.2 KiB
Markdown
221 lines
6.2 KiB
Markdown
|
|
# ADR 006: v5.1 자동 줄바꿈 + 검토 필요 시스템
|
||
|
|
|
||
|
|
**날짜**: 2026-02-06
|
||
|
|
**상태**: 채택
|
||
|
|
**의사결정자**: 시스템 아키텍트
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 상황 (Context)
|
||
|
|
|
||
|
|
v5 반응형 레이아웃에서 "화면 밖" 개념으로 컴포넌트를 처리했으나, 다음 문제가 발생했습니다:
|
||
|
|
|
||
|
|
### 문제 1: 정보 손실
|
||
|
|
```
|
||
|
|
12칸 모드:
|
||
|
|
┌────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┬────┐
|
||
|
|
│ A │ B (col=5, 6칸) │
|
||
|
|
└────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┴────┘
|
||
|
|
|
||
|
|
4칸 모드 (기존):
|
||
|
|
┌────┬────┬────┬────┐ 화면 밖:
|
||
|
|
│ A │ │ - B
|
||
|
|
└────┴────┴────┴────┘
|
||
|
|
↑ A만 보임 ↑ 뷰어에서 안 보임!
|
||
|
|
```
|
||
|
|
|
||
|
|
### 문제 2: 사용자 의도 불일치
|
||
|
|
사용자가 기대한 "화면 밖" 역할:
|
||
|
|
- ❌ 컴포넌트 숨김 (현재 동작)
|
||
|
|
- ✅ "이 컴포넌트 검토 필요" 알림
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 결정 (Decision)
|
||
|
|
|
||
|
|
### 채택: 자동 줄바꿈 + 검토 필요 시스템
|
||
|
|
|
||
|
|
```
|
||
|
|
col > maxCol → 자동으로 맨 아래에 배치 (줄바꿈)
|
||
|
|
오버라이드 없음 → "검토 필요" 알림
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 구현 (Implementation)
|
||
|
|
|
||
|
|
### 1. 자동 줄바꿈 로직
|
||
|
|
|
||
|
|
**파일**: `gridUtils.ts` - `convertAndResolvePositions()`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 단계별 처리:
|
||
|
|
1. 비율 변환 + 원본 col 보존
|
||
|
|
converted = components.map(comp => ({
|
||
|
|
id: comp.id,
|
||
|
|
position: convertPositionToMode(comp.position, targetMode),
|
||
|
|
originalCol: comp.position.col, // ⭐ 원본 보존
|
||
|
|
}))
|
||
|
|
|
||
|
|
2. 정상 vs 초과 분리
|
||
|
|
normalComponents = originalCol ≤ targetColumns
|
||
|
|
overflowComponents = originalCol > targetColumns
|
||
|
|
|
||
|
|
3. 초과 컴포넌트 자동 배치
|
||
|
|
maxRow = normalComponents의 최대 row
|
||
|
|
overflowComponents → col=1, row=맨아래+1
|
||
|
|
|
||
|
|
4. 겹침 해결
|
||
|
|
resolveOverlaps([...normalComponents, ...wrappedComponents])
|
||
|
|
```
|
||
|
|
|
||
|
|
### 2. 검토 필요 판별
|
||
|
|
|
||
|
|
**파일**: `gridUtils.ts` - `needsReview()`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
function needsReview(
|
||
|
|
currentMode: GridMode,
|
||
|
|
hasOverride: boolean
|
||
|
|
): boolean {
|
||
|
|
// 12칸 모드는 기본 모드이므로 검토 불필요
|
||
|
|
if (GRID_BREAKPOINTS[currentMode].columns === 12) return false;
|
||
|
|
|
||
|
|
// 오버라이드가 있으면 이미 편집함 → 검토 완료
|
||
|
|
if (hasOverride) return false;
|
||
|
|
|
||
|
|
// 오버라이드 없으면 → 검토 필요
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**판단 기준 (최종)**: "이 모드에서 편집했냐 안 했냐"
|
||
|
|
|
||
|
|
### 3. 검토 필요 패널
|
||
|
|
|
||
|
|
**파일**: `PopCanvas.tsx` - `ReviewPanel`
|
||
|
|
|
||
|
|
```typescript
|
||
|
|
// 필터링
|
||
|
|
const reviewComponents = visibleComponents.filter(comp => {
|
||
|
|
const hasOverride = !!layout.overrides?.[currentMode]?.positions?.[comp.id];
|
||
|
|
return needsReview(currentMode, hasOverride);
|
||
|
|
});
|
||
|
|
|
||
|
|
// UI
|
||
|
|
<ReviewPanel
|
||
|
|
components={reviewComponents}
|
||
|
|
onSelectComponent={onSelectComponent} // 클릭 시 선택
|
||
|
|
/>
|
||
|
|
```
|
||
|
|
|
||
|
|
**변경 사항**:
|
||
|
|
- 기존: `OutOfBoundsPanel` (주황색, 드래그로 복원)
|
||
|
|
- 변경: `ReviewPanel` (파란색, 클릭으로 선택)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 결과 (Consequences)
|
||
|
|
|
||
|
|
### 긍정적 효과
|
||
|
|
|
||
|
|
| 효과 | 설명 |
|
||
|
|
|------|------|
|
||
|
|
| **정보 손실 방지** | 모든 컴포넌트가 항상 그리드 안에 표시됨 |
|
||
|
|
| **사용자 부담 감소** | 자동 배치를 먼저 제공, 필요시에만 편집 |
|
||
|
|
| **의도 명확화** | "숨김" ≠ "검토 필요" (기능 분리) |
|
||
|
|
| **뷰어 호환** | 자동 배치가 뷰어에도 적용됨 |
|
||
|
|
|
||
|
|
### 트레이드오프
|
||
|
|
|
||
|
|
| 항목 | 설명 |
|
||
|
|
|------|------|
|
||
|
|
| **스크롤 증가** | 아래로 자동 배치되면 페이지가 길어질 수 있음 |
|
||
|
|
| **자동 배치 품질** | 사용자가 원하지 않는 위치에 배치될 수 있음 |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 사용자 시나리오
|
||
|
|
|
||
|
|
### 시나리오 1: 수용 (자동 배치 그대로)
|
||
|
|
```
|
||
|
|
1. 12칸에서 컴포넌트 A, B, C 배치
|
||
|
|
2. 4칸 모드로 전환
|
||
|
|
3. 시스템: 자동 배치 + "검토 필요 (3개)" 알림
|
||
|
|
4. 사용자: 확인 → "괜찮네" → 아무것도 안 함
|
||
|
|
5. 결과: 자동 배치 유지 (오버라이드 없음)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 시나리오 2: 편집 (오버라이드 저장)
|
||
|
|
```
|
||
|
|
1. 12칸에서 컴포넌트 A, B, C 배치
|
||
|
|
2. 4칸 모드로 전환
|
||
|
|
3. 시스템: 자동 배치 + "검토 필요 (3개)" 알림
|
||
|
|
4. 사용자: A 클릭 → 드래그/리사이즈
|
||
|
|
5. 결과: A 오버라이드 저장 → A 검토 완료
|
||
|
|
6. "검토 필요 (2개)" (B, C만 남음)
|
||
|
|
```
|
||
|
|
|
||
|
|
### 시나리오 3: 보류 (나중에)
|
||
|
|
```
|
||
|
|
1. 12칸에서 컴포넌트 A, B, C 배치
|
||
|
|
2. 4칸 모드로 전환
|
||
|
|
3. 시스템: 자동 배치 + "검토 필요 (3개)" 알림
|
||
|
|
4. 사용자: 다른 모드로 전환 또는 저장
|
||
|
|
5. 결과: 자동 배치 유지, 나중에도 "검토 필요" 표시
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 기능 비교
|
||
|
|
|
||
|
|
| 구분 | 역할 | 뷰어에서 | 판단 기준 |
|
||
|
|
|------|------|---------|----------|
|
||
|
|
| **검토 필요** | 자동 배치 알림 | **보임** | 오버라이드 없음 |
|
||
|
|
| **숨김** | 의도적 숨김 | **안 보임** | hidden 배열에 ID |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 대안 (Alternatives Considered)
|
||
|
|
|
||
|
|
### A안: 완전 자동 (채택 ✅)
|
||
|
|
- 모든 초과 컴포넌트 자동 배치
|
||
|
|
- "검토 필요" 알림으로 확인 유도
|
||
|
|
- 업계 표준 (Webflow, Retool)
|
||
|
|
|
||
|
|
### B안: 선택적 자동 (미채택)
|
||
|
|
- 첫 전환 시만 자동 배치
|
||
|
|
- 사용자가 원하면 "화면 밖"으로 드래그
|
||
|
|
- 복잡성 증가
|
||
|
|
|
||
|
|
### C안: 수동 배치 유지 (미채택)
|
||
|
|
- 기존 "화면 밖" 패널 유지
|
||
|
|
- 사용자가 모든 모드 수동 편집
|
||
|
|
- 사용자 부담 과다
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 참고 자료
|
||
|
|
|
||
|
|
### 업계 표준 (2026년 기준)
|
||
|
|
- **Grafana, Tableau**: Masonry Layout (조적식)
|
||
|
|
- **Retool, PowerApps**: Vertical Stacking (수직 스택)
|
||
|
|
- **Webflow, Framer**: CSS Grid Auto-Placement
|
||
|
|
|
||
|
|
**공통점**: "Fluid Reflow (유동적 재배치)" - 정보 손실 방지
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## 관련 파일
|
||
|
|
|
||
|
|
| 파일 | 변경 내용 |
|
||
|
|
|------|----------|
|
||
|
|
| `gridUtils.ts` | convertAndResolvePositions, needsReview 추가 |
|
||
|
|
| `PopCanvas.tsx` | ReviewPanel로 변경 |
|
||
|
|
| `PopRenderer.tsx` | isOutOfBounds import 제거 |
|
||
|
|
| `pop-layout.ts` | 타입 변경 없음 (기존 구조 유지) |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**결론**: 자동 줄바꿈 + 검토 필요 시스템으로 정보 손실 방지 및 사용자 부담 최소화
|