338ae7a19d
연결 → 연결 해제 → 메인 → [기기 연결] → [저장된 기기] 를 누르면 스피너가 영원히 돌고,
다른 기기 행까지 전부 비활성이 됐다(enabled = connectingDeviceAddress == null).
## 원인
화면이 스피너를 끄는 신호는 **둘뿐**이다 — `isServiceReady` 또는 `connectionError`.
그런데 10초 타임아웃이 보던 것은 `isConnected` 였다:
if (!isConnected.value) { connectionError.value = "Connection timed out..." }
링크는 붙었는데(STATE_CONNECTED) 서비스 탐색·CCCD 구독이 끝나지 않으면 **타임아웃이
면제된다.** 에러도 성공도 안 오니 화면이 영원히 잠긴다.
그 상태가 실제로 생긴다. 2026-09-10 12:29 로그에서 연결 직후 **15초 동안** 모든 명령이
`CMDQ drop ... timeout` 이었다(RX 0). 그때는 풀렸지만 안 풀리면 그대로다 — 연결 해제 뒤
재연결에서 특히 잘 난다.
## 고친 것 넷
**① 타임아웃이 보는 신호** `!isConnected` → `!isServiceReady`. 화면이 기다리는 것과 같아야
한다. 같이 10초 → **20초**로 올렸다 — 위 실측이 15초라 10초로 자르면 정상 연결을 끊는다.
**② 타임아웃 후 정리** 종전에는 에러만 띄우고 GATT 를 살려 뒀다. 다시 누르면 같은 반쪽
연결을 물고 간다. disconnect + close + 상태 리셋까지 한다.
**③ 화면 쪽 보험** DeviceScanView 에 25초 상한. BleManager 가 신호를 못 주는 경로가 하나라도
남으면 이 화면은 영원히 잠기므로, 원인을 고쳤어도 상한은 둔다. BleManager 의 20초보다
**길게** 잡았다 — 짧으면 정상 연결 중인 시도를 다시 눌러 GATT 가 두 번 열린다.
**④ 재연결 경로에도 같은 구멍** 재연결의 connectGatt 에는 상한이 **아예 없었다.** 그리고
watchdog 은 `isServiceReady` 뒤에만 시작하고, 8초 스캔 타임아웃은 `isConnected` 를 보고
"붙었다"며 재시도를 멈춘다. 결과는 **아무도 보지 않는 반쪽 연결** — 앱은 연결됐다고
표시하는데 명령이 전부 timeout 난다. 같은 20초 상한을 걸고 실패하면 재연결을 잇는다.
문구는 en/ko 둘 다 추가했다.
테스트 143개 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>