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>
This commit is contained in:
MarekWo
2026-03-31 09:58:41 +02:00
parent 0ecb91aa08
commit 695321c0c9
2 changed files with 30 additions and 8 deletions
+21 -2
View File
@@ -1403,12 +1403,15 @@ class DeviceManager:
'max_attempts': max_attempts,
}, namespace='/chat')
def _emit_retry_failed(self, dm_id: int, expected_ack: str):
def _emit_retry_failed(self, dm_id: int, expected_ack: str,
attempt: int = 0, max_attempts: int = 0):
"""Notify frontend that all retry attempts were exhausted."""
if self.socketio:
self.socketio.emit('dm_retry_failed', {
'dm_id': dm_id,
'expected_ack': expected_ack,
'attempt': attempt,
'max_attempts': max_attempts,
}, namespace='/chat')
@staticmethod
@@ -1732,7 +1735,7 @@ class DeviceManager:
# ── Common epilogue: mark failed, grace period for late ACKs ──
self.db.update_dm_delivery_info(dm_id, attempt + 1, max_attempts, '')
self.db.update_dm_delivery_status(dm_id, 'failed')
self._emit_retry_failed(dm_id, initial_ack)
self._emit_retry_failed(dm_id, initial_ack, attempt + 1, max_attempts)
logger.warning(f"DM retry exhausted ({attempt + 1} total attempts, scenario={scenario}) "
f"for dm_id={dm_id}")
self._retry_tasks.pop(dm_id, None)
@@ -1759,6 +1762,12 @@ class DeviceManager:
dm_id=dm_id,
)
# Save delivery info from retry context (attempt count + path)
ctx = self._retry_context.pop(dm_id, None)
if ctx:
self.db.update_dm_delivery_info(
dm_id, ctx['attempt'], ctx['max_attempts'], ctx['path'])
# Mark delivery_status so reloading messages from DB shows delivered
self.db.update_dm_delivery_status(dm_id, 'delivered')
@@ -1776,6 +1785,16 @@ class DeviceManager:
'snr': data.get('snr'),
}, namespace='/chat')
# Emit delivery info so frontend shows attempt count + route immediately
if ctx:
self.socketio.emit('dm_delivered_info', {
'dm_id': dm_id,
'attempt': ctx['attempt'],
'max_attempts': ctx['max_attempts'],
'path': ctx['path'],
'hash_size': ctx.get('hash_size', 1),
}, namespace='/chat')
# Cleanup pending acks for this DM
stale = [k for k, v in self._pending_acks.items() if v == dm_id]
for k in stale:
+9 -6
View File
@@ -117,9 +117,6 @@ function connectChatSocket() {
if (wrapper) {
wrapper.replaceWith(statusEl);
}
// Clear retry counter in actions area
const retryInfo = el.querySelector('.dm-retry-info');
if (retryInfo) retryInfo.textContent = '';
}
});
});
@@ -131,7 +128,7 @@ function connectChatSocket() {
if (info) info.textContent = `Attempt ${data.attempt}/${data.max_attempts}`;
});
// DM retry exhausted — mark as failed
// DM retry exhausted — mark as failed, show final attempt count
chatSocket.on('dm_retry_failed', (data) => {
if (!data.dm_id) return;
// Update status icon
@@ -145,9 +142,15 @@ function connectChatSocket() {
wrapper.removeAttribute('onclick');
wrapper.classList.remove('dm-status-unknown');
}
// Clear retry counter
// Show final attempt count
const info = document.querySelector(`.dm-retry-info[data-dm-id="${data.dm_id}"]`);
if (info) info.textContent = '';
if (info) {
if (data.attempt && data.max_attempts) {
info.textContent = `Attempt ${data.attempt}/${data.max_attempts}`;
} else {
info.textContent = '';
}
}
});
// Real-time delivery info — show attempt count + route after successful delivery