From 2f8f765af520dd254477646d406ce02c8b0dfdb8 Mon Sep 17 00:00:00 2001 From: MarekWo Date: Tue, 9 Jun 2026 19:01:07 +0200 Subject: [PATCH] fix(channels): backfill raw-resend buttons when displayMessages races loadStatus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit loadStatus and loadMessages fire in parallel at page init. Whichever lost the race left own-message bubbles without the raw-resend button — and once the row was rendered, neither displayMessages (no re-render on switch without going back through createMessageElement, which is gated on window.deviceCaps at the time of call) nor refreshMessagesMeta (skips rows that already have route info) would patch it back in. So the button only ever appeared for messages sent in the current session. Walk visible own bubbles in two places — at the end of loadStatus and at the end of displayMessages — and inject the button on any row missing it. Idempotent (skips bubbles that already have .btn-raw-resend), cheap (no network), and covers both the page-reload race and the channel-switch / archive-view paths. Co-Authored-By: Claude Opus 4.7 --- app/static/js/app.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app/static/js/app.js b/app/static/js/app.js index 0c442f5..5e35153 100644 --- a/app/static/js/app.js +++ b/app/static/js/app.js @@ -1046,6 +1046,13 @@ function displayMessages(messages) { markChannelAsRead(currentChannelIdx, latestTimestamp); } + // Backfill raw-resend buttons on own messages — handles the case where + // displayMessages ran before window.deviceCaps was populated (initial + // page load race), and the channel-switch case where createMessageElement + // does render the button but we want belt-and-suspenders for any path + // (e.g. archive view) that might bypass it. + injectRawResendButtonsForVisibleMessages(); + // Re-apply filter if active clearFilterState(); } @@ -1760,6 +1767,11 @@ async function loadStatus() { supports_raw_resend: !!data.supports_raw_resend, fw_ver_code: data.fw_ver_code ?? null, }; + // loadStatus and loadMessages run in parallel at page init, so + // any messages rendered before this point missed the deviceCaps + // check and have no raw-resend button. Walk visible own bubbles + // and inject the button where it's missing. + injectRawResendButtonsForVisibleMessages(); } } catch (error) { console.error('Error loading status:', error); @@ -1767,6 +1779,31 @@ async function loadStatus() { } } +/** + * Walk the messagesList and inject the raw-resend button on any own message + * bubble that's missing it (e.g. rendered before window.deviceCaps was set, + * or re-rendered by displayMessages on channel switch). Safe to call any + * time — does nothing when the device doesn't support raw resend. + */ +function injectRawResendButtonsForVisibleMessages() { + if (!window.deviceCaps?.supports_raw_resend) return; + const container = document.getElementById('messagesList'); + if (!container) return; + const wrappers = container.querySelectorAll('.message-wrapper.own[data-msg-id]'); + for (const wrapper of wrappers) { + const msgId = wrapper.dataset.msgId; + if (!msgId || msgId.startsWith('_pending_')) continue; + const actionsEl = wrapper.querySelector('.message-actions'); + if (!actionsEl || actionsEl.querySelector('.btn-raw-resend')) continue; + const rawBtn = document.createElement('button'); + rawBtn.className = 'btn btn-outline-secondary btn-msg-action btn-raw-resend'; + rawBtn.setAttribute('onclick', `resendChannelMessageRaw(${msgId}, this)`); + rawBtn.title = 'Resend (rebroadcast same packet so unreached repeaters can pick it up)'; + rawBtn.innerHTML = ''; + actionsEl.appendChild(rawBtn); + } +} + /** * Copy text to clipboard with visual feedback */