feat(ui): mesh header "Flash LoRa" button opens the in-app flash flow
Demo images / Build & push demo images (push) Failing after 2m9s

Replaces the external flasher.meshcore.co.uk link with a button that
opens the global device-setup modal directly at its flash step, via a
new manual entry point in the mesh store (flashFlowPath). Manual opens
target the connected radio (else the first detected stick), skip the
read-only probe — the port is held by the live session and a second tty
opener corrupts it; the backend flash job stops the listener itself —
and close the modal instead of stepping back to the detection screen.
Button is disabled with a hint when no radio is present.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
archipelago
2026-07-29 04:43:38 -04:00
co-authored by Claude Fable 5
parent c99f1c7b77
commit 6ba39041d0
4 changed files with 59 additions and 7 deletions
@@ -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
}
</script>