Two independent root causes were producing auth failures on the upstream
SOCKS5 dial path:
1. `url::Url::username()` / `Url::password()` return percent-encoded
strings per the WHATWG URL spec, but the producer side already
percent-encodes the credentials when assembling the upstream URL —
so the upstream was receiving `%40` instead of `@` and authentication
silently failed for any credential containing `@ : / + = % ! space`
or non-ASCII characters. Centralize the decode in a new
`upstream_userpass` helper and route all four upstream-dial sites
through it (HTTP CONNECT → SOCKS5, HTTP CONNECT → HTTP Basic-Auth,
local SOCKS5 → HTTP Basic-Auth, local SOCKS5 → SOCKS5). The
Shadowsocks path already decoded manually and is unchanged.
2. async_socks5 0.6 issues a `write_u8` for every single-byte field of
the SOCKS5 method-selection and RFC1929 sub-negotiation. On a raw
`TcpStream` each call becomes its own TCP segment, and some upstream
SOCKS5 implementations treat this fragmented submission as a
misbehaving client and silently FIN instead of returning a status —
curl with the same credentials succeeds because it buffers each
sub-message into a single send(). Wrap the upstream socket in
`tokio::io::BufStream` (the usage pattern the async_socks5 README
shows) and enable TCP_NODELAY so flushes leave unsegmented.
Includes unit tests covering percent-decode for ASCII / special-char /
non-ASCII / no-credentials / username-only inputs, plus a trace-level
SOCKS5 handshake byte logger that can be enabled with
RUST_LOG=donutbrowser_lib::proxy_server=trace for future debugging.
Intercept the main window CloseRequested event so the user can choose
between minimizing the app to the system tray and quitting, instead of
the close button immediately tearing the process down.
- Add an on_window_event handler that prevents close, emits
close-confirm-requested, and lets the next CloseRequested through
once confirm_quit flips a QUIT_CONFIRMED flag.
- Add a TrayIconBuilder in the main process with Show / Quit menu items
and a left-click handler that restores the window. Tray icon is
decoded via the image crate so the donut glyph renders on every
platform.
- Add hide_to_tray command used by the dialog's Minimize action.
- New CloseConfirmDialog React component mounted in app/page.tsx.
- Enable Tauri features tray-icon and image-png.
- Add closeConfirm strings across all eight locale files.
The existing standalone donut-daemon tray binary is left untouched.