fix: transport key HTTP failure, MQTT tab polling, WS packet recovery, status popout position#28
Merged
Merged
Conversation
- EditKeyModal: derive transport key with @noble/hashes/sha2.js instead of crypto.subtle.digest — crypto.subtle is restricted to secure contexts (HTTPS or localhost) so it silently fails when the app is served over plain HTTP directly from the repeater - Configuration: change all 12 tab content divs from v-show to v-if so that per-tab lifecycle hooks (e.g. MQTT polling in LetsMeshSettings) only run while the tab is visible, not for the entire time Configuration is open Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- On WS close (reconnect path only): record disconnect timestamp via noteDisconnect() so the recovery window is anchored to the actual drop time, not an arbitrary lookback from now - On reconnect: fetch missed packets via /filtered_packets with start_timestamp=max(disconnectTime, now-600) and limit=1000 — whichever cap is hit first — then merge into the store by dedup on packet_hash rather than replacing wholesale - System Status popout: top-14 (56px) clipped into the topbar; changed to top-16 sm:top-24 to clear the bar at both mobile and desktop padding Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ting - Configuration.vue: fix ?? operator bug in requestCurrentTabLeave — requestLeave() returns void (undefined), so ?? callback() was always firing immediately, switching the tab before the modal could block it; with v-if tabs this caused the component to unmount and the modal to vanish entirely - useUnsavedChanges: add handleCancel (close modal, abort nav) and pendingCancelFn so the router-leave path calls next(false) on cancel rather than leaving the navigation permanently pending - UnsavedChangesModal: add Cancel emit and Keep Editing button alongside existing Discard and Save Settings - All 7 config tab components: wire @cancel="handleCancel" Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug Fixes
Transport key derivation fails when served over HTTP — Bug
The Edit Key modal derives the region transport key client-side using SHA-256. The original implementation used
crypto.subtle.digest, which is a Web Crypto API restricted to secure contexts only (HTTPS or localhost). It works correctly in the dev server (localhost) and silently fails — returning no key — when the compiled UI is served over plain HTTP directly from the repeater.Fixed by replacing
crypto.subtlewith@noble/hashes/sha2.js, a pure-JS synchronous implementation with no secure-context restriction.MQTT polling active on all config tabs — Bug
The Observer (LetsMesh) settings component polls MQTT broker status every 5 seconds while mounted. All 12 Configuration tabs were rendered with
v-show, which keeps every tab mounted in the DOM regardless of which is active. This meant MQTT was being polled continuously whenever the Configuration page was open, even when the user was on an unrelated tab.Changed all 12 tab content divs from
v-showtov-if. Tabs now only mount when active, so their polling and lifecycle hooks only run when visible. As a side effect, the unsaved-changes guard now also correctly limits itself to the active tab's component.Packet view freezes after WebSocket reconnect — Regression introduced on this branch
On
main, PacketTable had auseManagedPollingfallback that hit/api/recent_packetsevery 10 seconds when WebSocket was disconnected. During the DataService centralisation work on this branch, that fallback was removed in favour of a singleensure('recentPackets')call on mount. Theensurecall is subject to a 30-second TTL — meaning if the WS dropped after the TTL window, nothing would re-fetch until the page was reloaded.Fixed with a targeted recovery on reconnect:
/api/filtered_packetswithstart_timestampanchored to the disconnect time, capped at 10 minutes lookback or 1,000 packets — whichever is reached firstpacket_hash, so any packets that arrived via WS before the close aren't lostOn the recovery limits: The packet view is an operational dashboard, not an analytical tool. Its job is to show what is happening now and what happened recently — not to reconstruct history. If a user needs to go back further than 10 minutes they should be using purpose-built analysis tooling. A 10-minute window covers any realistic reconnect scenario (marginal link drop, brief power interruption, app backgrounded) without risking a large unbounded fetch on a resource-constrained device. The 1,000-packet cap provides a hard memory ceiling consistent with the existing store limit, ensuring recovery on a busy mesh doesn't blow out the browser on hardware that may be running the UI locally on the repeater itself. Both limits were chosen deliberately over a purely count-based approach (which
mainused) because time-bounded recovery is more meaningful operationally — you care about what you missed, not an arbitrary number of records. It's also what the limit was in main.System Status popout overlaps the TopBar — Minor regression
The popout was positioned
fixed top-14(56px from the top of the viewport). The TopBar itself is approximately 59px tall on mobile (p-3padding) and 83px on desktop (sm:p-6), so the panel opened partially behind the icon row it was anchored to.Fixed with
top-16 sm:top-24, derived directly from the TopBar's own padding and button-height classes.Unsaved changes modal not showing on tab switch, and missing workflow option — Bug + UX gap
The nav bug. Switching Configuration tabs while editing triggered
requestCurrentTabLeave, which called the active tab component'srequestLeave(callback)and then used??to fall back tocallback()if the result was nullish.requestLeavereturnsvoid— which isundefined. The??operator sawundefinedand always firedcallback()on the right-hand side, switching the tab immediately regardless of whether the modal had been shown. Withv-showtabs (old behaviour), the component stayed mounted after the switch so the modal appeared on the wrong tab — an awkward experience the user had already noticed. With thev-ifchange made earlier in this branch, the component unmounted the moment the tab switched, and the modal disappeared entirely.Fixed by replacing the
??pattern with an explicitif/else: if the tab ref exists, callrequestLeaveand let it own the flow; otherwise fall back to the callback directly.The missing workflow option. The
UnsavedChangesModalpreviously offered only two paths: Discard Changes (throw away edits and proceed) and Save Settings (save and proceed). There was no way to close the modal and return to editing — if the user accidentally clicked a tab or triggered a navigation they didn't intend, their only options were to lose their work or save prematurely. This gap also meant the Vue RouteronBeforeRouteLeaveguard had no cancel path:next()was called on save or discard, but if the user somehow closed the modal without choosing,nextwas never called and the navigation would hang permanently.Fixed by adding a Keep Editing button that closes the modal and aborts the pending navigation. For the router-leave path, this calls
next(false)explicitly. For the tab-switch path, it simply nulls the pending callback. The modal now presents three clear options: keep editing, discard and leave, or save and leave.