fix(mesh): setup modal re-fires on EVERY plug — dismissals key on plug time

detected_device_info now carries plugged_at (the /dev node's creation
time; udev recreates it per plug). Dismissals store (path -> plugged_at),
so swapping sticks or a fast unplug/replug between status polls can no
longer be suppressed by an old 'Not Now' (v1 path-only dismissals are
abandoned wholesale).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-22 19:08:18 -04:00
co-authored by Claude Fable 5
parent 197e351880
commit cad68eb941
2 changed files with 42 additions and 14 deletions
+13
View File
@@ -572,6 +572,12 @@ pub struct DetectedDeviceInfo {
pub pid: Option<String>,
pub product: Option<String>,
pub manufacturer: Option<String>,
/// Unix epoch seconds of the /dev node's creation — udev recreates the
/// node on every plug, so this changes on each replug. The UI keys its
/// "Not Now" dismissals on (path, plugged_at): swapping a stick (or
/// unplug/replug faster than a status poll) invalidates old dismissals
/// and the setup modal fires again, per the hot-swap UX (2026-07-22).
pub plugged_at: Option<u64>,
}
/// Like `detect_serial_devices`, but with USB metadata per port.
@@ -579,12 +585,19 @@ 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());
out.push(DetectedDeviceInfo {
path,
vid: usb.0,
pid: usb.1,
product: usb.2,
manufacturer: usb.3,
plugged_at,
});
}
out