Fix some frontend display/quality/doc issues

This commit is contained in:
Jack Kingsman
2026-04-10 15:43:08 -07:00
parent 8cc542ce23
commit 442c2fad20
20 changed files with 39 additions and 39 deletions

View File

@@ -3,7 +3,7 @@
* Feature: Add Arch AUR package
* Feature: 72hr packet density view in statistics
* Feature: Add warnings for event loop selection for MQTT on Windows startup
* Bufix: Bump Apprise to 1.9.9 to fix Matrix bug
* Bugfix: Bump Apprise to 1.9.9 to fix Matrix bug
* Misc: More memory-conscious on recent contact fetch
* Misc: Fix statistics pane e2e test
@@ -145,7 +145,7 @@
* Bugfix: Fix Apprise duplicate names
* Bugfix: Be better about identity resolution in the stats pane
* Misc: Docs, test, and performance enhancements
* Misc: Don't prompt "Are you sure" when leaving an unedited interation
* Misc: Don't prompt "Are you sure" when leaving an unedited integration
* Misc: Log node time on startup
* Misc: Improve community MQTT error bubble-up
* Misc: Unread DMs always have a red unread counter
@@ -172,7 +172,7 @@
## [3.3.0] - 2026-03-13
* Feature: Use dashed lines to show collapsed ambiguous router results
* Feature: Jump to unred
* Feature: Jump to unread
* Feature: Local channel management to prevent need to reload channel every time
* Feature: Debug endpoint
* Feature: Force-singleton channel management
@@ -235,7 +235,7 @@
* Feature: Massive codebase refactor and overhaul
* Bugfix: Fix packet parsing for trace packets
* Bugfix: Refetch channels on reconnect
* Bugfix: Load All on repeater pane on mobile doesn't etend into lower text
* Bugfix: Load All on repeater pane on mobile doesn't extend into lower text
* Bugfix: Timestamps in logs
* Bugfix: Correct wrong clock sync command
* Misc: Improve bot error bubble up
@@ -252,10 +252,6 @@
* Bugfix: Don't obscure new integration dropdown on session boundary
## [2.7.8] - 2026-03-08
## [2.7.8] - 2026-03-08
* Bugfix: Improve frontend asset resolution and fixup the build/push script

View File

@@ -23,7 +23,7 @@ For advanced setup and troubleshooting see [README_ADVANCED.md](README_ADVANCED.
## Requirements
- Python 3.10+
- Python 3.11+
- Node.js LTS or current (20, 22, 24, 25) if you're not using a prebuilt release
- [UV](https://astral.sh/uv) package manager: `curl -LsSf https://astral.sh/uv/install.sh | sh`
- MeshCore radio connected via USB serial, TCP, or BLE
@@ -135,6 +135,8 @@ sudo docker compose pull
sudo docker compose up -d
```
> If you switched to a local build (`build: .` instead of `image:`), use `sudo docker compose up -d --build` instead — `pull` only fetches remote images.
The example file and setup script default to the published Docker Hub image. To build locally from your checkout instead, replace:
```yaml

View File

@@ -2,10 +2,10 @@
import json
import logging
from typing import Any, Literal
from typing import Any, Literal, NotRequired
from pydantic import TypeAdapter
from typing_extensions import NotRequired, TypedDict
from typing_extensions import TypedDict
from app.models import Channel, Contact, Message, MessagePath, RawPacketBroadcast
from app.routers.health import HealthResponse

View File

@@ -164,7 +164,7 @@ class BotModule(FanoutModule):
),
timeout=BOT_EXECUTION_TIMEOUT,
)
except asyncio.TimeoutError:
except TimeoutError:
logger.warning("Bot '%s' execution timed out", self.name)
return
except Exception:

View File

@@ -538,7 +538,7 @@ class CommunityMqttPublisher(BaseMqttPublisher):
self._version_event.clear()
try:
await asyncio.wait_for(self._version_event.wait(), timeout=30)
except asyncio.TimeoutError:
except TimeoutError:
pass
return False
return True

View File

