mirror of
https://github.com/pe1hvh/meshcore-gui.git
synced 2026-03-28 17:42:38 +01:00
HotFixRoomServer
This commit is contained in:
57
CHANGELOG.md
57
CHANGELOG.md
@@ -1,26 +1,29 @@
|
||||
# CHANGELOG
|
||||
|
||||
<!-- CHANGED: Title changed from "CHANGELOG: Message & Metadata Persistence" to "CHANGELOG" —
|
||||
a root-level CHANGELOG.md should be project-wide, not feature-specific. -->
|
||||
|
||||
All notable changes to MeshCore GUI are documented in this file.
|
||||
Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Versioning](https://semver.org/).
|
||||
|
||||
---
|
||||
## [1.13.4] - 2026-03-12 — Room Server Login & Receive Reliability
|
||||
## [1.13.4] - 2026-03-12 — Room Server USB Login & Fetch Fix
|
||||
|
||||
### Changed
|
||||
- 🔄 `meshcore_gui/ble/commands.py` — Room login success now refreshes archived room history immediately after `LOGIN_SUCCESS`, so the room panel is populated deterministically right after a successful login
|
||||
- 🔄 `meshcore_gui/ble/events.py` — `CONTACT_MSG_RECV` with `txt_type == 2` is now always treated as a Room Server message, even when the `signature` field is absent; the author name falls back gracefully instead of routing the message through the normal DM path
|
||||
- 🔄 `meshcore_gui/ble/worker.py` — The global `LOGIN_SUCCESS` subscriber now also synchronizes room login state into `SharedData` and refreshes room history, so UI state no longer depends solely on the command-side waiter winning the event timing race
|
||||
- 🔄 `meshcore_gui/ble/commands.py` — After `LOGIN_SUCCESS`, the room login flow now starts a bounded background `get_msg()` sync loop so serial/USB sessions actively drain queued room messages instead of relying on a single defensive fetch
|
||||
- 🔄 `meshcore_gui/ble/events.py` — Room messages are now classified on `txt_type == 2` even when the `signature` field is absent; sender/room pubkeys also use broader payload fallbacks for room traffic
|
||||
- 🔄 `meshcore_gui/ble/worker.py` — Global `LOGIN_SUCCESS` handling now updates `room_login_states` and refreshes cached room history in `SharedData`
|
||||
- 🔄 `meshcore_gui/config.py` — Version bumped to `1.13.4`
|
||||
|
||||
### Fixed
|
||||
- 🛠 **Initial room login could remain pending or feel unreliable** — UI state now also updates from the subscribed `LOGIN_SUCCESS` event, not only from the command coroutine waiting for the same event
|
||||
- 🛠 **Room messages could be missed when `txt_type == 2` arrived without `signature`** — such packets are now still classified as room traffic and shown in the Room Server panel
|
||||
- 🛠 **Room history refresh after login was timing-sensitive** — history is now reloaded both from the command success path and from the subscribed login-success callback
|
||||
- 🛠 **USB/serial room login showed only app-sent messages** — After login, the app now keeps polling queued room messages for a short window so messages from other room participants are actually fetched
|
||||
- 🛠 **Incoming room messages without `signature` were misclassified** — `CONTACT_MSG_RECV` packets with `txt_type == 2` no longer fall back to DM handling just because the room server omitted `signature`
|
||||
- 🛠 **Room login UI state could depend on one code path** — Worker-side `LOGIN_SUCCESS` processing now reinforces the room state update even when the command-side wait path is not the only consumer
|
||||
|
||||
### Impact
|
||||
- More reliable first login behaviour for Room Server panels
|
||||
- Better chance that room history and newly arriving room messages show up immediately after login
|
||||
- No intended breaking changes outside the Room Server receive/login flow
|
||||
- Faster and more reliable room history retrieval on USB/serial setups
|
||||
- Room traffic from other users has a better chance of appearing in the Room Server panel immediately after login
|
||||
- No intended regression for DM or normal channel message handling
|
||||
|
||||
---
|
||||
## [1.13.3] - 2026-03-12 — Active Panel Timer Gating
|
||||
@@ -47,15 +50,34 @@ Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Ver
|
||||
## [1.13.2] - 2026-03-11 — Map Display Bugfix
|
||||
|
||||
### Fixed
|
||||
- 🛠 **MAP panel blank when contacts list is empty at startup** — dashboard update loop had two separate conditional map-update blocks that both silently stopped firing after tick 1 when `data['contacts']` was empty. Map panel received no further snapshots and remained blank indefinitely.
|
||||
- 🛠 **Leaflet map initialized on hidden (zero-size) container** — `processPending` in the browser runtime called `L.map()` on the host element while it was still `display:none` (Vue v-show, panel not yet visible). This produced a broken 0×0 map that never recovered because `ensureMap` returned the cached broken state on all subsequent calls. Fixed by adding a `clientWidth/clientHeight` guard in `ensureMap`: initialization is deferred until the host has real dimensions.
|
||||
- 🛠 **Route map container had no height** — `route_page.py` used the Tailwind class `h-96` for the Leaflet host `<div>`. NiceGUI/Quasar does not include Tailwind CSS, so `h-96` had no effect and the container rendered at height 0. Leaflet initialized on a zero-height element and produced a blank map.
|
||||
- 🛠 **Route map not rendered when no node has GPS coordinates** — `_render_map` returned early before creating the Leaflet container when `payload['nodes']` was empty. Fixed: container is always created; a notice label is shown instead.
|
||||
- 🛠 **MAP panel blank when contacts list is empty at startup** — dashboard update loop
|
||||
had two separate conditional map-update blocks that both silently stopped firing after
|
||||
tick 1 when `data['contacts']` was empty. Map panel received no further snapshots and
|
||||
remained blank indefinitely.
|
||||
- 🛠 **Leaflet map initialized on hidden (zero-size) container** — `processPending` in
|
||||
the browser runtime called `L.map()` on the host element while it was still
|
||||
`display:none` (Vue v-show, panel not yet visible). This produced a broken 0×0 map
|
||||
that never recovered because `ensureMap` returned the cached broken state on all
|
||||
subsequent calls. Fixed by adding a `clientWidth/clientHeight` guard in `ensureMap`:
|
||||
initialization is deferred until the host has real dimensions.
|
||||
- 🛠 **Route map container had no height** — `route_page.py` used the Tailwind class
|
||||
`h-96` for the Leaflet host `<div>`. NiceGUI/Quasar does not include Tailwind CSS,
|
||||
so `h-96` had no effect and the container rendered at height 0. Leaflet initialized
|
||||
on a zero-height element and produced a blank map.
|
||||
- 🛠 **Route map not rendered when no node has GPS coordinates** — `_render_map`
|
||||
returned early before creating the Leaflet container when `payload['nodes']` was
|
||||
empty. Fixed: container is always created; a notice label is shown instead.
|
||||
|
||||
### Changed
|
||||
- 🔄 `meshcore_gui/static/leaflet_map_panel.js` — Added size guard in `ensureMap`: returns `null` when host has `clientWidth === 0 && clientHeight === 0` and no map state exists yet. `processPending` retries on the next tick once the panel is visible.
|
||||
- 🔄 `meshcore_gui/gui/dashboard.py` — Consolidated two conditional map-update blocks into a single unconditional update while the MAP panel is active. Added `h-96` to the DOMCA CSS height overrides for consistency with the route page map container.
|
||||
- 🔄 `meshcore_gui/gui/route_page.py` — Replaced `h-96` Tailwind class on the route map host `<div>` with an explicit inline `style` (height: 24rem). Removed early `return` guard so the Leaflet container is always created.
|
||||
- 🔄 `meshcore_gui/static/leaflet_map_panel.js` — Added size guard in `ensureMap`:
|
||||
returns `null` when host has `clientWidth === 0 && clientHeight === 0` and no map
|
||||
state exists yet. `processPending` retries on the next tick once the panel is visible.
|
||||
- 🔄 `meshcore_gui/gui/dashboard.py` — Consolidated two conditional map-update blocks
|
||||
into a single unconditional update while the MAP panel is active. Added `h-96` to the
|
||||
DOMCA CSS height overrides for consistency with the route page map container.
|
||||
- 🔄 `meshcore_gui/gui/route_page.py` — Replaced `h-96` Tailwind class on the route
|
||||
map host `<div>` with an explicit inline `style` (height: 24rem). Removed early
|
||||
`return` guard so the Leaflet container is always created.
|
||||
|
||||
### Impact
|
||||
- MAP panel now renders reliably on first open regardless of contact/GPS availability
|
||||
@@ -63,6 +85,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/) and [Semantic Ver
|
||||
- No breaking changes outside the three files listed above
|
||||
|
||||
---
|
||||
>>>>>>> b76eacf1119026c49c25d2811a6d713da8f8e01b
|
||||
## [1.13.0] - 2026-03-09 — Leaflet Map Runtime Stabilization
|
||||
|
||||
### Added
|
||||
|
||||
@@ -35,6 +35,7 @@ class CommandHandler:
|
||||
self._mc = mc
|
||||
self._shared = shared
|
||||
self._cache = cache
|
||||
self._room_sync_tasks: Dict[str, asyncio.Task] = {}
|
||||
|
||||
# Handler registry — add new commands here (OCP)
|
||||
self._handlers: Dict[str, object] = {
|
||||
@@ -406,7 +407,6 @@ class CommandHandler:
|
||||
pubkey, 'ok',
|
||||
f"admin={is_admin}",
|
||||
)
|
||||
self._shared.load_room_history(pubkey)
|
||||
self._shared.set_status(
|
||||
f"✅ Room login OK: {room_name} — "
|
||||
f"history arriving over RF…"
|
||||
@@ -426,6 +426,8 @@ class CommandHandler:
|
||||
except Exception as exc:
|
||||
debug_print(f"login_room: defensive get_msg() error: {exc}")
|
||||
|
||||
self._start_room_sync(pubkey, room_name)
|
||||
|
||||
else:
|
||||
self._shared.set_room_login_state(
|
||||
pubkey, 'fail',
|
||||
@@ -552,6 +554,63 @@ class CommandHandler:
|
||||
)
|
||||
debug_print(f"send_room_msg exception: {exc}")
|
||||
|
||||
def _cancel_room_sync(self, pubkey: str) -> None:
|
||||
"""Cancel an active background room-history sync task."""
|
||||
task = self._room_sync_tasks.pop(pubkey, None)
|
||||
if task and not task.done():
|
||||
task.cancel()
|
||||
|
||||
def _start_room_sync(self, pubkey: str, room_name: str) -> None:
|
||||
"""Start a bounded background fetch loop for room history."""
|
||||
self._cancel_room_sync(pubkey)
|
||||
self._room_sync_tasks[pubkey] = asyncio.create_task(
|
||||
self._sync_room_history(pubkey, room_name)
|
||||
)
|
||||
|
||||
async def _sync_room_history(self, pubkey: str, room_name: str) -> None:
|
||||
"""Fetch queued room messages for a short period after login.
|
||||
|
||||
On some serial/USB setups the SDK's auto-message fetching is
|
||||
not sufficient to drain the room backlog promptly after
|
||||
``LOGIN_SUCCESS``. This bounded loop polls ``get_msg()`` for a
|
||||
short window so historical room messages from other users are
|
||||
actually pulled into the app.
|
||||
"""
|
||||
idle_errors = 0
|
||||
try:
|
||||
for attempt in range(24):
|
||||
try:
|
||||
result = await self._mc.commands.get_msg()
|
||||
result_type = getattr(result, 'type', None)
|
||||
if result_type == EventType.ERROR:
|
||||
idle_errors += 1
|
||||
debug_print(
|
||||
f"room_sync: get_msg ERROR for {room_name} "
|
||||
f"(attempt {attempt + 1}/24, idle={idle_errors})"
|
||||
)
|
||||
else:
|
||||
idle_errors = 0
|
||||
debug_print(
|
||||
f"room_sync: get_msg fetched data for {room_name} "
|
||||
f"(attempt {attempt + 1}/24)"
|
||||
)
|
||||
except Exception as exc:
|
||||
idle_errors += 1
|
||||
debug_print(
|
||||
f"room_sync: get_msg exception for {room_name}: {exc}"
|
||||
)
|
||||
|
||||
if idle_errors >= 4:
|
||||
break
|
||||
|
||||
await asyncio.sleep(2.0)
|
||||
except asyncio.CancelledError:
|
||||
debug_print(f"room_sync: cancelled for {room_name}")
|
||||
raise
|
||||
finally:
|
||||
self._shared.load_room_history(pubkey)
|
||||
self._room_sync_tasks.pop(pubkey, None)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Callback for refresh (set by SerialWorker after construction)
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
@@ -320,17 +320,26 @@ class EventHandler:
|
||||
|
||||
# --- Room Server message (txt_type 2) ---
|
||||
if txt_type == 2:
|
||||
# Prefer the embedded author signature when available.
|
||||
# Some room-history / server-side messages arrive without a
|
||||
# signature; those still belong to the room and must not fall
|
||||
# through to the regular DM path.
|
||||
room_pubkey = (
|
||||
payload.get('room_pubkey')
|
||||
or payload.get('receiver_pubkey')
|
||||
or payload.get('recipient_pubkey')
|
||||
or payload.get('pubkey')
|
||||
or pubkey
|
||||
)
|
||||
author_prefix = (
|
||||
signature
|
||||
or payload.get('sender_pubkey_prefix', '')
|
||||
or payload.get('sender_pubkey', '')
|
||||
or payload.get('sender_prefix', '')
|
||||
)
|
||||
author = ''
|
||||
if signature:
|
||||
author = self._shared.get_contact_name_by_prefix(signature)
|
||||
if not author:
|
||||
author = signature[:8]
|
||||
if author_prefix:
|
||||
author = self._shared.get_contact_name_by_prefix(author_prefix)
|
||||
if not author:
|
||||
author = pubkey[:8] if pubkey else '?'
|
||||
author = payload.get('sender_name', '') or payload.get('name', '')
|
||||
if not author:
|
||||
author = author_prefix[:8] if author_prefix else room_pubkey[:8] if room_pubkey else '?'
|
||||
|
||||
self._shared.add_message(Message.incoming(
|
||||
author,
|
||||
@@ -338,14 +347,14 @@ class EventHandler:
|
||||
None,
|
||||
snr=self._extract_snr(payload),
|
||||
path_len=path_len,
|
||||
sender_pubkey=pubkey,
|
||||
sender_pubkey=room_pubkey,
|
||||
path_hashes=path_hashes,
|
||||
path_names=path_names,
|
||||
message_hash=msg_hash,
|
||||
))
|
||||
debug_print(
|
||||
f"Room msg from {author} (sig={signature or '-'}) "
|
||||
f"via room {pubkey[:12]}: "
|
||||
f"Room msg from {author} (sig={signature}) "
|
||||
f"via room {room_pubkey[:12]}: "
|
||||
f"{payload.get('text', '')[:30]}"
|
||||
)
|
||||
return
|
||||
|
||||
@@ -258,21 +258,12 @@ class _BaseWorker(abc.ABC):
|
||||
# ── LOGIN_SUCCESS handler (Room Server) ───────────────────────
|
||||
|
||||
def _on_login_success(self, event) -> None:
|
||||
"""Synchronise Room Server login success into SharedData.
|
||||
|
||||
This callback is intentionally independent from the command-side
|
||||
``wait_for_event(LOGIN_SUCCESS)`` path. If the library delivers the
|
||||
event to subscribers before or instead of the waiter, the UI must
|
||||
still transition to the logged-in state and refresh room history.
|
||||
"""
|
||||
payload = event.payload or {}
|
||||
pubkey = payload.get("pubkey_prefix", "")
|
||||
is_admin = payload.get("is_admin", False)
|
||||
detail = f"admin={is_admin}"
|
||||
|
||||
debug_print(f"LOGIN_SUCCESS received: pubkey={pubkey}, admin={is_admin}")
|
||||
self.shared.set_room_login_state(pubkey, 'ok', detail)
|
||||
if pubkey:
|
||||
self.shared.set_room_login_state(pubkey, 'ok', f'admin={is_admin}')
|
||||
self.shared.load_room_history(pubkey)
|
||||
self.shared.set_status("✅ Room login OK — messages arriving over RF…")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user