"(exit: , optional)" printed with nothing after the colon whenever no
Tailscale exit node was configured (the default). Now renders "none",
and relabels "exit:" to "exit node:" -- the abbreviated form read as an
exit *code* at a glance, which is what led to this being misdiagnosed
as a process-exit-code formatting bug in the first place.
Features:
- Introduce a Backend trait + System impl so flow/status/watch can be unit
tested against a fake; add 11 connect-state-machine tests.
- Captive-portal detection: status::connectivity returns Online/Portal/Offline;
surfaced in status, JSON, connect notes, and a dedicated watch state.
- `status --json` for bars/scripts; `profile add`/`profile remove`; detect now
scores by number of in-range markers.
Robustness:
- Pin LC_ALL=C/LANG=C on child processes for locale-independent parsing.
- Atomic config/state writes (temp + rename); 0600 config never world-readable.
- Transient PSK file written to $XDG_RUNTIME_DIR when available.
Fixes (from prior audit):
- Feed Wi-Fi PSK to nmcli via stdin/passwd-file, never argv.
- mask() no longer panics on multi-byte passwords.
- Connectivity check requires HTTP 204 (no captive-portal false positives).
- nmcli NAME,TYPE parsing handles escaped colons.
- Strip CIDR suffix from displayed IP; PKGBUILD/Cargo version aligned (2.1.0).