mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-08-07 09:12:57 +02:00
docs: cover raw resend, touch-send, analyzer-as-row, and reliability fixes
Brings the docs up to date with the two undocumented batches on dev since the last docs commit (d16093b). whatsnew.md is split by ship date: a 2026-06-26 section (already live on main) gains the raw channel-message resend feature, the Edit-message button rename, and the log-loop / connection-badge / failed-reconnect fixes; a new 2026-07-02 section covers touch-device explicit send, DM line-break preservation, and the Letsmesh analyzer becoming a normal editable row. A fresh Unreleased(sincedebb711) opens on top. architecture.md documents the channel_messages.raw_packet column, the POST /api/messages/<id>/resend endpoint, the new /api/status fw/resend/ path-hash fields, the DeviceManager raw-resend + liveness-watcher behavior, the werkzeug log-filter, and the analyzer seeding change. user-guide.md adds a Message Actions section (Edit/Resend), the touch Enter-vs-Send note in channels and DM, and rewrites the Analyzer tab to reflect Letsmesh-as-normal-row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -80,7 +80,8 @@ The `DeviceManager` handles the connection to the MeshCore device via a direct s
|
||||
- **Per-channel region scope** - Before each channel send, the channel's mapped region scope key (16 bytes) is pushed to the firmware via `CMD_SET_FLOOD_SCOPE_KEY` (54). The scope-set + send pair is serialised under a `_send_lock` so concurrent sends on different channels can't swap each other's scope. Channels without a mapping get an all-zero key so a previously-set scope doesn't leak across channels
|
||||
- **Per-send channel-secret refresh** - Channel indices on the device compact down after a deletion, so the boot-time `_load_channel_secrets()` cache can drift. `send_channel_message` calls `_refresh_channel_secret(idx)` first (one extra `get_channel(idx)` round-trip) to fetch the current secret straight from firmware, update the in-memory cache and DB if they had drifted, and use it for the `pkt_payload` echo correlation
|
||||
- **Liveness telemetry** - Tracks `_last_rx_at` (bumped on every `RX_LOG_DATA` event) and `_consecutive_stats_failures` (incremented on `get_stats_*` / `get_bat` exceptions, cleared on success). Surfaced via `/health/strict` for the external watchdog
|
||||
- **TCP self-heal** - A `_liveness_watcher_loop` task on the DM event loop calls `force_reconnect()` when no RX event has arrived for `HEALTH_STRICT_MAX_RX_STALE_SEC` (5 min). `send_channel_message` also detects empty-string `concurrent.futures.TimeoutError` from `set_flood_scope_key` (the symptom of a degraded long-lived TCP) and runs an in-place reconnect + one retry before failing. A 30 s cooldown and `_reconnect_lock` prevent churn; `_intentional_disconnect` keeps the DISCONNECTED handler from racing the reconnect
|
||||
- **TCP self-heal** - A `_liveness_watcher_loop` task on the DM event loop calls `force_reconnect()` when no RX event has arrived for `HEALTH_STRICT_MAX_RX_STALE_SEC` (5 min). `send_channel_message` also detects empty-string `concurrent.futures.TimeoutError` from `set_flood_scope_key` (the symptom of a degraded long-lived TCP) and runs an in-place reconnect + one retry before failing. A 30 s cooldown and `_reconnect_lock` prevent churn; `_intentional_disconnect` keeps the DISCONNECTED handler from racing the reconnect. The watcher keeps re-checking staleness even after `_connected` has gone False, so a single failed reconnect (e.g. an empty `self_info`) no longer silently stops all further healing for non-BLE transports
|
||||
- **Raw packet resend** - Own channel sends capture a full hex wire snapshot (`header + transport_codes + path_len + encrypted payload`) into `channel_messages.raw_packet`, rebuilt from the actual `pkt_payload` once echo correlation resolves it and honouring the device's cached `path_hash_mode`. `resend_channel_message()` re-broadcasts that snapshot verbatim via `CMD_SEND_RAW_PACKET` (0x41) so repeaters dedupe by packet hash (`Mesh::hasSeen`) and only previously-unreached nodes pick it up. Requires companion firmware ≥1.16 (`fw_ver_code` ≥ 13), gated via the cached `supports_raw_resend` flag captured from the connect-time `DEVICE_INFO` event. Self-echoes of a resend (the firmware seen-table can evict the hash within minutes on a busy mesh) are detected by recomputing the expected `pkt_payload` and matching an existing own row, so a resend never reappears as an inbound message from yourself
|
||||
|
||||
---
|
||||
|
||||
@@ -143,7 +144,7 @@ Key tables:
|
||||
- `read_status` - Per-channel read counters and favorites (`is_favorite` column; used to pin channels in the sidebar/dropdown sort order)
|
||||
- `analyzers` - User-configured MeshCore Analyzer services (`name`, `url_template` with `{packetHash}` placeholder, `is_default`, `is_disabled`; partial unique index enforces a single default)
|
||||
|
||||
`direct_messages` gained a `delivery_path_hash_size` column (auto-migrated, defaults to 1) so reloaded DM bubbles render multi-byte routes correctly. The `path_len` column on `channel_messages`, `direct_messages`, and `paths` now stores the raw firmware byte (masked hop count plus path_hash_mode in the upper bits), recombined at write time via `pack_path_len()`; the API endpoints decode it back into `path_hash_size` on read.
|
||||
`direct_messages` gained a `delivery_path_hash_size` column (auto-migrated, defaults to 1) so reloaded DM bubbles render multi-byte routes correctly. The `path_len` column on `channel_messages`, `direct_messages`, and `paths` now stores the raw firmware byte (masked hop count plus path_hash_mode in the upper bits), recombined at write time via `pack_path_len()`; the API endpoints decode it back into `path_hash_size` on read. `channel_messages` also gained a `raw_packet` column (the full hex wire snapshot captured at send time, indexed by `idx_cm_pkt` on `pkt_payload` for fast self-echo lookups) that powers raw resend; it is `NULL` for received and pre-migration rows, so the resend button stays disabled there.
|
||||
|
||||
The use of SQLite allows for fast queries, reliable data storage, full-text search, and complex filtering (such as contact ignoring/blocking) without the risk of file corruption inherent to flat JSON files.
|
||||
|
||||
@@ -169,6 +170,7 @@ The channels API reads from the `channels` DB table rather than iterating device
|
||||
| POST | `/api/messages` | Send message (`{text, channel_idx, reply_to?}`) |
|
||||
| GET | `/api/messages/updates` | Check for new messages (smart refresh) |
|
||||
| GET | `/api/messages/<id>/meta` | Get message metadata (echoes, paths) |
|
||||
| POST | `/api/messages/<id>/resend` | Re-broadcast an own channel message verbatim via `CMD_SEND_RAW_PACKET` (same packet hash, so unreached repeaters pick it up). 400 for not-own / missing `raw_packet` snapshot / disconnected / firmware < 1.16, 404 for unknown id |
|
||||
| GET | `/api/messages/search` | Full-text search (`?q=`, `?channel_idx=`, `?limit=`) |
|
||||
|
||||
### Contacts
|
||||
@@ -249,7 +251,7 @@ The `PUT /api/channels/<index>/scope` endpoint accepts any `index` in `[0, devic
|
||||
| POST | `/api/analyzers/<id>/default` | Mark as default (enforced single-default via partial unique index) |
|
||||
| DELETE | `/api/analyzers/default` | Clear the default analyzer |
|
||||
|
||||
The backend no longer ships a pre-built `analyzer_url` per message — channel-message payloads include `packet_hash` instead, and the frontend substitutes `{packetHash}` in the chosen URL template at click time.
|
||||
The backend no longer ships a pre-built `analyzer_url` per message — channel-message payloads include `packet_hash` instead, and the frontend substitutes `{packetHash}` in the chosen URL template at click time. The Letsmesh Analyzer is not a hardcoded pseudo-row: `seed_default_analyzers()` inserts it as an ordinary `analyzers` row exactly once per install (guarded by the `analyzer_letsmesh_seeded` flag in `app_settings`), so it can be renamed, disabled, or deleted like any user entry. The row stores `is_disabled` (not `is_enabled`); the UI inverts it on read/write so the switch reads "Enabled", which is why no data migration was needed.
|
||||
|
||||
### Direct Messages
|
||||
|
||||
@@ -266,7 +268,7 @@ The backend no longer ships a pre-built `analyzer_url` per message — channel-m
|
||||
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/status` | Connection status (device name, transport type, serial port / BLE address) |
|
||||
| GET | `/api/status` | Connection status (device name, transport type, serial port / BLE address). When connected also surfaces `fw_ver_code`, `supports_raw_resend`, `path_hash_mode`, and `path_hash_size` so the frontend can show/hide the raw-resend button and verify the resend snapshot's hash size |
|
||||
| GET | `/api/device/info` | Device information |
|
||||
| GET | `/api/device/stats` | Device statistics |
|
||||
| GET | `/api/device/settings` | Get device settings |
|
||||
@@ -362,6 +364,8 @@ Real-time log streaming via Socket.IO.
|
||||
**Server → Client:**
|
||||
- `log_line` - New log line
|
||||
|
||||
The `MemoryLogHandler` filters werkzeug access-log records for `/socket.io/` and `/api/logs/` paths before buffering/broadcasting. With `async_mode='threading'` Socket.IO falls back to long-polling; without this filter every poll is logged, the broadcast wakes the pending poll, the client re-polls immediately, and an open System Log tab spins at 10+ requests/sec.
|
||||
|
||||
---
|
||||
|
||||
## Offline Support
|
||||
|
||||
+16
-6
@@ -155,10 +155,19 @@ Archives are created automatically at midnight (00:00 UTC) each day. The live vi
|
||||
|
||||
**Message limit:** 140 bytes (LoRa limitation)
|
||||
|
||||
**On phones and tablets:** pressing Enter inserts a new line instead of sending — tap the **Send** button to publish. This prevents a mistapped Enter on the on-screen keyboard from firing off a half-typed message. On desktop, Enter still sends.
|
||||
|
||||
### Replying to Users
|
||||
|
||||
Click the reply button on any message to insert `@[UserName]` into the text field, then type your reply.
|
||||
|
||||
### Message Actions
|
||||
|
||||
Your own messages carry a small row of action buttons:
|
||||
|
||||
- **Edit message** (pencil icon) - Copies the message text back into the composer so you can tweak it and send it again as a new message.
|
||||
- **Resend** (repeat-arrow icon) - Re-broadcasts the *same* packet. Repeaters that already forwarded the original ignore the duplicate, but nodes that never heard it can still pick it up — so the resend extends the repeater list on the existing message's badge instead of creating a new message. Handy right after sending when the delivery badge shows only partial coverage. This button only appears when your device runs companion firmware **1.16 or newer**; on older firmware it is hidden.
|
||||
|
||||
---
|
||||
|
||||
## Message Content Features
|
||||
@@ -209,7 +218,7 @@ Access the Direct Messages feature:
|
||||
- Use the (x) button to clear the search and select a different contact
|
||||
2. Type your message in the input field (max 140 bytes, same as channels)
|
||||
3. Use the emoji picker button to insert emojis
|
||||
4. Press Enter or click Send
|
||||
4. Press Enter or click Send (on phones and tablets, Enter inserts a new line — tap Send to deliver)
|
||||
5. Click "Back" button to return to the main chat view
|
||||
|
||||
### Persistence
|
||||
@@ -679,16 +688,17 @@ Manage MeshCore region scopes (also called flood scopes). See [Region Scopes](#r
|
||||
|
||||
### Analyzer Tab
|
||||
|
||||
Configure MeshCore Analyzer services used by the chart icon under each group-chat message. The icon resolves at click time depending on what you configure here:
|
||||
Configure MeshCore Analyzer services used by the chart icon under each group-chat message. The **Letsmesh Analyzer** is added for you on first startup as an ordinary entry — you can rename, disable, star, or delete it just like any service you add yourself. The chart icon resolves at click time depending on what you have enabled here:
|
||||
|
||||
- **No custom analyzers (or all disabled)** → opens the built-in Letsmesh analyzer
|
||||
- **One default analyzer set** → opens that service directly
|
||||
- **Multiple enabled analyzers, no default** → opens a chooser modal
|
||||
- **Nothing enabled** (everything disabled or deleted) → a toast tells you "No analyzer configured" and points you back to this tab
|
||||
- **A default is set** → opens that service directly
|
||||
- **Exactly one enabled, no default** → opens it directly
|
||||
- **Several enabled, no default** → opens a chooser modal so you can pick
|
||||
|
||||
Each row supports:
|
||||
|
||||
- **Enabled switch** — the switch reads as **Enabled** when it is on (checked = active); flip it off to keep a service configured but out of the picker
|
||||
- **Star toggle** — mark this analyzer as the default. Only one default is allowed
|
||||
- **Enabled switch** — temporarily disable a service without deleting it
|
||||
- **Edit / Delete** buttons
|
||||
|
||||
When adding or editing, the URL template must contain the placeholder `{packetHash}` — it is substituted with the message's packet hash at click time.
|
||||
|
||||
+25
-1
@@ -6,10 +6,31 @@ For deep technical notes, see [architecture.md](architecture.md). For the full g
|
||||
|
||||
---
|
||||
|
||||
## Unreleased (since fd2b3d0)
|
||||
## Unreleased (since debb711)
|
||||
|
||||
_Nothing yet — the next change to land on `dev` goes here._
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-02
|
||||
|
||||
### New features
|
||||
|
||||
- **Explicit Send on phones and tablets.** On touch devices, pressing Enter now inserts a new line instead of sending — you tap the **Send** button to publish. This stops a mistapped Enter on the on-screen keyboard from firing off a half-typed message. Desktop keeps Enter-to-send. Applies to both group chat and direct messages.
|
||||
- **Manage the Letsmesh analyzer like any other entry.** The built-in Letsmesh Analyzer is now a normal row in **Settings → Analyzer** — rename it, disable it, star it as the default, or delete it, just like a service you add yourself. The chart icon under a message resolves at click time: nothing enabled shows a "No analyzer configured" hint, a starred default (or a single enabled service) opens directly, and several enabled without a default show a chooser. The row's switch now reads **Enabled** when it's on.
|
||||
|
||||
### Reliability & polish
|
||||
|
||||
- **Multi-line direct messages keep their line breaks.** A DM you sent across several lines showed correctly to the recipient but collapsed to a single line in your own copy. Your own bubble now preserves the line breaks too.
|
||||
|
||||
---
|
||||
|
||||
## 2026-06-26
|
||||
|
||||
### New features
|
||||
|
||||
- **Resend a channel message (same packet).** Your own group-chat messages now carry a repeat-arrow button that re-broadcasts the *exact same* packet. Repeaters that already forwarded it stay quiet, but nodes that never heard it can still pick it up — so a resend fills in coverage on the existing message's delivery badge instead of posting a duplicate. Requires companion firmware 1.16 or newer; the button is hidden on older devices.
|
||||
- **Clearer "Edit message" button.** The button that copies a message back into the composer for hand-editing used to be mislabeled "Resend." It's now a pencil **Edit message** button on both channel and direct messages, clearly separate from the real Resend above.
|
||||
- **Custom Analyzer services.** A new **Settings → Analyzer** tab lets you register your own MeshCore Analyzer services. Each entry has an enable/disable switch, a "star" toggle to mark it as the default, and an Edit/Delete pair. The chart icon under each group-chat message now resolves at click time: built-in Letsmesh if you haven't configured anything, the default service when one is set, or a chooser modal when several are enabled. URL templates use `{packetHash}` as a placeholder.
|
||||
- **Apply a saved path straight from Contact Info.** Each entry in the **Paths** list inside the DM Contact Info modal gained an upload-arrow button. Click it to push that configured path to the device as the active route — no more switching to the console to run `change_path`.
|
||||
- **Database "Optimize now" button + live size.** The Backup modal now shows the current DB size and exposes an **Optimize now** button that runs SQLite `VACUUM` on demand. Useful after a big retention pass when you want to reclaim space without waiting for the nightly job.
|
||||
@@ -18,6 +39,9 @@ For deep technical notes, see [architecture.md](architecture.md). For the full g
|
||||
|
||||
### Reliability & polish
|
||||
|
||||
- **System Log tab no longer floods the server.** Opening the System Log could trigger a feedback loop that hammered the server with 10+ requests a second; the log noise that caused it is now filtered out, so the tab stays quiet.
|
||||
- **The connection badge stops lying about device state.** The status badge could flip back to "Connected" on a routine message refresh even while the device was actually disconnected. Device status is now driven only by real device connectivity, with a 60-second fallback check so a long-open tab stays accurate.
|
||||
- **Automatic recovery after a failed reconnect.** A reconnect that failed quietly used to leave the app stuck "disconnected" until the container was restarted by hand. The background liveness watcher now keeps retrying instead of giving up after a single failure.
|
||||
- **No more 10–15 s freezes on app load.** The realtime channel used a transport that the dev server couldn't upgrade; we now stay on long-polling, which keeps real-time pushes working without the reconnect loop.
|
||||
- **Channel list stays complete when the device is slow.** Channels are now read from the local cache rather than re-queried slot-by-slot, so a brief device stall no longer leaves you with just the Public channel after a refresh.
|
||||
- **Sending on a re-used channel slot now works after a deletion.** When you delete a channel, the device compacts the remaining slots — until now the app kept using the old keys for that slot. We refresh the secret from the device just before each send.
|
||||
|
||||
Reference in New Issue
Block a user