From 98e01f4c5b389011df4c1281916d4d420d0b9a40 Mon Sep 17 00:00:00 2001 From: James Smith Date: Wed, 13 May 2026 20:59:33 +0100 Subject: [PATCH] fix(meshcore): surface backend error messages and extend polling window - Store last status message on MeshcoreClient so error details survive beyond the SSE event (which isn't active during connecting state) - Status endpoint now returns message field so the frontend can show the real reason (e.g. 'Connection failed after retries: ...') - Extend JS polling from 30s to 90s to outlast the backend's 65s retry sequence (5+15+45s delays) before declaring timeout Co-Authored-By: Claude Sonnet 4.6 --- routes/meshcore.py | 6 +++++- static/js/modes/meshcore.js | 3 ++- utils/meshcore.py | 8 +++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/routes/meshcore.py b/routes/meshcore.py index 57f6fca..a596f25 100644 --- a/routes/meshcore.py +++ b/routes/meshcore.py @@ -48,7 +48,11 @@ def status(): } ) c = _client() - return jsonify({"available": True, "state": c.get_state().value}) + state, message = c.get_state() + payload = {"available": True, "state": state.value} + if message: + payload["message"] = message + return jsonify(payload) @meshcore_bp.route("/connect", methods=["POST"]) diff --git a/static/js/modes/meshcore.js b/static/js/modes/meshcore.js index 769978d..3551437 100644 --- a/static/js/modes/meshcore.js +++ b/static/js/modes/meshcore.js @@ -97,7 +97,8 @@ const MeshCore = (function () { function _pollUntilConnected(attempts) { if (_connected) return; - if (attempts > 15) { + if (attempts > 45) { + // Backend retry window (5+15+45s) has elapsed — give up _updateStatusUI('error', 'Connection timed out'); return; } diff --git a/utils/meshcore.py b/utils/meshcore.py index 7eb46aa..857bb3f 100644 --- a/utils/meshcore.py +++ b/utils/meshcore.py @@ -250,6 +250,7 @@ class MeshcoreClient: def __init__(self) -> None: self._state = ConnectionState.DISCONNECTED + self._status_message: str | None = None self._config: ConnectionConfig | None = None self._event_queue: queue.Queue = queue.Queue(maxsize=500) self._nodes: dict[str, MeshcoreNode] = {} @@ -261,14 +262,15 @@ class MeshcoreClient: # -- State -- - def get_state(self) -> ConnectionState: - """Return the current connection state.""" + def get_state(self) -> tuple[ConnectionState, str | None]: + """Return the current connection state and last status message.""" with self._lock: - return self._state + return self._state, self._status_message def _set_state(self, state: ConnectionState, **extra) -> None: with self._lock: self._state = state + self._status_message = extra.get("message") # Push the status event OUTSIDE the lock (avoids deadlock; _push is queue-based) payload: dict = {"state": state.value} payload.update(extra)