fix(ui): refresh Contact Info path display in real-time

Path info in Contact Info modal was stale due to 60s server cache
and no refresh after path operations. Now:
- Invalidate contacts cache after reset_path, change_path, path_update
- Emit 'path_changed' socket event on PATH_UPDATE from device
- UI listens and re-renders Contact Info when path changes
- Reset to FLOOD button immediately refreshes the path display

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
MarekWo
2026-03-27 20:29:26 +01:00
parent 292d1d91af
commit 5df9b4b4a2
3 changed files with 61 additions and 0 deletions

View File

@@ -740,6 +740,19 @@ class DeviceManager:
)
logger.debug(f"Path update for {pubkey[:8]}...")
# Invalidate contacts cache so UI gets fresh path data
try:
from app.routes.api import invalidate_contacts_cache
invalidate_contacts_cache()
except ImportError:
pass
# Notify UI about path change
if self.socketio:
self.socketio.emit('path_changed', {
'public_key': pubkey,
}, namespace='/chat')
# Backup: check for pending DM to this contact
for ack_code, dm_id in list(self._pending_acks.items()):
dm = self.db.get_dm_by_id(dm_id)
@@ -1161,6 +1174,12 @@ class DeviceManager:
"""Change contact path on device with proper hash_size encoding."""
path_hash_mode = hash_size - 1 # 0=1B, 1=2B, 2=3B
await self.mc.commands.change_contact_path(contact, path_hex, path_hash_mode=path_hash_mode)
# Invalidate contacts cache so UI gets fresh path data
try:
from app.routes.api import invalidate_contacts_cache
invalidate_contacts_cache()
except ImportError:
pass
async def _restore_primary_path(self, contact, contact_pubkey: str):
"""Restore the primary configured path on the device after retry exhaustion."""

View File

@@ -2322,6 +2322,7 @@ def reset_contact_to_flood(pubkey):
dev_result = dm.reset_path(pubkey)
logger.info(f"reset_path({pubkey[:12]}...) result: {dev_result}")
if dev_result.get('success'):
invalidate_contacts_cache()
return jsonify({'success': True}), 200
return jsonify({'success': False, 'error': dev_result.get('error', 'Device reset failed')}), 500
except Exception as e:

View File

@@ -120,6 +120,18 @@ function connectChatSocket() {
chatSocket.on('device_status', (data) => {
updateStatus(data.connected ? 'connected' : 'disconnected');
});
// Real-time path change — refresh Contact Info if open for this contact
chatSocket.on('path_changed', async (data) => {
const modalEl = document.getElementById('dmContactInfoModal');
if (!modalEl || !modalEl.classList.contains('show')) return;
const currentPubkey = getCurrentContactPubkey();
if (!currentPubkey) return;
const changedKey = (data.public_key || '').toLowerCase();
if (changedKey && changedKey.startsWith(currentPubkey.toLowerCase())) {
await refreshContactInfoPath();
}
});
}
// Initialize on page load
@@ -975,6 +987,34 @@ function populateContactInfoModal() {
}
}
/**
* Refresh contact data from device and re-render Contact Info modal if open.
* Uses ?refresh=true to bypass server-side cache.
*/
async function refreshContactInfoPath() {
try {
const response = await fetch('/api/contacts/detailed?refresh=true');
const data = await response.json();
if (data.success) {
contactsList = (data.contacts || []).sort((a, b) =>
(a.name || '').localeCompare(b.name || ''));
contactsMap = {};
contactsList.forEach(c => {
if (c.public_key) contactsMap[c.public_key] = c;
});
}
} catch (e) {
console.error('[DM] refreshContactInfoPath fetch error:', e);
return;
}
// Re-populate modal if still open
const modalEl = document.getElementById('dmContactInfoModal');
if (modalEl && modalEl.classList.contains('show')) {
populateContactInfoModal();
loadPathSection();
}
}
/**
* Load messages for current conversation
*/
@@ -2114,6 +2154,7 @@ function setupPathFormHandlers(pubkey) {
const data = await response.json();
if (data.success) {
showNotification('Device path reset to FLOOD', 'info');
await refreshContactInfoPath();
} else {
showNotification(data.error || 'Reset failed', 'danger');
}