impl LesavkaClientApp {
    /*──────────────── paste loop ───────────────*/
    #[cfg(not(coverage))]
    fn paste_loop(
        ep: Channel,
        mut rx: mpsc::UnboundedReceiver<String>,
    ) -> tokio::task::JoinHandle<()> {
        tokio::spawn(async move {
            let mut cli = RelayClient::new(ep.clone());
            while let Some(text) = rx.recv().await {
                match paste::build_paste_request(&text) {
                    Ok(req) => match cli.paste_text(Request::new(req)).await {
                        Ok(resp) => {
                            let reply = resp.get_ref();
                            if !reply.ok {
                                warn!("📋 paste rejected: {}", reply.error);
                            } else {
                                debug!("📋 paste delivered");
                            }
                        }
                        Err(e) => {
                            warn!("📋 paste failed: {e}");
                            cli = RelayClient::new(ep.clone());
                        }
                    },
                    Err(e) => {
                        warn!("📋 paste build failed: {e}");
                    }
                }
            }
        })
    }

    /*──────────────── keyboard stream ───────────────*/
    #[cfg(not(coverage))]
    async fn stream_loop_keyboard(&self, ep: Channel) {
        loop {
            info!("⌨️🤙 Keyboard dial {}", self.server_addr);
            let mut cli = RelayClient::new(ep.clone());
            let capture_enabled = Arc::clone(&self.remote_capture_enabled);
            let mut remote_capture_was_enabled = capture_enabled.load(Ordering::Relaxed);

            let outbound =
                BroadcastStream::new(self.kbd_tx.subscribe()).filter_map(move |report| {
                    let remote_capture_enabled = capture_enabled.load(Ordering::Relaxed);
                    keyboard_stream_report(
                        report,
                        remote_capture_enabled,
                        &mut remote_capture_was_enabled,
                    )
                });

            match cli.stream_keyboard(Request::new(outbound)).await {
                Ok(mut resp) => {
                    while let Some(msg) = resp.get_mut().message().await.transpose() {
                        if let Err(e) = msg {
                            warn!("⌨️ server err: {e}");
                            break;
                        }
                    }
                }
                Err(e) => warn!("❌⌨️ connect failed: {e}"),
            }
            tokio::time::sleep(Duration::from_secs(1)).await; // retry
        }
    }

    /*──────────────── mouse stream ──────────────────*/
    #[cfg(not(coverage))]
    async fn stream_loop_mouse(&self, ep: Channel) {
        loop {
            info!("🖱️🤙 Mouse dial {}", self.server_addr);
            let mut cli = RelayClient::new(ep.clone());
            let capture_enabled = Arc::clone(&self.remote_capture_enabled);
            let mut remote_capture_was_enabled = capture_enabled.load(Ordering::Relaxed);

            let outbound =
                BroadcastStream::new(self.mou_tx.subscribe()).filter_map(move |report| {
                    let remote_capture_enabled = capture_enabled.load(Ordering::Relaxed);
                    mouse_stream_report(
                        report,
                        remote_capture_enabled,
                        &mut remote_capture_was_enabled,
                    )
                });

            match cli.stream_mouse(Request::new(outbound)).await {
                Ok(mut resp) => {
                    while let Some(msg) = resp.get_mut().message().await.transpose() {
                        if let Err(e) = msg {
                            warn!("🖱️ server err: {e}");
                            break;
                        }
                    }
                }
                Err(e) => warn!("❌🖱️ connect failed: {e}"),
            }
            tokio::time::sleep(Duration::from_secs(1)).await;
        }
    }

}
