diff --git a/neode-ui/src/views/Mesh.vue b/neode-ui/src/views/Mesh.vue index 4523e49b..cd30e753 100644 --- a/neode-ui/src/views/Mesh.vue +++ b/neode-ui/src/views/Mesh.vue @@ -38,6 +38,13 @@ const sendError = ref('') const broadcasting = ref(false) const configuring = ref(false) const connectingDevice = ref(null) +// Onboarding modal (#6): guides a first-time connect for a freshly-detected, +// not-yet-connected device — a friendlier wrapper around the same Connect +// action the "Detected USB devices" list already offers, not a new setup +// engine. `onboardingDismissed` remembers paths the user closed without +// connecting, so it doesn't reappear every poll tick for the same device. +const showOnboardingModal = ref(false) +const onboardingDismissed = ref>(new Set()) const chatScrollEl = ref(null) const mobileShowChat = ref(false) // Device status panel starts collapsed on mobile (expandable via its header). @@ -1014,11 +1021,34 @@ async function handleConnectDevice(devicePath: string) { connectingDevice.value = devicePath try { await mesh.configure({ enabled: true, device_path: devicePath } as Partial) + showOnboardingModal.value = false } finally { connectingDevice.value = null } } +const undismissedDetectedDevices = computed(() => + (mesh.status?.detected_devices ?? []).filter((d) => !onboardingDismissed.value.has(d)) +) + +function dismissOnboarding() { + for (const d of undismissedDetectedDevices.value) onboardingDismissed.value.add(d) + showOnboardingModal.value = false +} + +// Pop the onboarding modal the moment a device is detected but not yet +// connected — same trigger condition the inline "Detected USB devices" list +// already uses (mesh.status.detected_devices non-empty + not connected), +// just surfaced as a guided prompt instead of requiring the user to notice +// the collapsed Device card. +watch( + () => [mesh.status?.device_connected, undismissedDetectedDevices.value.length] as const, + ([connected, count]) => { + if (!connected && count > 0) showOnboardingModal.value = true + }, + { immediate: true }, +) + function signalBars(rssi: number | null): number { if (rssi === null) return 0 if (rssi > -60) return 4 @@ -2308,6 +2338,32 @@ function isImageMime(mime?: string): boolean { + +
+
+

📡 Mesh Device Found

+

+ A radio was detected but isn't connected yet. Connect it to start using off-grid mesh chat. +

+
+ +
+ +
+
+