Four builders repeated the same batch-mode SSH option list and the same
POSIX "~/.local/bin/herdr or PATH" fallback script, and the delete/stop
helpers were near-identical copies. Build every remote command through
ssh_command()/posix_herdr_script() and run Session mutations through one
helper. Generated argv is byte-for-byte unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
The Ctrl+A h command menu gains two host-scoped actions: a plain
`herdr update` that streams updater output into the popup, and an
experimental update-and-hot-restart flow that verifies a live server
handoff and reconnects the originating Hub tab once the popup closes.
Remote updates use the remote host's own proxy settings. A standalone
helper reads literal proxy assignments from shell profiles and running
Clash/Mihomo listeners without executing startup files; Windows reads
process, user and machine proxy variables through PowerShell.