# 06 — UX Contracts: 일일 체크인 · 추천 · 주간 reflection (#204) > 부모 설계서: [README.md](./README.md) > 참조: R8 (≤ 60 초), R9 (variant 무제한), R10 (minimum_ratio hint), ADR-0001. ## 1. 일일 체크인 화면 (R8 ≤ 60 초 보장) ### 1.1 화면 계약 ``` ┌────────────────────────────────┐ │ 오늘 · hb_… 아침 햇빛 10분 │ ← AppBar (≤ 1 줄) ├────────────────────────────────┤ │ │ │ 장소 │ ← chip row 1 (수평 스크롤) │ [집] [짐] [출장] [외부] + │ │ │ │ 컨디션 │ ← chip row 2 │ [좋음] [보통] [나쁨] │ │ │ │ ─────────────────────────────│ │ 추천 도즈 │ ← variant card (자동 추천) │ 📍 짐-메인 │ │ 데드 리프트 60kg 5x5 │ │ [○ 완료] [다른 옵션 ▾] │ │ │ └────────────────────────────────┘ ``` 탭 수: 1. 장소 chip 1 탭 2. 컨디션 chip 1 탭 3. ○ 완료 1 탭 (또는 다른 옵션 → override 1 탭 → ○ 완료 1 탭) → 최소 3 탭, 최대 5 탭. R8 (≤ 60 초) 자연스럽게 달성. ### 1.2 R8 측정 `CheckInTimer` 가 화면 진입 시 시작, ○ 완료 탭 시 종료. - `elapsed > 60s` 면 dev console warning. - `elapsed > 120s` 면 사용자에게 toast "체크인이 오래 걸렸어요. UI 개선 피드백" (선택, A/B). ### 1.3 데이터 흐름 ``` 화면 진입 └─ CheckInTimer.start() └─ DAO.loadVariantsForHabit(habitId) → List 장소 chip 선택 └─ controller.setLocation(s) └─ recommendVariant(habit, ctx(loc, null)) → 부분 추천 (location 만) 컨디션 chip 선택 └─ controller.setCondition(s) └─ recommendVariant(habit, ctx(loc, cond)) → 최종 추천 + score [다른 옵션] 탭 └─ 모달 시트: 모든 variants (score 내림차순) + 사용자 선택 [○ 완료] └─ TrackerDao.recordCheckIn( habitId, today, 'done', variantId, ctx(loc, cond)) └─ computeStreak(habitId, today) → StreakState └─ UI: 오늘 셀 ○ + tier badge 갱신 └─ if T1~T4 진입 → Celebration 모달 └─ CheckInTimer.stop() ``` ### 1.4 blank 처리 - "○ 완료" 안 누르고 화면 종료 → tracker_entry insert 안 됨. `blank` 는 자동 — 명시적 insert 불요. (`computeStreak` 은 entry 부재 = blank 로 해석.) ## 2. 추천 매칭 알고리즘 (의사코드) > 상세는 [fn-recommend-variant.md](./fn-recommend-variant.md). ``` function recommendVariant(habit, ctx) -> {variant, score, reason} variants = habit.dose_variants if variants.isEmpty: return null scored = [] for v in variants: s = scoreVariant(v, ctx) scored.push({v, s}) scored.sortByDesc(s.score, breakTies = v.sortOrder) best = scored[0] if best.score > 0: return best else: # fallback: is_minimum=true 첫 variant minimum = variants.firstWhere(v -> v.is_minimum, orElse: variants.first) return {minimum, score: 0, reason: 'fallback_minimum'} function scoreVariant(v, ctx) -> int score = 0 if ctx.location in v.context_tags: score += 2 if ctx.condition in v.condition_tags: score += 2 if v.is_minimum and ctx.condition == 'bad': score += 1 return score ``` 복잡도: O(N) per call, N = variants 개수. R9 무제한이지만 실용 N ≤ 약 10 → < 50 µs. ## 3. 사용자 override 흐름 ``` [다른 옵션 ▾] 탭 ↓ 모달 sheet (전체 variants 표시, score 표시) ↓ 사용자 선택 → controller.overrideVariant(variantId) ↓ 변경 후에도 ○ 완료 가능. tracker_entry.variant_id = overridden. ``` > override 시에도 score 0 의 fallback variant 도 선택 가능 — 사용자 autonomy (SDT). ## 4. habit 생성 폼 (vertical slice — 최소) ``` [1] protocol 선택 (목록 — category 탭) ↓ [2] frame.framed_text 입력 (L2 default, L3 toggle) ↓ validateFrameLevel (L0/L1 감지 시 변환 제안 모달) ↓ [3] anchor.when, after_what, where 입력 (Tiny Habits) ↓ [4] 기본 variant 자동 생성 (label='기본', dose_text=protocol.min_dose_for_start, is_minimum=true) ↓ checkActiveHabitQuota (R1/R2) ↓ [5] [habit 생성] → HabitDao.insertWithVariants ``` > dose_variants 추가 입력은 v1 vertical slice 에선 생략 가능 (기본 1 개 자동). 추가 입력 UI 는 같은 Phase 안의 후속 sub-task 로 둘 수 있음 (Developer 가 시간 보고 분리). ## 5. 주간 reflection 화면 ### 5.1 진입 수동 — Tracker view 의 "이번 주 회고" 버튼. ### 5.2 minimum_ratio 표시 ``` 이번 주 (6/8 ~ 6/14) ── kept (잘 한 것): _________ ── missed (못 한 것): _________ ── adjust (다음 주 변경 1 개): _________ [hint] 이번 주 done 중 'tiny' (최소 도즈) 비율: 43% ↑ 강제 임계값 없음. 정보 hint only (R10). ``` ### 5.3 계산식 ``` minimum_ratio = (done && is_minimum_true 카운트) / (done 카운트) done = 0 → null (UI: "이번 주 done 없음") ``` 상세는 [fn-weekly-minimum-ratio.md](./fn-weekly-minimum-ratio.md). ### 5.4 표시 트리거 - minimum_ratio ≥ 0.7 → UI hint: "tiny 가 자주 고정되고 있어요. 컨디션 좋은 날 메인 도즈를 시도해보세요." (이것도 강제 X, 단순 메시지) - minimum_ratio ≤ 0.2 → UI hint: "본 도즈를 잘 유지하고 있어요." (긍정 강화) - 그 외 → 표시만, 메시지 없음. ## 6. Celebration (T0~T4) | 진입 | UI | |------|-----| | T0 (매일 완료 직후) | 작은 ○ 애니메이션 + user.preferences.celebration_style 적용 (verbal/gesture/emoji/silent) | | T1 (3 연속 done) | 모달 1 회: "3 회 스트릭. 작은 변화의 시작." + (선언된 reward_text 있으면 표시) | | T2 (7 일) | 모달 1 회: 주간 축하 + reward_text | | T3 (30 일 중 24 일) | 모달 + reward_claim insert 유도 (사용자가 fulfilled=true 체크 가능) | | T4 (42 일 = 6 주) | 모달 + phase 종료 안내 (status='completed' 토글) | > celebration 은 한 번만. 동일 tier 재진입은 표시 X (per-tier flag). ## 7. R8 보장 검증 - AC: 체크인 평균 elapsed < 30 초 (수동 QA + 측정). - 변동성: 새 variant 만들 때 (최초) 는 90 초 허용 (도즈 입력 자체가 사용자 의도적 작업). ## 8. 접근성 / 한국어 - 최소 폰트 16 sp. - 모든 chip text 한국어. 카테고리는 SoT 의 ko 제목 그대로. - 다크모드 대응은 Phase 2 (out of scope).