@@ -225,7 +225,7 @@ class FanoutManager:
handler = getattr(module, handler_name)
await asyncio.wait_for(handler(data), timeout=_DISPATCH_TIMEOUT_SECONDS)
self._clear_module_error(config_id)
except asyncio.TimeoutError:
except TimeoutError:
timeout_error = f"{handler_name} timed out after {_DISPATCH_TIMEOUT_SECONDS:.1f}s"
self._set_module_error(config_id, timeout_error)
logger.error(

View File

@@ -196,7 +196,7 @@ class BaseMqttPublisher(ABC):
self._version_event.wait(),
timeout=self._not_configured_timeout,
)
except asyncio.TimeoutError:
except TimeoutError:
continue
except asyncio.CancelledError:
return
@@ -231,7 +231,7 @@ class BaseMqttPublisher(ABC):
self._version_event.clear()
try:
await asyncio.wait_for(self._version_event.wait(), timeout=60)
except asyncio.TimeoutError:
except TimeoutError:
elapsed = time.monotonic() - connect_time
await self._on_periodic_wake(elapsed)
if self._should_break_wait(elapsed):

View File

@@ -24,7 +24,7 @@ logger = logging.getLogger(__name__)
NO_EVENT_RECEIVED_GUIDANCE = (
"Radio command channel is unresponsive (no_event_received). Ensure that your firmware is not "
"incompatible, outdated, or wrong-mode (e.g. repeater, not client), and that"
"incompatible, outdated, or wrong-mode (e.g. repeater, not client), and that "
"serial/TCP/BLE connectivity is successful (try another app and see if that one works?). The app cannot proceed because it cannot "
"issue commands to the radio."
)

View File

@@ -118,7 +118,7 @@ async def test_serial_device(port: str, baudrate: int, timeout: float = 3.0) ->
return True
return False
except asyncio.TimeoutError:
except TimeoutError:
logger.debug("Device %s timed out", port)
return False
except Exception as e:

View File

@@ -480,7 +480,7 @@ async def drain_pending_messages(mc: MeshCore) -> int:
# Small delay between fetches
await asyncio.sleep(0.1)
except asyncio.TimeoutError:
except TimeoutError:
break
except Exception as e:
logger.warning("Error draining messages: %s", e, exc_info=True)
@@ -518,7 +518,7 @@ async def poll_for_messages(mc: MeshCore) -> int:
# If we got a message, there might be more - drain them
count += await drain_pending_messages(mc)
except asyncio.TimeoutError:
except TimeoutError:
pass
except Exception as e:
logger.warning("Message poll exception: %s", e, exc_info=True)

View File

