feat: load update conversations progressively (Closes #761)
CI / lint (pull_request) Successful in 1m38s
CI / build-release (pull_request) Successful in 5s
CI / release-candidate (pull_request) Has been skipped

This commit is contained in:
timmy committed 2026-08-13 18:25:21 +00:00
1 parent 7c86f28346
commit a9d586e689
8 files changed
+123 -33

No files matched your search

+10 -1
View File
@@ -761,8 +761,9 @@
}
async function fetchNotificationConversation(notificationId, page) {
const pageQuery = Number.isInteger(page) ? '&page=' + encodeURIComponent(page) : '';
const response = await fetch('api/v1/notifications/' + encodeURIComponent(notificationId) +
'/conversation?page=' + encodeURIComponent(page) + '&limit=20', {
'/conversation?limit=20' + pageQuery, {
headers: { Accept: 'application/json' },
});
const payload = await response.json().catch(() => ({}));
@@ -936,6 +937,7 @@
qs('#update-sheet-title').textContent = item.title || 'Unread update';
qs('#update-comments').textContent = '';
qs('#update-conversation-status').textContent = '';
qs('#retry-update-conversation').hidden = true;
qs('#load-older-update-comments').hidden = true;
qs('#update-subject-body').textContent = '';
qs('#update-subject-type').textContent = item.subject_type || 'Update';
@@ -975,6 +977,10 @@
qs('#retry-update-load').hidden = true;
},
onConversation: renderUpdateConversation,
onConversationStatus: message => {
qs('#update-conversation-status').textContent = message;
qs('#retry-update-conversation').hidden = !message.startsWith('Conversation temporarily unavailable.');
},
onItems: items => {
const readId = selectedUpdate?.notification_id;
lastMyWork = items;
@@ -5527,6 +5533,9 @@
qs('#retry-update-load').addEventListener('click', () => {
if (selectedUpdate) notificationReader.open(selectedUpdate, lastMyWork);
});
qs('#retry-update-conversation').addEventListener('click', () => {
notificationReader.retryConversation();
});
qs('#load-older-update-comments').addEventListener('click', async () => {
const button = qs('#load-older-update-comments');
const panel = qs('#update-sheet .update-sheet-panel');
+1
View File
@@ -763,6 +763,7 @@
<button class="update-new-activity-jump" id="jump-update-new-activity" type="button" hidden>Jump to new activity</button>
<button class="conversation-more" id="load-older-update-comments" type="button" hidden>Load older messages</button>
<div id="update-conversation-status" class="small" aria-live="assertive"></div>
<button class="update-retry" id="retry-update-conversation" type="button" hidden>Retry conversation</button>
<details>
<summary><h2>Subject context</h2></summary>
<div class="update-sheet-content muted markdown-content" id="update-subject-body"></div>
+31
View File
@@ -379,6 +379,7 @@ function createNotificationReader({
loadSaved = () => null,
loadConversation = null,
onConversation = () => {},
onConversationStatus = () => {},
createPager = typeof createConversationPager === 'function' ? createConversationPager : null,
getScope = () => '',
}) {
@@ -390,6 +391,29 @@ function createNotificationReader({
let prefetched = null;
let prefetchKey = '';
async function hydrateConversation(item, version) {
if (!loadConversation || !createPager || !item || offlineHydrated) return false;
onConversationStatus('Loading conversation…');
try {
const initial = await loadConversation(item.notification_id, null);
if (selected !== item || version !== loadVersion) return false;
conversationPager = createPager({
loadPage: page => loadConversation(item.notification_id, page),
});
const state = conversationPager.reset(initial);
onConversation(state);
onConversationStatus(state.comments.length + ' of ' +
Math.max(state.total || 0, state.comments.length) + ' messages loaded.');
return true;
} catch (_error) {
if (selected === item && version === loadVersion) {
conversationPager = null;
onConversationStatus('Conversation temporarily unavailable. Retry.');
}
return false;
}
}
function prefetch(item) {
if (!item) { prefetched = null; prefetchKey = ''; return false; }
const notificationId = item?.notification_id;
@@ -446,6 +470,9 @@ function createNotificationReader({
conversationPager = null;
}
onStatus('Update ready.');
if (!offlineHydrated && detail.conversation_available) {
void hydrateConversation(item, version);
}
return true;
} catch (_error) {
if (selected === item && version === loadVersion) {
@@ -458,6 +485,10 @@ function createNotificationReader({
return {
open,
prefetch,
retryConversation() {
if (!selected || offlineHydrated) return Promise.resolve(false);
return hydrateConversation(selected, loadVersion);
},
commentPager() {
return conversationPager;
},