diff --git a/neode-ui/src/components/mesh/MeshDeviceSetupModal.vue b/neode-ui/src/components/mesh/MeshDeviceSetupModal.vue
index 4b4973bc..895e910a 100644
--- a/neode-ui/src/components/mesh/MeshDeviceSetupModal.vue
+++ b/neode-ui/src/components/mesh/MeshDeviceSetupModal.vue
@@ -340,7 +340,7 @@ function stopProbeProgress(done = false) {
if (done) probeProgress.value = 100
}
-const devicePath = computed(() => mesh.undismissedDetectedDevices[0] ?? '')
+const devicePath = computed(() => mesh.flashFlowPath ?? mesh.undismissedDetectedDevices[0] ?? '')
const show = computed(() => !!devicePath.value)
const imageFailed = ref(false)
const deviceImage = computed(() =>
@@ -402,6 +402,23 @@ watch([show, devicePath], async ([visible]) => {
stopFlashPoll()
return
}
+ if (mesh.flashFlowPath) {
+ // Manual "Flash LoRa" entry: jump straight to the flash step. Skip the
+ // read-only probe — the port is usually the live session's, and a second
+ // opener on the tty corrupts the running connection; status already
+ // knows the firmware kind for the connected radio.
+ probe.value = null
+ probeError.value = ''
+ probing.value = false
+ stopProbeProgress()
+ imageFailed.value = false
+ openFlashStep()
+ const t = (mesh.status?.device_type ?? '').toLowerCase()
+ if (t === 'meshcore' || t === 'meshtastic' || t === 'reticulum') {
+ flashFamily.value = t as FlashFirmwareFamily
+ }
+ return
+ }
step.value = 1
error.value = ''
imageFailed.value = false
@@ -428,6 +445,10 @@ watch([show, devicePath], async ([visible]) => {
}, { immediate: false })
function dismiss() {
+ if (mesh.flashFlowPath) {
+ mesh.closeFlashFlow()
+ return
+ }
if (devicePath.value) mesh.dismissDetectedDevice(devicePath.value)
}
@@ -598,6 +619,11 @@ async function cancelFlash() {
function closeFlashStep() {
stopFlashPoll()
+ if (mesh.flashFlowPath) {
+ // Manual entry has no detection step to go back to — close the modal.
+ mesh.closeFlashFlow()
+ return
+ }
step.value = 1
}
diff --git a/neode-ui/src/stores/mesh.ts b/neode-ui/src/stores/mesh.ts
index 7d9a815a..96788428 100644
--- a/neode-ui/src/stores/mesh.ts
+++ b/neode-ui/src/stores/mesh.ts
@@ -377,6 +377,16 @@ export const useMeshStore = defineStore('mesh', () => {
}
localStorage.setItem(DETECT_DISMISS_KEY, JSON.stringify(dismissedDetected.value))
}
+ // Manual "Flash LoRa" entry (Mesh header button): forces the global setup
+ // modal open at the flash step for this port — including the live session's
+ // port, which detection-driven opening deliberately excludes.
+ const flashFlowPath = ref