demo-final 에서도 clinical mode 테스트 중인데 매 세션마다 Upload 버튼을
눌러야 반영되는 문제. tab-navigation / cloud-mvp 브랜치의 자동 재시도
로직을 demo-final 구조에 맞춰 축소 이식.
원인:
- demo-final ClinicalSessionStore.endMeasurement 자체에 자동 업로드
로직이 아예 없었음 (주석: "labdb 업로드는 자동 X — Upload 버튼으로
수동 트리거").
- recentFinalizedFolders / capturedCount 히스토리 구조도 없어 단순
cherry-pick 은 불가.
수정:
- endMeasurement: isRegistered (apiKey 존재) 면 uploadAsync 자동 호출.
- LabdbAutoRetry object 신설 (services/labdb/LabdbAutoRetry.kt):
demo-final 은 lastFinalizedFolder 단일 값만 있으므로
listOfNotNull(lastFinalizedFolder) 로 축소. tab-nav/cloud-mvp 는
recentFinalizedFolders 리스트 사용 (그쪽 브랜치 코드는 그대로).
- ClinicalHomeView LaunchedEffect(Unit) 에서 자동 트리거 + 상단 배너 UI:
* 진행 중: "자동 업로드 진행 중 · 남은 세션 N · <name>" (파랑, spinner)
* 완료: "N uploaded, M failed" (초록/주황) + 확인 버튼
효과:
- 세션 종료 후 뒤로가기 → 자동 업로드 시도 → 실패해도 Clinical Home
재진입 시 재시도. 사용자 개입 0.
- 여러 세션 밀린 상황도 사용자가 다음 세션 진입할 때마다 이전 세션이
하나씩 재시도됨 (demo-final 구조 특성).
- 기존 폴더 구조 / measurement.json 포맷 100% 유지.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
병렬 진단 (BLE/thread + Compose lifecycle + null safety) 결과 HIGH
심각도 12건 중 crash 유발 가능성 높고 저비용 고효과인 것 우선 처리.
**H4 — coroutine leak**
- PiezoMonitoringView.kt BleDebugPanel: `while(true) { delay/refresh }` →
`while(isActive)`. LaunchedEffect 취소 후에도 refreshTick++ 이 계속
돌아 GC 방해.
- BladdyRiveView.kt 2곳: 동일 패턴 → isActive 로 통일.
**H1 — BleManager postDelayed 미취소**
- `fwFallbackTimer` (4초 mfv? fallback) + `cccdRetryTimer` (500ms CCCD
재시도) 를 Runnable 참조로 저장. disconnect / GATT_ERROR / watchdog
timeout 3곳에서 handler.removeCallbacks 로 명시적 취소.
- 이전에는 disconnect 후에도 4초 후 sendFirmwareVersionQuery() 가 발동
→ 이미 close 된 GATT 에 write → silent exception → state 오염.
**H2 — MeasurementService 콜백 leak**
- performNirsMeasurement/performPiezoMeasurement 는 singleton 에서
콜백 대입만 하고 정리 안 함. Self-clearing lambda 로 응답 1회 처리
후 자동 null. 시작 시 이전 stale 콜백도 clear.
- stop() 에서도 대기 중 콜백 취소.
**H3 — Watchdog race condition**
- watchdog thread 가 GATT disconnect/close + characteristic null 을
binder thread 에서 직접 실행 → UI thread 의 sendRaw 와 race.
- 정리 전부를 handler.post 로 UI thread 에 위임 → single-threaded.
- fwFallback/cccdRetry timer 도 여기서 함께 취소.
**H6 — PiezoMonitoringView measure() closure leak**
- DisposableEffect 에 piezoCollector.onMultiChannelComplete 정리 추가.
measure() 함수 안에서 이 콜백에 measureScope + channels + outer
state 를 다수 capture → 화면 이탈 후에도 GC 방해 + 재진입 시 stale
closure 가 새 상태 오염 위험.
**H9 — UrineCameraScreen NPE 위험**
- LaunchedEffect 안 while 루프에서 `currentDetection!!` 이 다른 recompose
가 detection 을 null 로 만들면 NPE. Local val snapshot 으로 fix.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
사용자앱 (feature/tab-navigation) 과 패키지 이름 일치. namespace + applicationId
둘 다 com.medithings.vesiscan 로 변경 (기존 데모 앱은 재설치 필요).
## 변경 범위
- Kotlin 148 파일: package + import 문 (714 occurrences)
- 디렉토리 이동: com/example/medilightv2android → com/medithings/vesiscan
(main, test, androidTest 각각)
- app/build.gradle.kts: namespace, applicationId
- docs/FLAVOR_DEMO_STABLE.md: 참조 갱신
- V41DetectorCH4Test: BvDispatchResult.methodChosen → method (dto field name fix)
## 주의
- applicationId 가 바뀌므로 기존 데모 앱 (com.example.medilightv2android.demo) 은
Android 관점에서 다른 앱으로 취급 — 재설치 시 PIN/설정 초기화됨.
- Fresh install 권장. 기존 앱 (com.example...) 은 별도로 uninstall 필요.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>