@@ -4,7 +4,7 @@ import os
import platform
import struct
import sys
from datetime import datetime, timezone
from datetime import UTC, datetime
from typing import Any, Literal
from fastapi import APIRouter
@@ -390,7 +390,7 @@ async def debug_support_snapshot() -> DebugSnapshotResponse:
is_reconnecting=is_reconnecting,
)
return DebugSnapshotResponse(
captured_at=datetime.now(timezone.utc).isoformat(),
captured_at=datetime.now(UTC).isoformat(),
system=_build_system_info(),
application=_build_application_info(),
health=_build_debug_health_summary(health_data, radio_state=radio_state),

View File

@@ -473,7 +473,7 @@ async def discover_mesh(request: RadioDiscoveryRequest) -> RadioDiscoveryRespons
break
try:
event = await asyncio.wait_for(events.get(), timeout=remaining)
except asyncio.TimeoutError:
except TimeoutError:
break
merged = _merge_discovery_result(
@@ -536,7 +536,7 @@ async def trace_path(request: RadioTraceRequest) -> RadioTraceResponse:
timeout_seconds = _trace_timeout_seconds(send_result)
try:
event = await asyncio.wait_for(response_task, timeout=timeout_seconds)
except asyncio.TimeoutError as exc:
except TimeoutError as exc:
raise HTTPException(status_code=504, detail="No trace response heard") from exc
finally:
if not response_task.done():

View File

@@ -94,7 +94,7 @@ async def fetch_contact_cli_response(
while _monotonic() < deadline:
try:
result = await mc.commands.get_msg(timeout=2.0)
except asyncio.TimeoutError:
except TimeoutError:
continue
except Exception as exc:
logger.debug("get_msg() exception: %s", exc)
@@ -196,7 +196,7 @@ async def prepare_authenticated_contact_connection(
login_future,
timeout=response_timeout,
)
except asyncio.TimeoutError:
except TimeoutError:
logger.warning(
"No login response from %s %s within %.1fs",
contact_label,

View File

@@ -1,6 +1,6 @@
import asyncio
import logging
from datetime import datetime, timezone
from datetime import UTC, datetime
logger = logging.getLogger(__name__)
@@ -193,7 +193,7 @@ async def run_post_connect_setup(radio_manager) -> None:
logger.info(
"Radio clock at connect: epoch=%d utc=%s",
radio_time,
datetime.fromtimestamp(radio_time, timezone.utc).strftime(
datetime.fromtimestamp(radio_time, UTC).strftime(
"%Y-%m-%d %H:%M:%S UTC"
),
)
@@ -274,7 +274,7 @@ async def prepare_connected_radio(radio_manager, *, broadcast_on_success: bool =
try:
await radio_manager.post_connect_setup()
break
except asyncio.TimeoutError as exc:
except TimeoutError as exc:
if attempt < POST_CONNECT_SETUP_MAX_ATTEMPTS:
logger.warning(
"Post-connect setup timed out after %ds on attempt %d/%d; retrying once",

View File

@@ -13,13 +13,12 @@ import importlib.metadata
import json
import os
import subprocess
import tomllib
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path
from typing import Any
import tomllib
RELEASE_BUILD_INFO_FILENAME = "build_info.json"
PROJECT_NAME = "remoteterm-meshcore"

View File

@@ -59,7 +59,7 @@ class WebSocketManager:
try:
# Timeout prevents blocking on slow/unresponsive clients
await asyncio.wait_for(connection.send_text(message), timeout=SEND_TIMEOUT_SECONDS)
except asyncio.TimeoutError:
except TimeoutError:
logger.debug("Timeout sending to WebSocket client, marking disconnected")
disconnected.append(connection)
except Exception as e:

View File

@@ -9,7 +9,8 @@ import type {
RadioTraceResponse,
} from '../types';
import { CONTACT_TYPE_REPEATER } from '../types';
import { calculateDistance, isValidLocation } from '../utils/pathUtils';
import { calculateDistance, formatDistance, isValidLocation } from '../utils/pathUtils';
import { useDistanceUnit } from '../contexts/DistanceUnitContext';
import { getContactDisplayName } from '../utils/pubkey';
import { handleKeyboardActivate } from '../utils/a11y';
import { ContactAvatar } from './ContactAvatar';
@@ -186,6 +187,7 @@ function TraceNodeRow({
}
export function TracePane({ contacts, config, onRunTracePath }: TracePaneProps) {
const distanceUnit = useDistanceUnit();
const [searchQuery, setSearchQuery] = useState('');
const [sortMode, setSortMode] = useState<TraceSortMode>('alpha');
const [draftHops, setDraftHops] = useState<TraceDraftHop[]>([]);
@@ -536,7 +538,7 @@ export function TracePane({ contacts, config, onRunTracePath }: TracePaneProps)
</div>
{sortMode === 'distance' && distanceKm !== null ? (
<div className="mt-1 text-[0.6875rem] text-muted-foreground">
{distanceKm.toFixed(1)} km away
{formatDistance(distanceKm, distanceUnit)} away
</div>
) : null}
{selectedCount > 0 ? (

View File

@@ -13,6 +13,9 @@
#MESHCORE_TCP_PORT=5000
# BLE (also requires the optional `bluez` package)
# NOTE: The systemd service sets ProtectHome=yes, which may block the D-Bus
# session bus at /run/user/. If BLE fails to connect, try overriding with
# ProtectHome=no in a systemd drop-in.
#MESHCORE_BLE_ADDRESS=AA:BB:CC:DD:EE:FF
#MESHCORE_BLE_PIN=123456

View File

@@ -32,7 +32,7 @@ testpaths = ["tests"]
addopts = "-n auto --dist worksteal"
[tool.ruff]
target-version = "py310"
target-version = "py311"
line-length = 100
[tool.ruff.lint]

View File

@@ -1073,9 +1073,7 @@ class TestPostConnectSetupOrdering:
rm = RadioManager()
rm._connection_info = "Serial: /dev/ttyUSB0"
rm.post_connect_setup = AsyncMock(
side_effect=[asyncio.TimeoutError(), asyncio.TimeoutError()]
)
rm.post_connect_setup = AsyncMock(side_effect=[TimeoutError(), TimeoutError()])
with (
patch("app.websocket.broadcast_error") as mock_broadcast_error,
@@ -1099,7 +1097,7 @@ class TestPostConnectSetupOrdering:
rm = RadioManager()
rm._connection_info = "Serial: /dev/ttyUSB0"
rm.post_connect_setup = AsyncMock(side_effect=[asyncio.TimeoutError(), None])
rm.post_connect_setup = AsyncMock(side_effect=[TimeoutError(), None])
with (
patch("app.websocket.broadcast_error") as mock_broadcast_error,