diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8d2c1dd22..03a922805 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -411,6 +411,7 @@ jobs: BASE_URL=http://localhost:13581 node test-channel-issue-1087-e2e.js 2>&1 | tee -a e2e-output.txt BASE_URL=http://localhost:13581 node test-channel-issue-1111-e2e.js 2>&1 | tee -a e2e-output.txt BASE_URL=http://localhost:13581 node test-channels-client-state-152-e2e.js 2>&1 | tee -a e2e-output.txt + CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-channels-154-155-e2e.js 2>&1 | tee -a e2e-output.txt BASE_URL=http://localhost:13581 node test-map-modal-fluid-e2e.js 2>&1 | tee -a e2e-output.txt CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-map-nodes-pagination-e2e.js 2>&1 | tee -a e2e-output.txt CHROMIUM_REQUIRE=1 BASE_URL=http://localhost:13581 node test-issue-123-map-lifecycle-e2e.js 2>&1 | tee -a e2e-output.txt diff --git a/public/channels.js b/public/channels.js index 2252b2257..a0d45b2b1 100644 --- a/public/channels.js +++ b/public/channels.js @@ -1999,7 +1999,19 @@ if (panel) panel.remove(); } - async function loadChannels(silent) { + // #154: overlapping loadChannels() calls (region changes, the + // show-encrypted toggle, approval, init()) can answer out of order. Only + // the newest request renders; an older response is dropped without a + // render or a reconcile. Same idea as messageRequestId above. + let channelsRequestId = 0; + let latestChannelsLoad = null; + + function loadChannels(silent) { + latestChannelsLoad = loadChannelsFor(++channelsRequestId, silent); + return latestChannelsLoad; + } + + async function loadChannelsFor(requestId, silent) { // #152: WS activity stamped after this point is newer than the snapshot. const seqAtRequestStart = wsActivitySeq; try { @@ -2010,6 +2022,10 @@ if (showEnc) params.push('includeEncrypted=true'); const qs = params.length ? '?' + params.join('&') : ''; const data = await api('/channels' + qs, { ttl: CLIENT_TTL.channels }); + // #154: a newer request owns the list. Resolve when it is done, so a + // caller's follow-up (init()'s deep link, the region handler) sees the + // list that actually renders. + if (requestId !== channelsRequestId) return latestChannelsLoad; const prevChannels = channels; // Copies: api() hands the same cached objects back on a TTL hit, and // mergeUserChannels() below mutates rows. @@ -2032,6 +2048,7 @@ renderChannelList(); reconcileSelectionAfterChannelRefresh(); } catch (e) { + if (requestId !== channelsRequestId) return latestChannelsLoad; if (!silent) { const el = document.getElementById('chList'); if (el) el.innerHTML = `
Failed to load channels
`; @@ -2062,6 +2079,13 @@ return 'Channel ' + (typeof formatHashHex === 'function' ? formatHashHex(ch.hash) : ch.hash); } + // Unread badge for a channel row, desktop and mobile (#1029, #155). + function renderUnreadBadge(ch) { + if (!(ch.unread && ch.unread > 0)) return ''; + const count = escapeHtml(String(ch.unread)); + return '' + (ch.unread > 99 ? '99+' : count) + ''; + } + // #1034 PR1: render a single channel row (used by all sidebar sections). function renderChannelRow(ch) { const isEncrypted = ch.encrypted === true; @@ -2127,15 +2151,13 @@ : ''; const userBadge = isUserAdded ? ' ' : ''; const sharedBadge = isShared ? ' Shared' : ''; - const unreadBadge = (ch.unread && ch.unread > 0) - ? ' ' + (ch.unread > 99 ? '99+' : ch.unread) + '' - : ''; + const unreadBadge = renderUnreadBadge(ch); return `