Files
life-helper/docs/design/311-llm-warmup/UX-REVIEW.md
joungmin 1fa4f24a8a [02-Architect] #311 design spec + UX-Reviewer persona for LLM warm-up
- docs/design/311-llm-warmup/README.md — 기능 설계서. ChatWarmupController (5-state) + GemmaLlmService _loadingFuture concurrent guard + ModelLifecycle.quickCheck (lightweight ready).
- docs/design/311-llm-warmup/UX-REVIEW.md — UX-Reviewer parallel pass. Strong 4 + Suggest 2 권고. 입력창 enabled 유지 (타이핑 가능) + hintText 만 교체 + 상태-행동 분리.
- docs/design/311-llm-warmup/fn-chat_warmup_controller.md — start/retry 상세 + 빠른 경로 (isLoaded 시 Loading skip).
- docs/design/311-llm-warmup/fn-concurrent_load_guard.md — _loadingFuture 패턴 + whenComplete cleanup.
- .claude/agents/ux-reviewer.md — 신규 페르소나 (02-Architect 단계 내 parallel reviewer, 카테고리 부여 X).

AC 8 → 12 (UX 신규 4건 통합). OQ 3건 모두 해소. ADR 없음 (backward-compatible 추가).

Refs #311 #260
2026-06-15 11:41:03 +09:00

8.7 KiB

