Files
life-helper/CHANGELOG.md
joungmin c18dca1def [Designer] #342 UX round 2 — chat 빈 상태 + 한국식 날짜 + 표현 방식
남은 P1/P2 3건.

- ChatScreen 빈 상태: 아이콘 + 한 줄 설명 + 예시 prompt 4개
  (tap → _textCtrl 자동 채움, 자동 send X).
- CheckIn 날짜: '2026-06-15' raw → '6월 15일 (월)' 한국식.
  DB 저장은 _ymd 유지.
- HabitCreate '프레임 레벨' → '표현 방식' + helperText.
  아이템: '조건부 행동 (예: 아침에 햇빛 받기)' / '정체성 (예: 나는 일찍 자는 사람)'.
- 설계서 #342 README — D 섹션 + AC-D1/D2/D3 추가.
- CHANGELOG v0.4.2 UX round 2 블록.

167 tests passed, analyze clean.

Refs #342
2026-06-15 15:28:03 +09:00

174 lines
14 KiB
Markdown

# Changelog
본 프로젝트의 모든 의미있는 변경은 본 파일에 기록한다.
형식: [Keep a Changelog](https://keepachangelog.com/) · 버전: [SemVer](https://semver.org/).
## [0.4.2] — 2026-06-15 (hotfix, dev)
### Fixed (Redmine #342)
- **ChatScreen 하단 잘림** — Android edge-to-edge 모드에서 시스템 nav bar (3-button / gesture handle) 가 입력창을 덮던 문제. `Scaffold.body``SafeArea(top: false, …)` 로 감쌈. AppBar 가 이미 top inset 처리하므로 top 만 false.
### UX round 1 — raw enum 노출 정리 (Redmine #342 추가)
- **습관 카드 부제** — `build · L3 · …` (raw enum) → `만들기 · …`. FrameLevel 노출 제거 (시스템 규약이라 사용자 가치 낮음).
- **스트릭 화면 현재 티어** — `T0` / `T1` raw → `🌱 새싹` / `🥉 3회 도전` / `🥈 7일 형성` / `🥇 30일 정착` / `🏆 6주 완주` 이모지+한국어 라벨.
- **스트릭 강등 경고** — `Never miss twice 발동 — 티어 강등` (영문 잠언) → `이틀 연속 빠졌어요. 한 단계 강등됐습니다.`.
- **스트릭 hero 위계** — 현재 스트릭을 `displayLarge` 큰 숫자 + 티어 라벨로 시각 강조 (사용자의 핵심 동기 지표).
- **습관 추가 드롭다운** — `만들기 (build)``만들기` (영어 식별자 병기 제거).
- 신규 `app/lib/ui/labels.dart` — domain enum 의 한국어 라벨 매핑 단일 지점. domain layer 에 `koreanLabel` 두지 않음 (관심사 분리).
### UX round 2 — 빈 상태 + 날짜 + 라벨 명확화 (Redmine #342 추가)
- **ChatScreen 빈 상태 안내** — 첫 진입 시 빈 메시지 리스트 대신 아이콘 + 한 줄 설명 + 예시 prompt 4개 (`아침 햇빛 받기 습관 추가해줘`, `오늘 운동 했어`, `내 스트릭 보여줘`, `수면 프로토콜 알려줘`). tap → 입력창 자동 채움 (자동 send X, 사용자 수정 여지).
- **CheckIn 날짜 한국식** — `2026-06-15` raw → `6월 15일 (월)`. DB 저장은 `_ymd` 유지.
- **HabitCreate 표현 방식** — `프레임 레벨` (의미 모호) → `표현 방식` + helperText `행동 위주 vs 정체성 위주`. 아이템 라벨 `L2 · 조건부 긍정` / `L3 · 정체성``조건부 행동 (예: 아침에 햇빛 받기)` / `정체성 (예: 나는 일찍 자는 사람)` 식 예시 포함.
### Dev
- **LLM 실패 빨간 배너에 full message + stack trace** — 단말 진단을 위해 release 빌드에서도 노출. `LLM 응답 실패: <Type>\n<message>\n--- STACK ---\n<stack>` 형식. SelectableText + monospace + 최대 화면 1/3 높이 + scroll. 사용자 친화 메시지로 좁히는 작업은 #342 종료 후 follow-up.
## [0.4.1] — 2026-06-15
### Added — ChatScreen LLM warm-up (Redmine #311, follow-up of #260)
- **백그라운드 warm-up** — `ChatScreen` 진입 시 `ChatWarmupController``LlmService.load()` 를 백그라운드로 트리거. 첫 send 에서 cold native-init (수 초) 비용 제거.
- **`ModelLifecycle.quickCheck()`** (신규) — SHA-256 재해싱 없이 meta_kv + 파일 존재만으로 ready 추정. ~2.4GB Gemma 4 E2B 파일에 대한 매 mount 마다의 hash 비용 회피.
- **Concurrent load guard** — `GemmaLlmService.load()` + `MockLlmService.load()``_loadingFuture` 가드 추가. ChatScreen warm-up + 동시 `userTurn` lazy load 가 race 해도 native init 1회만 실행.
- **Sealed state machine** — `ChatWarmupState`: Idle / Loading / Ready / Failed(kind) / Unavailable. autoDispose StateNotifier + `_disposed` 가드로 unmount race 방지.
### UX (Designer + Reviewer)
- warmup 중 입력창 `enabled` 유지 — 사용자가 미리 메시지 작성 가능 (UX R1+R2).
- send 자리에 `CircularProgressIndicator(strokeWidth: 2)``isStreaming` 패턴과 일관.
- hintText 교체 — warmup: `AI 준비 중… 첫 시작은 몇 초 걸려요` / 평상: `습관 추가, 기록, 카탈로그 질문…`.
- 실패 메시지는 상태 기술만 (UX R5/AC12) — `AI 모델 파일을 찾을 수 없어요.` / `AI 를 시작하지 못했어요.`. 행동은 버튼이 담당.
- `_WarmupErrorBanner``kind` 분기:
- `fileMissing`**[설정으로 가기]** + `SettingsScreen` push + pop 후 자동 `retry()` (UX R5/R6).
- `runtime`**[다시 시도]** + 즉시 `retry()`.
- `isLoaded=true` 재진입 시 Loading state skip — 1 frame 라벨 깜빡임 방지 (UX R4/AC11).
### Added — Tests
- 167/167 passed (1 pre-existing skip) — 신규 12 (`chat_warmup_test.dart` 8 + `model_lifecycle_test.dart` quickCheck 4).
- AC1~AC2, AC5~AC7, AC11, AC12 controller-level 검증.
- AC3/AC4/AC8/AC9/AC10 widget E2E 는 deferred — `CircularProgressIndicator` 무한 ticker + `Future.delayed``pumpAndSettle` race. `chat_screen_test.dart` NOTE comment 에 사유 명시.
### Docs
- 설계서 `docs/design/311-llm-warmup/` (4 파일) — README + 2 fn-spec + UX-REVIEW.md.
- 신규 페르소나 `ux-reviewer.md` — 02-Architect 단계의 parallel review.
### Known follow-ups (후속 이슈 권장)
- Widget E2E 인프라 개선 (FakeAsync 또는 spinner 가짜 대체) — ticker race 해소.
- 다른 recovery loop 도 `Navigator.push().then((_) => retry())` 패턴 적용 검토.
## [0.4.0] — 2026-06-15
### Added — Phase 2-B in-app tool calling (Redmine #260)
- **In-process Dart tool runtime** (ADR-0005): MCP 와 동등한 capability 추상화를 별도 서버 없이 in-process Dart 함수로 구현. latency 거의 0.
- **6 tools** (`app/lib/ai/tools/`): `search_catalog`, `query_protocol`, `list_active_habits`, `get_streak` (read-only) / `add_habit`, `log_tracker_entry` (destructive).
- **Multi-turn loop** (`ChatSessionController`) — MAX_TURNS=4 안전 cap, 8-turn soft history warning. `ToolChoice.auto` 로 reply-only + tool call 모두 지원.
- **ConfirmGate 모달** — destructive tool 호출 시 AlertDialog (`이 작업을 수행할까요?`) 의무. 좁은 화면 SingleChildScrollView.
- **2KB result cap** (ADR-0005 §OQ-2) — `encodeToolResult` 가 ToolOk payload 초과 시 `_truncated:true` + `_hint` 로 잘림 (`chat_providers.dart:192` 에서 runtime wire).
- **R 규칙 enforce = 핸들러 책임** — 모델 prompt 학습 아닌 코드 게이트. R3 quota, R5 (habit,date) dedup, R7 회피 키워드, R8 XOR (build/break) 모두 ToolErr 코드로 노출.
- **ChatScreen** (`app/lib/ui/screens/chat_screen.dart`) — 신규 AI 코치 화면. HabitListScreen AppBar 의 🤖 entry (AI opt-in 시).
- **schema SoT = Dart 코드** (ADR-0005 §D-4) — `ToolDefinition.parametersSchema` Map 리터럴.
### Polish (Designer)
- ToolCallChatMessage 라벨 한국어화 (`_kToolKoreanLabels``add_habit → 습관 추가` 등 6종 매핑).
- ConfirmDialog content 를 SingleChildScrollView 로 감싸 좁은 폰 + 긴 description 대응.
- Streaming cursor `▍``Text.rich` 로 분리 후 `colorScheme.primary` 적용 — 다크 모드 contrast.
- AppBar tooltip `새 대화``새 대화 (이전 기록 비우기)`.
### Added — Tests
- 154/154 passed (1 skip) — 신규 41 → 43 (tool_envelope 6 + catalog_tools 7 + habit_tools 8 + tracker_tools 7 + dispatcher 6 + controller 8 + widget E2E 2).
- AC-9 회귀: 인위 `huge_dump` tool 로 `_truncated:true` + `_hint` 직접 검증.
- AC-10 widget E2E (`test/ui/chat_screen_test.dart`): add_habit 호출 → ConfirmDialog `수행` → habits +1 / `취소` → 무변화 + `취소됨` 라벨.
### Docs
- 설계서 `docs/design/260-gemma-tool-calling/` (5 파일, 844 라인) — README + 4 함수 fn-spec.
- ADR-0005 — In-app tool calling architecture (4 결정사항).
### Known follow-ups (후속 이슈 권장)
- `ToolDefinition.koreanLabel` 필드 도입 — 현재 `_kToolKoreanLabels` hardcoded.
- `log_tracker_entry` value=blank 시 confirm skip — 현재 done/blank 무차별 모달.
- `search_catalog` category matching case-insensitive — 모델 hallucination 대비.
### Release artifact
- `app-release.apk` 287MB (300.9MB raw / 287MB on-disk), SHA-256 `6670da0c4e9bf5e826174ebc48088540867d877cf58699119a519e2ffb40ea3a`.
- Build: `flutter build apk --release` (Gradle assembleRelease 106.4s).
## [0.3.0] — 2026-06-12
### Added — Phase 2-A OQ-1 resolved: real Gemma 4 E2B inference (Redmine #218)
- `GemmaLlmService` 본문 구현 — `flutter_gemma` 0.16.5 위에 Gemma 4 E2B 실 추론. `InferenceModel.createChat(modelType: gemma4, supportsFunctionCalls: true, toolChoice: required, tools: [...])` + `collectFunctionCall(stream)` 로 structured JSON 강제.
- `_LazyLlmService` (main.dart) — Mock ↔ Gemma 런타임 어댑터. 매 호출마다 `checkAvailability` 재평가 → opt-in/opt-out 즉시 반영 (앱 재시작 불필요).
- 실 모델 핀: `gemma-4-E2B-it.litertlm` 2.41GB, SHA-256 `181938105e0eefd105961417e8da75903eacda102c4fce9ce90f50b97139a63c` (HF `litert-community/gemma-4-E2B-it-litert-lm`).
- HF_TOKEN `--dart-define` 주입 — 빈 기본값으로 빌드 안전.
### Added — Device gate (AC-6)
- 플랫폼 채널 `life_helper/device_caps` (`MainActivity.kt``ActivityManager.MemoryInfo.totalMem`) — Android 단말 실 RAM 측정. `device_info_plus``isLowRamDevice` (~1GB) 로는 4GB 임계치 불가하여 채널 도입.
- `DeviceCapabilities` 추상 + `PlatformDeviceCapabilities` 구현 (테스트 주입 가능). `kAiMinRamBytes = 4 GiB`. fail-closed (`null` → false).
- `deviceMeetsAiRamProvider` (Riverpod `FutureProvider`) — `SettingsScreen` 토글 disabled + 안내 문구.
### Added — Tests
- 88/88 통과 — 신규 10 (`device_capabilities_test.dart` 7 + lazy resolve regression 3).
### Polish (Designer)
- AC-6 게이트 안내 톤 정렬 — "RAM 부족" → "이 단말에서는 AI 도움을 사용할 수 없어요 (RAM 4GB 이상 필요)".
- `_describe(missing, meetsRam:)` 분기 — 토글 disabled 상황에서 "토글 켜면" 모순 제거.
- 옵트아웃 다이얼로그 "다시 다시" 중복 → "처음부터".
### Fixed (Reviewer)
- `_LazyLlmService._delegate` sticky cache — 첫 호출 시점의 delegate 종류가 앱 재시작까지 유지되던 버그 (Mock → Gemma 전환 안 됨). re-resolve + (kind + modelPath) 일치 시만 캐시 재사용.
- Reference 문서 nit 3건 — `215-ai-frame-suggest.md` (L184 채널 사실 정정 / L186 F1 follow-up 매핑 / L191 OOS 기준).
### Release artifact
- `app-release.apk` 286MB, SHA-256 `4a237d5124bfcd56aaa8c0ae89060a9ecf9ce7cc739f0b056ce66e9b9ca6b54a`.
### Known limitations (deferred to #219~#222)
- **AC-7** (실 단말 cold-start 예산) — DEFER. 실기기 E2E 검증은 본 릴리스 후 권고.
- **#219** F1: 60초 idle auto-unload.
- **#220** GemmaLlmService.load 동시성 가드 + `isThinking:false` 명시.
- **#221** AC-10 한국어 corpus ≥70%.
- **#222** HF_TOKEN keystore 기반 secret 전환.
---
## [0.2.0] — 2026-06-12
### Added — Phase 2-A: On-device Gemma 4 frame suggestion (Redmine #215)
- `LlmService` 추상 인터페이스 + `MockLlmService` (테스트/v1 런타임 주입) + `GemmaLlmService` stub (OQ-1 시점 활성화).
- `ModelLifecycle` — Range 기반 재개 가능 다운로드 + SHA-256 검증 + opt-out 즉시 삭제.
- `ModelDownloadController` (Riverpod `StateNotifier`) — start / pause / resume / cancel.
- 도메인 함수 `suggestFrame` + `buildFewShotPrompt` + `parseFrameCandidates` (모두 순수, graceful — 실패 시 빈 리스트).
- `FrameSuggestionDialog` — L2/L3 후보 카드 + 컬러 배지.
- `SettingsScreen` 신규 — AI 도움 토글 (기본 OFF, 옵트인 시 동의 다이얼로그 → 다운로드 시작), 진행률 + 일시정지/재개/취소, 옵트아웃 시 즉시 purge + 해제 공간 토스트.
- `HabitCreateScreen` 의 "AI 제안" 버튼 3분기 (hidden / visible+disabled+tooltip / enabled).
- ADR-0003 — on-device LLM Gemma 도입 결정 + 5 대안 기각.
- 설계서 `docs/design/215-gemma-frame-suggest/` (README + fn-suggest_frame + fn-model_lifecycle).
- 신규 31 테스트 (parse 8 + few_shot 7 + suggest 12 + lifecycle 7 + AC2 state 3 + AC6 widget 4).
- 9/10 AC PASS — AC10 (한국어 평가 corpus ≥70%) DEFER → OQ-1 해결 후 수행.
### Architecture
- `lib/data/ai/` (I/O 경계) ↔ `lib/domain/ai/` (순수 도메인) 분리.
- `meta_kv` 5 키 추가 (`ai_opt_in` / `ai_model_path` / `ai_model_sha256` / `ai_download_state` / `ai_download_bytes`) — schema migration 0.
- 프라이버시: 사용자 raw text 단말 밖 송출 0. 추론 100% 단말.
### Known limitations
- **OQ-1 미해결**: 실제 Gemma 4 E2B Q4_0 모델 URL + SHA-256 미확정. `_kModelUrlPlaceholder` / `_kModelShaPlaceholder` 상태. v1 런타임은 `MockLlmService` 주입 — opt-in 토글 시 다운로드는 placeholder URL 로 실패 (graceful 처리).
- **F1**: 60초 idle auto-unload 미구현 (현재 stub 라 무의미).
- **F2**: `ModelLifecycle.purge()``File.delete()` try/catch 미감쌈 (placeholder URL 라 도달 불가).
- 위 3건 모두 OQ-1 후속 이슈에서 처리.
### Polish (Designer)
- 다운로드 타일 상태 라벨 + 컬러 텍스트 + 라운드 progress bar + `FilledButton.tonalIcon` 재개/재시도.
- `_friendlyError()` — 내부 코드 (`network:` / `http ` / `stream:` / `sha mismatch`) 를 한국어 다음행동 문구로 매핑.
- 옵트인/아웃 다이얼로그 `_Bullet` 위젯으로 정렬.
- `FrameCandidate` 카드에서 confidence% 표기 제거 (내부값).
---
## [0.1.0] — 2026-06-12 (retro-tagged)
### Added — Phase 1 MVP (Redmine #204)
- Flutter 3.x + Drift 21 테이블 (habits, habit_variants, checkins, ladders, frame_patterns, reward_menu_items, references, protocols, methodologies, diet_patterns ...).
- 도메인 함수 6개 (validateFrameLevel / judgeActiveHabitQuota / computeStreak / 등).
- UI 화면 4개 (HabitListScreen / HabitCreateScreen / CheckinScreen / RewardListScreen).
- 시드 데이터 8 JSON (refs 84 / protocols 34 / break 8 / common 5 / methodology 21 / frame 30 / reward 30 / diet 5).
- ADR-0001 (dose_variants 도입) + ADR-0002 (정규화 방식).
- 62 테스트 통과.