From 82d6b6638638a07b1c0583d807787743b682cb74 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 17:39:37 +0300 Subject: [PATCH 01/12] fix plugin early exit --- src/lib/push_notifications_plugin.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/push_notifications_plugin.js b/src/lib/push_notifications_plugin.js index b3f6885d7..ad546922d 100644 --- a/src/lib/push_notifications_plugin.js +++ b/src/lib/push_notifications_plugin.js @@ -37,9 +37,7 @@ export const piniaPushNotificationsPlugin = ({ store }) => { if (store.$id === 'interface') { if (actionName === 'setNotificationPermission') { permissionGranted = args[0] === 'granted' - } else if (actionName === 'setLoginStatus') { - user = args[0] - } else { + } else if (actionName !== 'onLogin' && actionName !== 'onLogout') { return } } else if (store.$id === 'sync_config') { From ae36bcbb4ef1b4e5243b5fbc1780f9af098bdc7f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 17:40:34 +0300 Subject: [PATCH 02/12] fix admin store --- src/stores/admin_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index f3b845f61..1e47cce2e 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -430,7 +430,7 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) resultUserIds.data.forEach((userId) => { - useStatusesStore().wipeUserStatuses(status.user.id) + useStatusesStore().wipeUserStatuses(userId) // Users are technically never deleted, just deactivated // so there's no real need to delete them from store. }) From d9f03f61aebda0c141ad85cad6ebca67ac67c8b4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 17:43:48 +0300 Subject: [PATCH 03/12] fix chats fetcher --- src/stores/chats.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stores/chats.js b/src/stores/chats.js index 86ea310ce..1cf4f4a3d 100644 --- a/src/stores/chats.js +++ b/src/stores/chats.js @@ -40,8 +40,8 @@ export const useChatsStore = defineStore('chats', { useStreamingStore().addSubscriber(socket) }, startFetching() { - this.fetcher = () => promiseInterval(() => this.fetchChats(), 5000) - this.fetcher() + this.fetcher = promiseInterval(() => this.fetchChats(), 5000) + this.fetchChats() }, stopFetching() { this.fetcher?.stop() From a4b02936acb40396c3255c5dd366485d158faf59 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 17:50:14 +0300 Subject: [PATCH 04/12] fix chat periodic notification --- src/services/chat_utils/chat_utils.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/services/chat_utils/chat_utils.js b/src/services/chat_utils/chat_utils.js index e99958110..643f7f59e 100644 --- a/src/services/chat_utils/chat_utils.js +++ b/src/services/chat_utils/chat_utils.js @@ -3,8 +3,13 @@ import { showDesktopNotification } from '../desktop_notification_utils/desktop_n import { useUsersStore } from 'src/stores/users.js' export const maybeShowChatNotification = (chat) => { + // No messages if (!chat.lastMessage) return + // Already shown notification for this message + if (chat.lastMessage.id === chat.lastNotifiedMessageId) return + // No unreads to display if (chat.unread === 0) return + // Don't notify on outgoing message (shouldn't happen with condition above) if (useUsersStore().currentUser.id === chat.lastMessage.account_id) return const opts = { @@ -19,6 +24,7 @@ export const maybeShowChatNotification = (chat) => { opts.image = chat.lastMessage.attachment.preview_url } + chat.lastNotifiedMessageId = chat.lastMessage.id showDesktopNotification(opts) } From f46575add2ca19c85d1ef12d8854238c1343dcad Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 18:05:15 +0300 Subject: [PATCH 05/12] better fix --- src/services/chat_utils/chat_utils.js | 3 --- src/stores/chats.js | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/services/chat_utils/chat_utils.js b/src/services/chat_utils/chat_utils.js index 643f7f59e..bbe74ce09 100644 --- a/src/services/chat_utils/chat_utils.js +++ b/src/services/chat_utils/chat_utils.js @@ -5,8 +5,6 @@ import { useUsersStore } from 'src/stores/users.js' export const maybeShowChatNotification = (chat) => { // No messages if (!chat.lastMessage) return - // Already shown notification for this message - if (chat.lastMessage.id === chat.lastNotifiedMessageId) return // No unreads to display if (chat.unread === 0) return // Don't notify on outgoing message (shouldn't happen with condition above) @@ -24,7 +22,6 @@ export const maybeShowChatNotification = (chat) => { opts.image = chat.lastMessage.attachment.preview_url } - chat.lastNotifiedMessageId = chat.lastMessage.id showDesktopNotification(opts) } diff --git a/src/stores/chats.js b/src/stores/chats.js index 1cf4f4a3d..b30c1c42c 100644 --- a/src/stores/chats.js +++ b/src/stores/chats.js @@ -79,13 +79,15 @@ export const useChatsStore = defineStore('chats', { updateChat(updatedChat) { const chat = this.data.get(updatedChat.id) if (chat) { + const isNewMessage = chat.lastMessage !== updatedChat.lastMessage chat.lastMessage = updatedChat.lastMessage chat.unread = updatedChat.unread chat.updated_at = updatedChat.updated_at + if (!isNewMessage) return } else { this.data.set(updatedChat.id, updatedChat) } - maybeShowChatNotification(chat ?? updatedChat) + maybeShowChatNotification(chat) }, deleteChat(id) { this.data.delete(id) From b112bf11c2ff239e51fd2b004dc7d8a359de2a11 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 18:07:12 +0300 Subject: [PATCH 06/12] fix reply visibility timelines --- src/stores/fetchers/timeline_fetcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/fetchers/timeline_fetcher.js b/src/stores/fetchers/timeline_fetcher.js index 54250318f..5737befef 100644 --- a/src/stores/fetchers/timeline_fetcher.js +++ b/src/stores/fetchers/timeline_fetcher.js @@ -46,7 +46,7 @@ const timelineFetcher = (timeline, argument, credentials) => { } args.withMuted = !hideMutedPosts - if (loggedIn && REPLY_VISIBILITY_TIMELINES.has(timeline)) { + if (loggedIn && REPLY_VISIBILITY_TIMELINES.has(timeline.name)) { args.replyVisibility = replyVisibility } From a44b60dbc8e67c98d599de6649eefa7d58fac089 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 18:12:17 +0300 Subject: [PATCH 07/12] fix reports --- src/stores/reports.js | 5 +++-- src/stores/statuses.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/stores/reports.js b/src/stores/reports.js index 2d9819442..f502e9f43 100644 --- a/src/stores/reports.js +++ b/src/stores/reports.js @@ -19,8 +19,9 @@ export const useReportsStore = defineStore('reports', { actions: { openUserReportingModal({ userId, statusIds = [] }) { const preTickedIds = new Set(statusIds) - // There shouldn't be a case where this is undefined - const userAllStatusesIds = useStatusesStore().statusesPerUser.get(userId) + // There could be a case (i.e. user is only ever mentioned in someone else's post -> user popover) + // where user has no known posts + const userAllStatusesIds = useStatusesStore().statusesPerUser.get(userId) ?? new Set() // Set constructor should take care of duplicated IDs and order, // later duplicated IDs will be dropped in favor of earlier const sortedIds = new Set([...preTickedIds, ...userAllStatusesIds]) diff --git a/src/stores/statuses.js b/src/stores/statuses.js index 8a5938211..ceb51ceb6 100644 --- a/src/stores/statuses.js +++ b/src/stores/statuses.js @@ -534,7 +534,7 @@ export const useStatusesStore = defineStore('statuses', { // For when blocking a user wipeUserStatuses(userId) { - const removed = this.statusesPerUser.get(userId) + const removed = this.statusesPerUser.get(userId) ?? new Set() removed.forEach((statusId) => { const status = this.allStatuses.get(statusId) this.allStatuses.delete(statusId) From 4e1d6f704dd3ea4541bc1603a3acfd66908ca4c4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 18:24:22 +0300 Subject: [PATCH 08/12] even more login/logout woes! --- src/stores/chats.js | 2 -- src/stores/timelines.js | 8 ++++++++ src/stores/users.js | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/stores/chats.js b/src/stores/chats.js index b30c1c42c..b0373b0e5 100644 --- a/src/stores/chats.js +++ b/src/stores/chats.js @@ -56,8 +56,6 @@ export const useChatsStore = defineStore('chats', { }, resetChats() { this.data = new Map() - this.stopFetching() - this.startFetching() }, addNewChats(result) { useUsersStore().addNewUsers({ diff --git a/src/stores/timelines.js b/src/stores/timelines.js index 562bec21d..834c29f50 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -316,6 +316,14 @@ export const useTimelinesStore = defineStore('timelines', { reason, ) return + } else if (timeline.paused) { + console.debug( + '[Timelines] Deactivating paused timeline', + timelineName, + 'Reason:', + reason, + ) + timeline.fetching = false } else { timeline.fetcher.stopFetching() console.debug( diff --git a/src/stores/users.js b/src/stores/users.js index f41f75aa2..412fbfb33 100644 --- a/src/stores/users.js +++ b/src/stores/users.js @@ -746,6 +746,7 @@ export const useUsersStore = defineStore('users', { // Full reset on logout success useTimelinesStore().deactivateAll() useStatusesStore().resetStatuses() + useChatsStore().stopFetching() useChatsStore().resetChats() this.users = new Map() @@ -777,6 +778,7 @@ export const useUsersStore = defineStore('users', { useAnnouncementsStore().startFetching() useListsStore().startFetching() useBookmarkFoldersStore().startFetching() + useChatsStore().startFetching() store?.dispatch('startFetchingFollowRequests') }) .finally(() => { From 7c46ba462f0e18211d0ea2740e81d49f692e3984 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 18:35:10 +0300 Subject: [PATCH 09/12] documentation --- src/stores/notifications.js | 26 +++++++++++++++++++++++++- src/stores/timelines.js | 31 +++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/stores/notifications.js b/src/stores/notifications.js index 4f18ccc21..c69e9c164 100644 --- a/src/stores/notifications.js +++ b/src/stores/notifications.js @@ -22,17 +22,41 @@ import { import { isStatusNotification } from 'src/services/notification_utils/notification_utils_sw.js' export const defaultState = () => ({ + // Prevents desktop notification spam on startup desktopNotificationSilence: true, + + // Pagination maxId: '', minId: '', + + // Order data: [], + + // TODO: Implement! + // Useful for making notification as seen + // when interacting with status statusNotificationRelations: new WeakMap(), + + // ID to Object notification idStore: new Map(), - statusIdStore: new Set(), + + // Reference to WS subscriber socket: null, + + // Indicates whether notifications receive push updates streaming: false, + + // Indicates whether notifications are SUPPOSED to be fetching + // this is partiualrly useful for when pausing/resuming. I.e. + // whether we need to start fetching again if timeline was resumed. fetching: true, + + // Reference to fetcher, used for polling for new notifications + // and manually fetching old notifications fetcher: null, + + // Whether notifications fetcher has been paused - it stops fetching + // (but still receives pushes!) paused: false, }) diff --git a/src/stores/timelines.js b/src/stores/timelines.js index 834c29f50..bdd49d05c 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -8,18 +8,49 @@ import { TIMELINE_STREAM_MAP, useStreamingStore } from 'src/stores/streaming.js' const emptyTl = (name, argument = null) => { const result = { + // Name of the timeline. Useful for debugging and logging name, + + // Order of statuses, important for timelines that + // have different ordering, i.e. bookmarks and favorites order: [], + + // All statuses belonging to the timeline statusIds: new Set(), + + // Statuses shown to user visibleStatusIds: new Set(), + + // Number of statuses not shown yet newStatusCount: 0, + + // Pagination maxId: '', minId: '', + + // Indicates whether timeline receives push updates streaming: false, + + // Indicates whether timeline is SUPPOSED to be fetching + // this is partiualrly useful for when pausing/resuming + // timeline. I.e. whether we need to start fetching again + // if timeline was resumed. fetching: false, + + // Indicates that in recent poll update we've hit more than or + // equal to 20 statuses and most likely missed some statuses + // between polls reloadNeeded: false, + + // Reference to fetcher, used for polling for new statuses and + // manually fetching old statuses fetcher: null, + + // Reference to WS subscriber socket: null, + + // Whether the timeline has been paused - it stops fetching + // (but still receives pushes!) paused: false, } From 713eb828c8374707c4aadb40d4f07e4f9949db95 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 18:38:33 +0300 Subject: [PATCH 10/12] fix chats fetcher again --- src/stores/chats.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stores/chats.js b/src/stores/chats.js index b0373b0e5..6fcfbcd1e 100644 --- a/src/stores/chats.js +++ b/src/stores/chats.js @@ -81,11 +81,11 @@ export const useChatsStore = defineStore('chats', { chat.lastMessage = updatedChat.lastMessage chat.unread = updatedChat.unread chat.updated_at = updatedChat.updated_at - if (!isNewMessage) return + if (isNewMessage) maybeShowChatNotification(chat) } else { this.data.set(updatedChat.id, updatedChat) + maybeShowChatNotification(updatedChat) } - maybeShowChatNotification(chat) }, deleteChat(id) { this.data.delete(id) From 9676f95cf4808c3a9668eb56948c8347a260f44a Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 18:48:36 +0300 Subject: [PATCH 11/12] remove persist plugin from users --- src/stores/users.js | 6 ------ test/unit/specs/stores/users.spec.js | 2 -- 2 files changed, 8 deletions(-) diff --git a/src/stores/users.js b/src/stores/users.js index 412fbfb33..1c32ab679 100644 --- a/src/stores/users.js +++ b/src/stores/users.js @@ -50,7 +50,6 @@ import { promiseInterval } from 'src/services/promise_interval/promise_interval. export const useUsersStore = defineStore('users', { state: () => ({ loggingIn: false, - lastLoginName: null, currentUser: null, users: new Map(), usersByName: new Map(), @@ -616,7 +615,6 @@ export const useUsersStore = defineStore('users', { user.muteIds = new Set() user.domainMutes = new Set() - this.lastLoginName = user.screen_name useTimelinesStore().deactivateAll() useStatusesStore().resetStatuses() @@ -739,7 +737,6 @@ export const useUsersStore = defineStore('users', { oauth.clearToken() this.currentUser = null - this.lastLoginName = null useNotificationsStore().deactivate() @@ -787,7 +784,4 @@ export const useUsersStore = defineStore('users', { }) }, }, - persist: { - paths: ['lastLoginName'], - }, }) diff --git a/test/unit/specs/stores/users.spec.js b/test/unit/specs/stores/users.spec.js index 285359c62..2709d680a 100644 --- a/test/unit/specs/stores/users.spec.js +++ b/test/unit/specs/stores/users.spec.js @@ -679,7 +679,6 @@ describe('Users store', () => { expect(store.usersByName).to.have.length(1) expect(store.usersByURL).to.have.length(1) expect(store.relationships).to.have.length(0) - expect(store.lastLoginName).to.eql(userScreenName) spies.forEach((spy, index) => { expect(spy, `Spy ${index} has failed`).to.have.been.called }) @@ -783,7 +782,6 @@ describe('Users store', () => { expect(store.loggedIn).to.eql(true) await store.logout() expect(store.loggedIn).to.eql(false) - expect(store.lastLoginName).to.eql(null) expect(revokeApi).to.have.been.called expect(store.users).to.have.length(0) expect(store.usersByName).to.have.length(0) From 18bdc84200d0c941ab1f7936e225cbfe6037766b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 18:49:23 +0300 Subject: [PATCH 12/12] lint --- src/stores/reports.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/stores/reports.js b/src/stores/reports.js index f502e9f43..0b0e25380 100644 --- a/src/stores/reports.js +++ b/src/stores/reports.js @@ -21,7 +21,8 @@ export const useReportsStore = defineStore('reports', { const preTickedIds = new Set(statusIds) // There could be a case (i.e. user is only ever mentioned in someone else's post -> user popover) // where user has no known posts - const userAllStatusesIds = useStatusesStore().statusesPerUser.get(userId) ?? new Set() + const userAllStatusesIds = + useStatusesStore().statusesPerUser.get(userId) ?? new Set() // Set constructor should take care of duplicated IDs and order, // later duplicated IDs will be dropped in favor of earlier const sortedIds = new Set([...preTickedIds, ...userAllStatusesIds])