RemoteQueryPool.cancel() terminated the SSH process and then waited up
to 250 ms for it, on the curses thread. Ctrl+R re-enqueues every host,
so a refresh of six hosts could freeze the popup for over a second.
Only send SIGTERM when cancelling; reap the process on later polls,
escalating to SIGKILL after 250 ms and closing its pipes once it has
exited. close() still waits, since the popup is exiting anyway.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The update popup kept the last 64 KiB of raw bytes and decoded the
whole buffer on every refresh. Cutting the buffer, or reading a chunk
that ended inside a multi-byte sequence, made the UTF-8 decode fail and
sent everything through the GB18030 fallback, garbling the display.
Clip the retained tail at a UTF-8 boundary and decode still-growing
streams with an incremental decoder that holds back an incomplete
trailing sequence instead of treating it as corruption.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>