* [client] Coalesce peer list change notifications to the mobile listener
Every peer state change spawned a goroutine to call the platform
listener. During a reconnect storm this pinned hundreds of OS threads
in JNI and let the UI call back into the engine from each of them.
Deliver peer list changes from a single goroutine per listener and
collapse pending changes into the latest count.
* [client] Cover a pending wake-up when the peer list deliverer is replaced
The replacement test waited for the old callback to finish before
swapping listeners, so it never exercised the stop check that runs
after a wake-up. Block the old callback, queue a peer list change and
swap while it is blocked, then assert the old listener never sees the
new count.
* [client] Signal peer list deliverer exit and wait for it in the test
The replacement test sampled the old listener after a fixed sleep, so a
late stale delivery could slip past it. Close a done channel when the
deliverer goroutine returns and let the test wait on it instead.
* [client] Drop the test-only peer list deliverer exit channel
The done channel was only read by the replacement test. Production code
cannot wait on it, since joining the deliverer would block on a mobile
callback. The tests now drive the deliverer loop directly and check that
setListener and removeListener close its stop channel.