Commit Graph

555 Commits

Author SHA1 Message Date
MarekWo
1e6f8caf03 fix: invalidate self_info cache after set_param
get_device_info() cached SELF_INFO payload in _self_info and never
refreshed it after set operations, so get always returned stale values.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 14:40:14 +02:00
MarekWo
c3f61ce3f7 fix: get radio returns actual values, implement set radio command
get radio used wrong key names (freq/bw/sf/cr instead of
radio_freq/radio_bw/radio_sf/radio_cr from SELF_INFO payload).

set radio was missing entirely — would silently fall through to
custom variable handler. Now parses freq,bw,sf,cr and calls
mc.commands.set_radio().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 14:33:13 +02:00
MarekWo
3086ac1f8e fix: add value hints to console help for telemetry, advert and path params
Unified format: <0-2> on the left, (0=x/1=y/2=z) on the right.
Values sourced from MeshCore firmware defines:
- telemetry_mode_*: 0=off, 1=selected, 2=all
- advert_loc_policy: 0=none, 1=share, 2=prefs
- path_hash_mode: 0, 1, 2

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 14:24:25 +02:00
MarekWo
1a194d5050 fix: implement get advert_loc_policy console command
The set command was implemented but get was missing, causing
"Unknown param" error. Reads adv_loc_policy from device info.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 14:10:20 +02:00
MarekWo
19b2a172c8 feat: persist FAB collapsed state across page loads
Save collapsed/expanded state to localStorage (shared key for both
main chat and DM views) so buttons stay hidden when the user
previously collapsed them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 12:26:56 +02:00
MarekWo
fb99054e4b fix: defer FAB position restore when iframe viewport is too small
The DM iframe reloads on every modal open. During the show transition
the viewport is 0x0, causing clampFabPosition to push buttons to the
top-left corner. Now polls until viewport is valid before restoring.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 12:19:44 +02:00
MarekWo
60c698deb2 feat: add Settings FAB button, drag-and-drop positioning, and size/spacing controls
- Add Settings quick-access button to both main chat and DM views
- Make FAB container draggable via toggle button with position saved to localStorage
- Add button size and spacing sliders in Settings > Appearance tab
- Use CSS custom properties for dynamic FAB sizing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 11:59:24 +02:00
MarekWo
6c02220719 fix: skip empty channel slots during sync, clean up stale DB channels
Empty device channel slots have all-zero secrets (32 hex chars) which
passed the length check and got persisted to DB as "Channel N". This
caused ghost channels (e.g. Channel 14) to appear in unread counts
while the sidebar correctly showed only real channels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 11:08:38 +02:00
MarekWo
c36d7b5fbf fix(ble): simplify reconnection — rely on container restart for clean state
In-container BLE reconnection is unreliable because bleak leaves stale
GATT notification handles after abnormal disconnect, and adapter power-
cycling from within Docker corrupts bleak's internal BlueZ manager state.

New approach:
- On BLE disconnect or keepalive failure, immediately mark as permanently
  failed (no in-container reconnect attempts)
- Health endpoint returns 503, Docker healthcheck triggers container restart
- Docker entrypoint script disconnects stale BLE connections before app
  starts, ensuring clean GATT state for bleak

This is reliable because:
- MeshCore.create_ble(address=...) works on fresh container starts
- The BlueZ daemon on the host maintains adapter state correctly
- Container restart is fast (~5s) and gives a truly clean BLE state

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 16:39:03 +02:00
MarekWo
53063f199a fix(ble): connect via BlueZ D-Bus instead of bleak direct connect
bleak inside Docker cannot initiate new BLE connections — it can only
take over connections already established by BlueZ.  Replace the
force-disconnect approach with a connect-via-BlueZ approach:

1. _ble_ensure_connected() connects the device via BlueZ D-Bus
   (Device1.Connect) before bleak tries to take over
