Merge main into archy-hwconfig — reconcile probe/dedup/name work

Both sides independently fixed the serial-alias dedup and the ESP32
boot-reset races; kept the branch's defer-to-auto-detect for unpinned
preferred paths (single probe pass per cycle) on top of main's
advert-name threading, Reticulum name propagation and radio-first
routing. Modal keeps main's 'Set Recommended' naming + probe progress
bar alongside the branch's in-app firmware flasher step.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-28 19:03:19 -04:00
co-authored by Claude Fable 5
173 changed files with 10967 additions and 1925 deletions
+19 -6
View File
@@ -572,6 +572,8 @@ fn likely_non_mesh_serial_device(path: &str) -> bool {
/// was open (matches the reported "stops when I leave, resumes when I come
/// back"). SERIAL_CANDIDATES lists `/dev/mesh-radio` first, so it wins the
/// dedup and is what's reported when both alias and target are present.
/// (Independently re-discovered and fixed on main 2026-07-26 — both sides
/// of the 2026-07-28 merge carried an equivalent implementation.)
pub async fn detect_serial_devices() -> Vec<String> {
let mut devices = Vec::new();
let mut seen_real_paths = std::collections::HashSet::new();
@@ -618,12 +620,23 @@ pub async fn detect_serial_devices_info() -> Vec<DetectedDeviceInfo> {
let mut out = Vec::new();
for path in detect_serial_devices().await {
let usb = usb_info_for_tty(&path).await;
let plugged_at = tokio::fs::metadata(&path)
.await
.ok()
.and_then(|m| m.modified().ok())
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs());
// Birth time (btime), falling back to inode-change time (ctime) —
// NOT mtime: a tty node's mtime bumps on every open()/write, so with
// mtime here each probe/session open minted a "new" plugged_at, the
// UI's (path, plugged_at) dismissal key never matched again, and the
// setup modal re-fired forever on a device that never left the port
// (observed live on archi-dev-box 2026-07-28). btime/ctime only
// change when udev (re)creates/chowns the node — i.e. on real plugs.
let plugged_at = tokio::fs::metadata(&path).await.ok().and_then(|m| {
m.created()
.ok()
.and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
.map(|d| d.as_secs())
.or_else(|| {
use std::os::unix::fs::MetadataExt;
u64::try_from(m.ctime()).ok()
})
});
out.push(DetectedDeviceInfo {
path,
vid: usb.0,