debug(nests): trace announce-flow chain to localise still-empty _announcedSpeakers

The replay=64 SharedFlow fix in d6517cf did NOT close the receiver's
"announced=0" symptom — the next two-phone repro (15:20:08 onward)
still shows the cliff detector ticking with `active=1 announced=0`
indefinitely, even after the session-internal pumpAnnounceWatch
clearly received an Active update. Either moq-rs sends Active to
only the FIRST announce bidi (so the VM-level second bidi never
gets it), OR the chain from that bidi to `_announcedSpeakers` is
broken at a layer my last fix didn't visit.

Adds focused logging at every step of the announce chain so the
next repro pins down which one. All NestRx-tagged:

`MoqLiteSession.announce` (per-bidi pump):
  - "session.announce(prefix='…') bidi opened, pump launching"
  - "session.announce(prefix='…') bidi pump emit #N status=… suffix='…' chunks=…"
  - "session.announce(prefix='…') bidi.incoming() ended naturally chunks=… emits=…"
  - per-emission so we can compare bidi#1 (session-internal) vs
    bidi#2 (VM-level) emit counts. If bidi#2 has 0 emits, the
    relay is sending Actives to only one bidi.

`MoqLiteNestsListener.announces`:
  - "MoqLiteNestsListener.announces flow STARTING (state=…)"
  - "MoqLiteNestsListener.announces opened bidi#2 (VM-level)"
  - per-emission "bidi#2 #N status=… suffix='…'"
  - "bidi#2 collect ENDED naturally" / "finally emissions=N"

`ReconnectingNestsListener.announces` (wrapper):
  - "wrapper.announces() flow starting collect on activeListener"
  - "wrapper.announces() iter=N activeListener=set/null"
  - "wrapper.announces() iter=N inner state=Connected/Closed/…"
  - "wrapper.announces() iter=N inner collect ended naturally/X fwd=N"

`NestViewModel.observeAnnounces`:
  - "observeAnnounces launching collect on l.announces()"
  - per-emission "observeAnnounces #N active=… pubkey='…' → ADD/REMOVE"
  - "observeAnnounces collect ENDED naturally/X (emissions=N,
    _announcedSpeakers.size=…)"

After the next repro, the receiver-side log will show one of:
- bidi#2 never opens (terminalOrConnected != Connected somehow)
- bidi#2 opens, no chunks/emits → relay-side issue
- bidi#2 emits, but `MoqLiteNestsListener.announces` doesn't
  forward → bug in the flow body
- forwarding works but observeAnnounces collect ends → bug in
  `ReconnectingNestsListener.announces` collectLatest behavior
- everything works but `_announcedSpeakers.size=0` → mutation lost

Tests: full suite still passes (CliffDetectorTest 12, NestPlayerTest
12, NestBroadcasterTest 6, MoqLiteSessionTest 11, …).
This commit is contained in:
Claude
2026-05-05 19:27:19 +00:00
parent d6517cf346
commit 1fc8dbceee
4 changed files with 62 additions and 11 deletions
@@ -820,15 +820,27 @@ class NestViewModel(
announcesJob?.cancel()
announcesJob =
viewModelScope.launch {
runCatching {
l.announces().collect { ann ->
if (closed) return@collect
if (ann.active) {
_announcedSpeakers.update { it + ann.pubkey }
} else {
_announcedSpeakers.update { it - ann.pubkey }
com.vitorpamplona.quartz.utils.Log
.d("NestRx") { "observeAnnounces launching collect on l.announces()" }
var emissionCount = 0
val outcome =
runCatching {
l.announces().collect { ann ->
if (closed) return@collect
emissionCount += 1
com.vitorpamplona.quartz.utils.Log.d("NestRx") {
"observeAnnounces #$emissionCount active=${ann.active} pubkey='${ann.pubkey.take(12)}' → ${if (ann.active) "ADD" else "REMOVE"}"
}
if (ann.active) {
_announcedSpeakers.update { it + ann.pubkey }
} else {
_announcedSpeakers.update { it - ann.pubkey }
}
}
}
com.vitorpamplona.quartz.utils.Log.w("NestRx") {
val why = outcome.exceptionOrNull()?.let { "${it::class.simpleName}: ${it.message}" } ?: "naturally"
"observeAnnounces collect ENDED $why (emissions=$emissionCount, _announcedSpeakers.size=${_announcedSpeakers.value.size})"
}
}
}