2. BleakScanner.find_device_by_address() provides the BLEDevice
   object that bleak 3.x needs (raw MAC address doesn't work)
3. MeshCore.create_ble(device=...) takes over the BlueZ connection

On reconnect after disconnect:
1. Power-cycle adapter clears stale GATT notification handles
2. BlueZ re-connects the trusted device automatically
3. bleak takes over the re-established connection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 14:13:06 +02:00
MarekWo
9c692fac8b fix(ble): use BleakScanner to find device before connecting
In bleak 3.x, BleakClient(address_string) can't find paired BLE
devices that aren't actively advertising.  This caused
BleakDeviceNotFoundError or 30-second connection timeouts.

Fix: pre-scan via BleakScanner.find_device_by_address() which queries
BlueZ's D-Bus object tree directly, then pass the BLEDevice object to
MeshCore.create_ble(device=...) instead of the raw MAC address.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 14:10:47 +02:00
MarekWo
a92b505975 fix(ble): untrust device during connect to prevent BlueZ auto-reconnect
BlueZ auto-reconnects trusted BLE devices, which races with bleak's
connect and causes 'failed to discover services' or 'Notify acquired'.
Now we temporarily untrust the device before connecting (to prevent
BlueZ from auto-reconnecting during the handoff), then re-trust it
after bleak has established its GATT session.

Also adds _ble_retrust() helper to re-trust the device in a finally
block, ensuring the bond is maintained even on connection failure.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 14:06:18 +02:00
MarekWo
1de98433d4 fix(ble): add adapter power-cycle to startup retry loop
On startup, _connect_with_retry also needs adapter power-cycling every
3rd failed attempt to clear stale GATT state from previous sessions.
Without this, the container can fail all 10 startup retries when BlueZ
holds stale notification handles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 13:40:53 +02:00
MarekWo
f352ccd968 fix(ble): add keepalive and robust reconnection for BLE zombie connections
BLE connections can enter a "zombie" state where notifications (reads) still
arrive but writes silently fail.  This went undetected until the user tried
to send a message, at which point the connection was already dead.

Additionally, after an abnormal BLE disconnect, BlueZ retains stale GATT
notification handles, causing reconnection to fail with
"[org.bluez.Error.NotPermitted] Notify acquired".

Changes:
- Add BLE keepalive loop (60s interval) that sends get_bat() to detect
  zombie connections proactively and trigger reconnection automatically
- Add adapter power-cycle (hci0 off/on via D-Bus) during BLE reconnection
  to clear stale GATT notification state
- Dedicated _ble_reconnect() with 5 attempts + adapter reset between each
- Health endpoint returns 503 when BLE permanently fails, triggering
  Docker container restart via healthcheck
- Guard against concurrent reconnection attempts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 13:37:33 +02:00
MarekWo
61d60ea4b6 fix(ci): lowercase GHCR owner name and update actions versions
- Fix "repository name must be lowercase" error for GHCR tags
- Update actions/checkout to v5, docker/build-push-action to v6

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 15:39:30 +02:00
MarekWo
8aac3d4839 ci: add dual registry (Docker Hub + GHCR) and dev branch builds
- Push images to both Docker Hub and GitHub Container Registry
- Build on main (latest tag) and dev (dev tag) branches
- Update README with Docker Hub installation instructions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 15:35:33 +02:00
MarekWo
bc578c7018 ci: add Docker Hub publish workflow
Automatically builds and pushes mc-webui image to Docker Hub
(mawoj/mc-webui) on push to main, with manual trigger support.
Tags: latest + version from git (e.g. 2026.04.01-b286072).

Based on x9daniel's PR #21, adapted for Docker Hub.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 15:00:32 +02:00
MarekWo
b2860720d5 fix(ui): change initial DM status text to 'Sending...'
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-01 07:54:07 +02:00
MarekWo
f6c9c65a51 fix(channels): refresh channel secret cache after join/create
After set_channel(), read back the actual secret from the device and
update both _channel_secrets in-memory cache and the DB. This fixes
newly-joined # channels (where firmware auto-generates the key) having
no repeater info, missing Analyzer URLs, and incorrect route data until
container restart.

Also clean up _channel_secrets on channel removal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 21:00:34 +02:00
MarekWo
5919e43f3a refactor(chat): remove 60s polling, rely fully on WebSocket for real-time updates
The setupAutoRefresh 60s interval was a legacy fallback from before WebSocket
support. All updates it handled are now covered by SocketIO events:
- new_message: channel messages and DMs
- echo: repeater/route metadata
- pending_contact: new handler added to update badge in real-time

checkForUpdates() is kept for initial load and manual refresh button only,
with its loadMessages() call removed (badges-only now).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 10:32:24 +02:00
MarekWo
29e5e6982d fix(chat): prevent poll-triggered reload after send by using server timestamp
The 60s checkForUpdates poll was detecting has_updates due to clock skew
between client and server timestamps. Now the send API returns the server
timestamp, and the frontend uses it for markChannelAsRead — ensuring the
poll sees no updates for own sent messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 10:28:54 +02:00
MarekWo
6eb2250d88 fix(chat): remove separator line in bubbles and use WebSocket for echo updates
Remove unnecessary border-top separator above action buttons in message bubbles.
Replace 15s deferred loadMessages() after send with real-time echo updates via
WebSocket — API now returns msg_id so optimistic message gets linked to DB record.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 10:17:10 +02:00
MarekWo
695321c0c9 fix(dm): show delivery info immediately on ACK/failure without reopen
_confirm_delivery() now saves retry context (attempt, max_attempts,
path) and emits dm_delivered_info so the frontend shows delivery
details instantly. Similarly, dm_retry_failed now includes attempt
count so the failure state shows how many attempts were made.

Previously this info was only available after reloading messages
from DB (closing and reopening the conversation).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 09:58:41 +02:00
MarekWo
0ecb91aa08 fix(dm): show 'Attempt 1/...' immediately after sending DM
The retry progress element was always created empty. The socket event
with the real attempt count could arrive before the DOM was ready,
causing it to be lost. Now pending messages pre-populate with
'Attempt 1/...' which gets updated to the real count when the socket
event arrives.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 09:47:41 +02:00
MarekWo
b60c99aad1 docs: update documentation for BLE transport, DM retry scenarios, and path_hash_mode
Add new docs to README table (DM retry logic, BLE pairing guide), update
architecture diagram and DB schema for BLE/delivery tracking, rewrite DM
retry settings section in user guide to reflect 4-scenario matrix, add BLE
troubleshooting reference, and update .claude/context files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 19:50:43 +02:00
MarekWo
a7c5e1a8c3 fix(path_hash_mode): echo badge uses echo hash_size, not message path_hash_size
For own (sent) messages, path_len is NULL so path_hash_size defaults to
1, causing echo badge to show 1-byte prefixes (D1, 5E) instead of
2-byte (D103, 5E34) when path_hash_mode>0. Now uses hash_size from the
first echo record (echo_hash_sizes[0]) which carries the correct value
from RX_LOG_DATA parsing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 15:22:34 +02:00
MarekWo
2368ec656e feat(path_hash_mode): fix DM route display and delivery path segmentation
Stage 4 of path_hash_mode support. DM delivery paths now carry hash_size
through the entire pipeline: retry context → ACK handler → SocketIO
emission → frontend rendering. All hardcoded 2-char hex segmentation
removed from dm.js.

Backend changes (device_manager.py):
- Track path_hash_size alongside path_desc in DM retry context
- Update path_hash_size on path rotation and flood fallback
- Add hash_size to all 4 dm_delivered_info SocketIO emissions
- Derive hash_size from PATH event path_len for discovered paths

Frontend changes (dm.js):
- Add segmentHexPath() utility (shared by all 3 route functions)
- formatDmRoute(), buildDmRouteHtml(), showDmRoutePopup() accept hashSize
- All call sites pass hash_size from event data or message context

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 13:11:00 +02:00
MarekWo
083c322741 feat(path_hash_mode): fix frontend hops display, path segmentation, echo badge
Stage 3 of path_hash_mode support. All hardcoded 1-byte hash assumptions
removed from app.js — hops, path segments, and echo badges now use the
decoded hop_count and hash_size from the backend.

Changes in app.js:
- appendMessageFromSocket: pass hop_count, path_hash_size, echo_hash_sizes
- updateMessageMetaDOM: use hop_count instead of raw path_len for Hops
- updateMessageMetaDOM: segment paths by hash_size*2 chars, not fixed 2
- updateMessageMetaDOM: echo badge uses hash_size-aware prefix length
- createMessageElement: same fixes as updateMessageMetaDOM
- showPathsPopup: segment paths by hash_size, derive hops from segments

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 10:22:57 +02:00
MarekWo
e8f271f4ef feat(path_hash_mode): add hop_count and path_hash_size to API responses
Stage 2 of path_hash_mode support. All API endpoints and SocketIO
emissions now include decoded hop_count and path_hash_size fields
alongside the raw path_len, so the frontend can display and segment
paths correctly for any hash mode.

Changes:
- Import decode_path_len in api.py
- GET /api/messages: add hop_count, path_hash_size, echo_hash_sizes
- GET /api/messages/<id>/meta: add hop_count, path_hash_size, echo_hash_sizes
- GET /api/dm/messages: add hop_count, path_hash_size
- SocketIO new_message emission: add hop_count, path_hash_size

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 10:00:03 +02:00
MarekWo
719e11e868 feat(path_hash_mode): add decode_path_len and fix RX_LOG_DATA parsing
Stage 1 of path_hash_mode support. The critical bug in _on_rx_log_data
treated the raw path_len byte as a direct byte count, which breaks with
mode>0 (e.g. mode=1, 0 hops → path_len=0x40=64, reading 64 bytes of
non-existent path data). Now properly decodes the encoded path_len byte
into hop_count, hash_size, and path_byte_len.

Changes:
- Add decode_path_len() utility for MeshCore v1.14+ path_len encoding
- Fix _on_rx_log_data binary parsing to use decoded path length
- Pass hash_size through _process_echo → DB insert → SocketIO emission
- Add hash_size column to echoes table (schema + migration)
- Update insert_echo() to store hash_size (default 1 for backward compat)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 09:47:20 +02:00
MarekWo
1d9742a1ee style(contacts): change existing contacts badge to show total + device count
Format changed from "X/350 (Y cached)" to "Y (🖥 X/350)" where Y is
total known contacts and X is device count, with bi-cpu icon for device.
Applied consistently to both the manage tile and existing contacts header.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 08:35:00 +02:00
MarekWo
a983210e10 style(dm): move timestamp above bubble, improve meta readability
Move DM timestamp+status row above the message bubble (consistent with
group messages). Increase delivery/SNR meta font size and adjust color
for better readability in both light and dark themes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 20:32:36 +02:00
MarekWo
10c232fc7d fix(ble): force-disconnect stale BlueZ connection before connecting
BlueZ auto-reconnects trusted BLE devices after container restart,
blocking bleak from establishing a new GATT session. Clear the stale
connection via D-Bus before each connect attempt.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 19:42:34 +02:00
MarekWo
9f335794e4 fix(ble): update runtime device name on every connect
BLE connections with retries can take >60s, exceeding the startup
wait timeout. Move runtime_config.set_device_name() into _connect()
so the navbar shows the correct name regardless of connection delay.
Also fixes name update on reconnections.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 19:24:45 +02:00
MarekWo
147a12c8f5 fix(dm): persist delivery_status='delivered' on ACK receipt
DM delivery status was lost when switching conversations because
_confirm_delivery() only stored the ACK record and emitted a socket
event, but never set delivery_status='delivered' in direct_messages.

During retries, each attempt generates a new ACK code. The DM record
stores the initial expected_ack, but the actual ACK may arrive for a
later retry's code. The ACK lookup by expected_ack then fails to match.

Now _confirm_delivery() also sets delivery_status='delivered', and
message loading checks this DB field first (like it already did for
'failed'), so delivery persists across page navigations.

Also fixed 213 existing DMs on server via data migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 14:49:37 +02:00
MarekWo
1fdc2eda93 docs(ble): add troubleshooting section to pairing guide
bluetoothctl info auto-connects to trusted devices, stealing the
connection from Docker — document hcitool as safe alternative and
add connection loop recovery steps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 14:07:21 +02:00
MarekWo
b18c0145dd docs(ble): add pairing guide, remove unused MC_BLE_PIN config
MC_BLE_PIN was non-functional — bleak in Docker cannot perform
interactive pairing (no BlueZ agent). Pairing must be done on
the host before starting mc-webui. Added comprehensive pairing
guide at docs/meshcore_bluetooth_pairing.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 13:56:45 +02:00
MarekWo
710f69c350 feat: add BLE transport support for companion devices
Integrate meshcore library's BLE connection (via bleak) as a third
transport option alongside serial and TCP. Priority: BLE > TCP > Serial.

Config: MC_BLE_ADDRESS and MC_BLE_PIN environment variables.
Docker: bluez/dbus packages, NET_ADMIN cap, D-Bus socket mount.
UI: transport type badge in navbar, transport_type in /api/status.
Watchdog: skip USB reset for BLE connections (same as TCP).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 10:03:45 +02:00
MarekWo
bd59826504 docs: add DM retry logic guide for users
Explains the 4 delivery scenarios, how settings map to behavior,
why actual wait times can exceed configured intervals (firmware
suggested_timeout), and what to look for in the System Log.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 20:46:28 +01:00
MarekWo
701f6f1197 fix(dm): refresh mc.contacts from device on PATH_UPDATE event
The Contact Info dialog showed stale path data (e.g. "Flood" instead of
the discovered route) because auto_update_contacts is OFF and PATH_UPDATE
only sets _contacts_dirty=True without refreshing mc.contacts. The API
then served stale in-memory data even after cache invalidation.

Now ensure_contacts(follow=True) is called on PATH_UPDATE to read fresh
contact data from the device before invalidating cache and emitting the
socket event. PATH_UPDATE events are rare (only on path discovery), so
the serial I/O cost is acceptable unlike advertisements.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 17:52:41 +01:00
MarekWo
0b3bd1da60 fix(dm): delayed path backfill for FLOOD-delivered messages
When FLOOD delivery is confirmed, the PATH_UPDATE event payload often
has empty path data because firmware updates the contact's out_path
asynchronously. After 3s delay, read the contact's updated path from
the meshcore library's in-memory contacts dict and backfill the DB.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 15:23:35 +01:00
MarekWo
4de6d72cfe fix(dm): update delivery path from PATH event after ACK race
When both ACK and PATH_UPDATE fire for FLOOD delivery, _on_ack may
store empty path before PATH_UPDATE can provide the discovered route.
Now _on_path_update also checks for recently-delivered DMs with empty
delivery_path and backfills with the discovered path from the event.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 15:05:35 +01:00
MarekWo
58af37238b fix(ui): move retry counter above Resend button, same line as delivery info
Retry counter now renders as a dm-delivery-meta div above the Resend
button instead of inline next to it, matching the position of the
post-delivery info. Prevents text from crowding the button on short
messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 14:58:31 +01:00
MarekWo
f135c90e61 fix(ui): align DM route popup to the right to prevent overflow
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 14:48:27 +01:00
MarekWo
90c1c90ba3 feat(dm): clickable route popup for long delivery paths
Long routes (>4 hops) show truncated with dotted underline; clicking
opens a popup with the full route and hop count, same style as channel
message path popups. Short routes (<=4 hops) display inline as before.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 14:44:40 +01:00
MarekWo
7d8a3c895d fix(dm): use discovered path from PATH event for delivery route
When PATH_UPDATE confirms delivery, use the actual path from the
event data instead of the empty path_desc from _retry_context (which
is empty during FLOOD phase). This captures the route firmware
discovered via the flood delivery.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 14:39:53 +01:00
MarekWo
3c7f70175f fix(dm): handle FLOOD delivery and old DIRECT path gracefully
Add hex validation to formatDmRoute to avoid garbling old "DIRECT"
values. When no hex route available (FLOOD delivery), fall back to
delivery_route from ACK (e.g. show "FLOOD" stripped of PATH_ prefix).
Ensures delivery meta always shows something useful.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 14:28:40 +01:00
MarekWo
7a44d3b95d fix(dm): resolve race condition — delivery info stored before task cancel
The _on_ack handler cancels the retry task before _retry() can store
delivery info (attempt count, path). Fix by maintaining a _retry_context
dict updated before each send. _on_ack reads context and stores delivery
info + emits dm_delivered_info BEFORE cancelling the task. Same fix
applied to PATH_UPDATE backup delivery handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 14:11:05 +01:00
MarekWo
885a967348 fix(dm): show delivery route as hex path, add real-time delivery info
Store actual hex path instead of DIRECT/FLOOD labels in delivery_path.
Format route as AB→CD→EF (same as channel messages, truncated if >4
hops). Add dm_delivered_info WebSocket event so delivery meta appears
in real-time without needing page reload. Remove path info from failed
messages since it's not meaningful for undelivered messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 13:21:53 +01:00
MarekWo
677036a831 fix(dm): move retry counter below message, show delivery info visually
Move the attempt counter (e.g. "Attempt 15/24") from next to the status
icon to below the message text, left of the Resend button. Add visible
delivery meta line for delivered/failed messages showing attempt count
and path used. Store attempt info for failed messages too. Replace
Polish abbreviations (ŚK, ŚD, ŚG) with English in all log messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 12:52:00 +01:00