Fix
- ChatScreen body 를 SafeArea(top: false) 로 감쌈. Android edge-to-edge
모드에서 시스템 nav bar 가 입력창을 덮던 문제 해결.
Dev (#342)
- userTurn catch 블록이 e.toString() + stack trace 를 error 상태에 저장.
- 빨간 에러 컨테이너를 SingleChildScrollView + SelectableText (monospace)
+ 최대 화면 1/3 높이 제약. 스크롤 + 복사 가능. release 빌드에서도
full stack 노출 (#342 종료 후 follow-up 으로 좁힘).
테스트: chat_session_controller_test 8/8 통과.
APK: app-release.apk 301.0MB SHA 02a5d1c8.
Refs #342
SettingsScreen pop 후 ChatScreen state 가 여전히 Failed(fileMissing)
인 회로 — 사용자가 다운로드를 끝내고 돌아와도 ChatScreen 전체를
pop & re-push 해야 회복되는 UX gap.
Navigator.push().then((_) => retry()) 로 SettingsScreen 닫힐 때
자동 retry. quickCheck 다시 → ready 면 load → Ready 회복.
다운로드 안 했으면 다시 Failed 로 떨어져 같은 배너 노출 (일관).
`context.mounted` 가드는 ChatScreen 이 dispose 된 race 대비.
Refs #311
UX-REVIEW.md §마이크로카피 사전에서 fileMissing 케이스에 [설정으로
가기] 를 명시했으나 Developer 구현은 모든 kind 가 [다시 시도] 였음.
fileMissing 에서 retry 해도 파일이 없어 같은 결과 → 사용자 막힘.
- ChatWarmupFailureKind.fileMissing → SettingsScreen push (다운로드
재시도 경로 노출)
- ChatWarmupFailureKind.runtime → 기존 retry() 유지 (일시적 회복 가능)
여전히 메시지 본문은 상태 기술만 (AC12), 행동은 버튼이 담당 (UX R5).
Refs #311
ChatScreen 마운트 시 백그라운드 native init 으로 첫 send 시점에 native
load 지연을 안 보이게 한다. 12개 AC + UX-Reviewer 의 6개 권고 모두 코드
반영.
핵심 변경:
- `chat_warmup_provider.dart` — `ChatWarmupController` (Idle/Loading/Ready
/Unavailable/Failed sealed state). fast path (`llm.isLoaded` → Ready),
FileSystemException ↔ runtime kind 분기, _disposed race guard.
- `model_lifecycle.dart` — `quickCheck()`: 2.4GB SHA-256 hashing 없이
meta_kv + 파일 존재만 보고 ready 추정 (R4 UX 권고).
- `gemma_llm_service.dart` + `llm_service.dart` — `_loadingFuture` 동시
호출 가드. 두 caller 가 동시에 load() 해도 native init 은 1 회만.
- `chat_screen.dart` — initState postFrameCallback 에서 warmup.start().
warmup 상태에 따라 hintText / spinner / 실패 banner 분기.
AC coverage (12개):
- AC1~AC8: ChatWarmupController unit (chat_warmup_test.dart 8 tests).
- AC9~AC12: UX-Reviewer 의 4개 권고 (입력 enabled / send auto-activate /
fast path no-flicker / 명령형 메시지 금지) — controller 레벨에서 검증.
테스트: 167 passed (1 pre-existing skip). `flutter analyze` clean.
Refs #311
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>