UX 리뷰: ChatScreen LLM warm-up (#311)

검토: [AI] UX-Reviewer · 대상: ./README.md v1 + Planner AC 8개 · 날짜: 2026-06-15 위치: 02-Architect 단계 내 parallel review. Architect 가 흡수 후 03-Developer 인계.

요약

설계서가 백엔드 흐름 (state 머신, concurrent guard, lifecycle) 은 견고하다. 그러나 사용자가 실제로 보는 표면 — 라벨 톤, spinner 위치, 빠른 전이 시 깜빡임, 실패 시 다음 행동 — 에는 결정 안 된 것이 너무 많고 (OQ 3건), 결정된 부분도 마찰을 만들 위험이 있다. 6가지 권고를 아래에 정리.


[Strong] R1 — "AI 준비 중…" 을 입력창 자리에 박으면 안 됨

관점: 마찰 / 흐름 / 정신 모델 근거:

  • README §5 / §6 은 "입력창 자리에 라벨 + spinner" 를 기본안으로 둠.
  • 그러면 사용자가 메시지를 미리 타이핑해두는 행동 자체가 차단된다. 사용자는 모델 로드를 기다리는 동안에도 "어떤 질문을 할지" 머릿속에서 정리하면서 손가락은 이미 키보드 위에 있다.
  • 입력창이 사라지면 사용자는 "왜 안 보이지?" 하고 한 번 더 추론해야 한다 (마찰 +1).
  • 더 큰 문제: ChatScreen 의 ListView 영역이 비어 있는 첫 진입 시점에 입력창까지 사라지면 화면 전체가 spinner 하나뿐이 된다 — "이 앱이 멈췄나?" 시그널.

제안 (강력 권고):

  • 입력창은 항상 보이게 유지. enabled: false 로만 잠그고 hintText 만 교체:
    • 평상: "습관 추가, 기록, 카탈로그 질문…"
    • warmup: "AI 준비 중… 잠시만요"
  • send 버튼 자리에 CircularProgressIndicator(strokeWidth: 2) 표시 (현재 isStreaming 처리와 동일 패턴 — 일관성 ↑).
  • 별도 상단 라벨/배지 추가 X. 사용자는 send 버튼이 spinner 인 것 + hint 한 줄로 충분히 추론 가능.

README 영향: §3 AC3, §5 다이어그램, §6 binding 절 모두 수정.


[Strong] R2 — 사용자가 텍스트 입력하고 send 누르면 어떻게 되나? (현재 설계는 침묵)

관점: 정신 모델 / 마찰 근거:

  • R1 권고를 받아들이면 입력창은 보이지만 enabled: false. 사용자가 키보드를 띄우고 타이핑하려 하면 → 반응 없음. 또 다른 마찰.
  • 만약 enabled: true 로 두고 send 만 disable 하면, 사용자가 메시지를 친 뒤 send 를 누르려는 순간 "왜 안 가지?" 로 또 다른 마찰.
  • 어느 쪽이든 사용자의 의도 (메시지를 보내고 싶음) 와 시스템의 상태 (아직 못 받음) 사이의 간극 이 풀리지 않음.

제안 (강력 권고):

  • 입력창은 enabled: true 로 두어 타이핑은 허용한다. 사용자가 미리 메시지를 작성하도록.
  • send 버튼은 disabled + spinner. 누를 수는 없음.
  • warmup 완료 시점에 사용자가 이미 타이핑해둔 메시지가 있으면 → send 버튼 자동 활성화. (자동 send 까지는 X — 사용자 의도 확인 필요)
  • AC4 에 한 줄 추가: "warmup ready 시점에 입력창의 텍스트가 비어있지 않으면 send 활성화."

README 영향: §3 AC3/AC4 보강.


[Suggest] R3 — 첫 warmup 은 "예상 시간" 한 마디 더

관점: 정신 모델 / 인지된 지연 근거:

  • 사용자에게 "AI 준비 중" 만 보여주면 — 0.5초 후에도, 5초 후에도, 10초 후에도 같은 라벨. 정신 모델은 점점 "이게 멈췄나?" 로 기운다.
  • Gemma 4 E2B native init + mmap 은 디바이스에 따라 2-8초 범위로 추정 (cold launch). 첫 진입 시 한 번뿐이고 두 번째 진입부터는 거의 즉시 (isLoaded=true) — 즉 사용자가 이 라벨을 길게 보는 건 첫 진입 단 한 번.
  • 그 한 번을 부드럽게 만들 가치가 있다.

제안:

  • hint 를 "AI 준비 중… 첫 시작은 몇 초 걸려요" 로 한 번만 명시.
  • 1회성 SnackBar 도 검토할 수 있으나 — 사용자가 곧바로 입력창 영역으로 시선이 가므로 hint 한 줄로 통합하는 게 단순.

README 영향: §3 AC3 의 라벨 문안, §12 OQ-1 해소.


[Strong] R4 — 빠른 경로 (이미 loaded) 의 라벨 깜빡임을 명시적으로 차단

관점: 정신 모델 근거:

  • README §9 의 "ChatScreen 재진입" 케이스 + fn-spec 의 "빠른 경로" 분기로 Loading state skip 처리가 들어가 있음 — 좋다.
  • 하지만 §12 OQ-3 에 "라벨 깜빡임 가능 — 미해결" 이 남아있어 모순. fn-spec 의 빠른 경로가 Loading 을 스킵하므로 깜빡임은 일어나지 않음.
  • 명확히 못 박을 것.

제안:

  • OQ-3 를 OQ 에서 제거하고 §9 의 "빠른 경로" 분기 + fn-spec 의 step 2 를 명시적으로 인용한 결정 노트로 전환.
  • min display time (300ms 등) 같은 인위 지연은 도입 금지 — 사용자에게 거짓 작업을 보여주는 안티패턴.

README 영향: §9 endorse 표현, §12 OQ-3 삭제.


[Strong] R5 — 실패 메시지의 다음 행동이 약함

관점: 에러 회복 / 마찰 근거:

  • README §9 의 실패 메시지:
    • "AI 모델 파일을 찾을 수 없어요. 설정에서 다시 다운로드해주세요." — 좋음 (다음 행동 명시).
    • "AI 시작 중 오류가 발생했어요. 잠시 후 다시 시도해주세요." — 약함. "잠시 후" 가 얼마인지, "다시 시도" 가 어떻게 인지 불명.
  • 사용자는 두 가지 의문: ① 이게 일시적 문제인가, 영구적 문제인가 ② 내가 뭘 해야 하나.

제안:

  • "다시 시도" 버튼이 있으니, 메시지에서 "잠시 후 다시 시도해주세요" 는 빼고 상태 + 행동 분리:
    • 상태: "AI 를 시작하지 못했어요."
    • 행동: 별도 [다시 시도] 버튼 (이미 설계됨).
  • AC5 에 "한국어 메시지는 상태만 기술, 행동은 버튼이 담당" 명시.
  • 3회 연속 실패 시점에는 보조 안내 ("문제가 계속되면 앱을 재시작해보세요") — 후속 polish 로 deferrable.

README 영향: §9 메시지 사전 갱신, §3 AC5 보강.


[Suggest] R6 — 재시도 버튼 위치는 error container 안

관점: 흐름 / 접근성 근거:

  • README §12 OQ-2 가 "error container 내부 vs 입력창 옆 icon" 으로 열어둠.
  • 입력창 옆 icon 은 평상시에는 없는 자리에 갑자기 나타나 사용자가 학습해야 함. 게다가 send 자리 근처에 또 다른 액션 = 오탭 위험.
  • error container 는 이미 실패 메시지 영역이라 컨텍스트 일관 + 사용자가 "여기서 다음 행동" 학습.

제안:

  • OQ-2 → 결정: error container 내부 OutlinedButton('다시 시도').
  • container 좌우 패딩, 메시지와 버튼은 column 으로 분리, 버튼은 우측 정렬.

README 영향: §12 OQ-2 해소.


AC 보강 권고 (UX-Reviewer 가 작성한 추가 AC)

UX 관점에서 검증 가능한 새 AC 를 제안 (Architect 가 흡수 시 README §3 에 추가):

  • AC9 (신규) Warmup 중 입력창은 enabled: true 로 타이핑 가능. send 만 disabled + spinner. → R1+R2.
  • AC10 (신규) Warmup ready 시점에 입력창에 비어있지 않은 텍스트가 있으면 send 자동 활성화 (자동 send 는 X). → R2.
  • AC11 (신규) isLoaded=true 인 재진입 시 Loading state 가 1 frame 이라도 노출되지 않는다 (위젯 테스트로 verify). → R4.
  • AC12 (신규) 실패 메시지는 상태 기술만, 행동은 [다시 시도] 버튼이 담당. 메시지 본문에 "다시 시도해주세요" 같은 명령형 X. → R5.

마이크로카피 사전 (Architect 가 채택 시 README §6 또는 별도 부록)

상태 한국어 라벨 위치
warmup loading 입력창 hintText: AI 준비 중… 첫 시작은 몇 초 걸려요 입력창
warmup ready hintText: 습관 추가, 기록, 카탈로그 질문… 입력창 (기존 유지)
warmup unavailable (라벨 없음 — 평상시와 동일)
warmup failed (file missing) error container: AI 모델 파일을 찾을 수 없어요. 설정에서 다시 다운로드해주세요. + [설정으로 가기] error container
warmup failed (other) error container: AI 를 시작하지 못했어요. + [다시 시도] error container

[설정으로 가기] 는 R5 의 file-missing 케이스에서 "설정에서 다시 다운로드" 문구의 다음 행동을 한 탭으로 짧게 만드는 보조 권고. 채택은 Architect 재량.

Architect 가 결정해야 할 것 (요약)

  • Strong R1, R2, R4, R5 — 채택 또는 명시 거절 (OQ 로 남기지 말 것).
  • Suggest R3, R6 + 마이크로카피 사전 + [설정으로 가기] — 재량.
  • 새 AC 4건 — 채택 시 README §3 에 통합.