From cbd79a48af0723d428f9dffbfb1eac95f2837158 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 13:24:44 +0300 Subject: [PATCH 01/15] fix userIsMuted --- src/components/status/status.js | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/status/status.js b/src/components/status/status.js index 387146db1..a53812da1 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -141,6 +141,7 @@ const Status = { return this.statusoid ?? useStatusesStore().allStatuses.get(this.statusId) }, repeatedStatus() { + if (this.status.retweeted_status === undefined) return undefined return useStatusesStore().allStatuses.get(this.status.retweeted_status.id) }, repeater() { @@ -198,7 +199,7 @@ const Status = { } }, isRepeat() { - return !!this.status.retweeted_status + return !!this.repeatedStatus }, repeaterName() { return this.status.user.name || this.status.user.screen_name_ui @@ -313,19 +314,19 @@ const Status = { return !this.unmuted && !this.shouldNotMute && this.muteReasons.length > 0 }, userIsMuted() { - if (this.status.user.id === this.currentUser?.id) return false - const { reblog } = this.status - const relationship = useUsersStore().relationship(this.status.user.id) - const relationshipReblog = - reblog && useUsersStore().relationship(reblog.user.id) + if (!this.currentUser) return false + if (this.user === this.currentUser) return false + if (this.repeater === this.currentUser) return false + const relationship = useUsersStore().relationship(this.user.id) + const relationshipRepeat = useUsersStore().relationship(this.repeater?.id) return ( - (status.muted && !status.thread_muted) || + (this.status.muted && !this.status.thread_muted) || // Reprööt of a muted post according to BE - (reblog?.muted && !reblog.thread_muted) || + (this.repeatedStatus?.muted && !this.repeatedStatus.thread_muted) || // Muted user relationship.muting || // Muted user of a reprööt - relationshipReblog?.muting + relationshipRepeat?.muting ) }, shouldNotMute() { From 946682b3b43f51deae7c77bd04e17e5460762995 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 13:57:45 +0300 Subject: [PATCH 02/15] fix reporting modal --- src/components/list/list.js | 4 ++-- .../user_reporting_modal.js | 2 +- .../user_reporting_modal.vue | 5 ++-- src/stores/reports.js | 24 +++++++------------ src/stores/statuses.js | 18 ++++++++------ 5 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/components/list/list.js b/src/components/list/list.js index 56d3e2ec6..6a1f77ac0 100644 --- a/src/components/list/list.js +++ b/src/components/list/list.js @@ -21,8 +21,8 @@ const List = { default: () => '', }, preSelect: { - type: Array, - default: [], + type: Set, + default: new Set(), }, nonInteractive: { type: Boolean, diff --git a/src/components/user_reporting_modal/user_reporting_modal.js b/src/components/user_reporting_modal/user_reporting_modal.js index 232305415..1ae3f9b76 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.js +++ b/src/components/user_reporting_modal/user_reporting_modal.js @@ -56,7 +56,7 @@ const UserReportingModal = { // Reset state this.comment = '' this.forward = false - this.statusIdsToReport = new Set(this.reportModal.preTickedIds) + this.statusIdsToReport = new Set(this.reportModal.preTickedIds) // cloning this.processing = false this.error = false }, diff --git a/src/components/user_reporting_modal/user_reporting_modal.vue b/src/components/user_reporting_modal/user_reporting_modal.vue index a028ebeb6..7e58516c3 100644 --- a/src/components/user_reporting_modal/user_reporting_modal.vue +++ b/src/components/user_reporting_modal/user_reporting_modal.vue @@ -52,8 +52,9 @@
@@ -61,7 +62,7 @@ diff --git a/src/stores/reports.js b/src/stores/reports.js index 1c4d89bca..2d9819442 100644 --- a/src/stores/reports.js +++ b/src/stores/reports.js @@ -1,4 +1,3 @@ -import { filter } from 'lodash' import { defineStore } from 'pinia' import { useInterfaceStore } from 'src/stores/interface.js' @@ -11,28 +10,23 @@ export const useReportsStore = defineStore('reports', { state: () => ({ reportModal: { userId: null, - statuses: [], - preTickedIds: [], + statusIds: new Set(), + preTickedIds: new Set(), activated: false, }, reports: {}, }), actions: { openUserReportingModal({ userId, statusIds = [] }) { - const preTickedStatuses = statusIds.map((id) => - useStatusesStore().allStatuses.get(id), - ) - const preTickedIds = statusIds - const statuses = preTickedStatuses.concat( - filter( - window.vuex.state.statuses.allStatuses, - (status) => - status.user.id === userId && !preTickedIds.includes(status.id), - ), - ) + const preTickedIds = new Set(statusIds) + // There shouldn't be a case where this is undefined + const userAllStatusesIds = useStatusesStore().statusesPerUser.get(userId) + // 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]) this.reportModal.userId = userId - this.reportModal.statuses = statuses + this.reportModal.statusIds = sortedIds this.reportModal.preTickedIds = preTickedIds this.reportModal.activated = true }, diff --git a/src/stores/statuses.js b/src/stores/statuses.js index 11a5b81bc..134fbacf2 100644 --- a/src/stores/statuses.js +++ b/src/stores/statuses.js @@ -30,6 +30,7 @@ import { export const defaultState = () => ({ allStatuses: new Map(), + statusesPerUser: new Map(), timestamps: new WeakMap(), scrobblesNextFetch: {}, conversations: new Map(), @@ -83,6 +84,12 @@ export const useStatusesStore = defineStore('statuses', { // in case of likes (which are not statuses) it should return null const addStatus = (data) => { const [status] = this.mergeOrAdd(this.allStatuses, data, timestamp) + let userSet = this.statusesPerUser.get(status.user.id) + if (userSet === undefined) { + userSet = new Set() + this.statusesPerUser.set(status.user.id, userSet) + } + userSet.add(status.id) // Add to conversation const conversations = this.conversations @@ -527,14 +534,11 @@ export const useStatusesStore = defineStore('statuses', { // For when blocking a user wipeUserStatuses(userId) { - const removed = new Set() - this.allStatuses.forEach((status) => { - if (status.user.id === userId) { - this.allStatuses.delete(status.id) - - removed.add(status.id) - } + const removed = this.statusesPerUser.get(userId) + removed.forEach((statusId) => { + this.allStatuses.delete(statusId) }) + this.statusesPerUser.delete(userId) return removed }, }, From fdc0f39187bcdf5de276e202788f1c05715cf089 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 13:59:18 +0300 Subject: [PATCH 03/15] cleanup --- src/components/list/list.js | 2 +- src/stores/admin_settings.js | 8 +++----- test/unit/specs/components/chat_view.spec.js | 4 ---- test/unit/specs/stores/user_highlight.spec.js | 9 --------- 4 files changed, 4 insertions(+), 19 deletions(-) diff --git a/src/components/list/list.js b/src/components/list/list.js index 6a1f77ac0..8abf02fd6 100644 --- a/src/components/list/list.js +++ b/src/components/list/list.js @@ -48,7 +48,7 @@ const List = { data() { return { items: [], - selected: new Set(this.preSelect), + selected: new Set(this.preSelect), // clone loading: false, bottomedOut: true, error: null, diff --git a/src/stores/admin_settings.js b/src/stores/admin_settings.js index 70850e5f6..f3b845f61 100644 --- a/src/stores/admin_settings.js +++ b/src/stores/admin_settings.js @@ -430,11 +430,9 @@ export const useAdminSettingsStore = defineStore('adminSettings', { }) resultUserIds.data.forEach((userId) => { - window.vuex.dispatch( - 'markStatusesAsDeleted', - (status) => userId === status.user.id, - ) - // TODO when migrated to pinia, also remove user + useStatusesStore().wipeUserStatuses(status.user.id) + // Users are technically never deleted, just deactivated + // so there's no real need to delete them from store. }) return resultUserIds diff --git a/test/unit/specs/components/chat_view.spec.js b/test/unit/specs/components/chat_view.spec.js index d03809e38..ba06b6b4c 100644 --- a/test/unit/specs/components/chat_view.spec.js +++ b/test/unit/specs/components/chat_view.spec.js @@ -32,10 +32,6 @@ const global = { $store: { state: { api: {}, - users: {}, - statuses: { - allStatusesObject: {}, - }, }, }, $route: { diff --git a/test/unit/specs/stores/user_highlight.spec.js b/test/unit/specs/stores/user_highlight.spec.js index e97f8f382..865544035 100644 --- a/test/unit/specs/stores/user_highlight.spec.js +++ b/test/unit/specs/stores/user_highlight.spec.js @@ -9,15 +9,6 @@ import { describe('The UserHighlight store', () => { beforeEach(() => { setActivePinia(createPinia()) - window.vuex = { - state: { - users: { - currentUser: { - fqn: 'foo@bar.tld', - }, - }, - }, - } }) describe('mutations', () => { From 039870daf088bd7edc81ba0a994534e558c26f6d Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 14:13:47 +0300 Subject: [PATCH 04/15] edit history weirdness --- src/api/public.js | 5 ++++- .../status_action_buttons/buttons_definitions.js | 15 +-------------- .../entity_normalizer.service.js | 4 ---- 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/src/api/public.js b/src/api/public.js index ef799566d..839196b9e 100644 --- a/src/api/public.js +++ b/src/api/public.js @@ -184,7 +184,10 @@ export const fetchStatusHistory = ({ id, credentials }) => return { ...rest, data: [...data].reverse().map((item) => { - item.originalStatus = status + // History data is missing a lot of stuff present in original + // but we're really only missing the id for the timeago, the + // rest seem to render just fine. + item.id = id return parseStatus(item) }), } diff --git a/src/components/status_action_buttons/buttons_definitions.js b/src/components/status_action_buttons/buttons_definitions.js index 399fad395..762b5be6a 100644 --- a/src/components/status_action_buttons/buttons_definitions.js +++ b/src/components/status_action_buttons/buttons_definitions.js @@ -174,20 +174,7 @@ export const BUTTONS = [ ) }, action({ status }) { - const originalStatus = { ...status } - const stripFieldsList = [ - 'attachments', - 'created_at', - 'emojis', - 'text', - 'raw_html', - 'nsfw', - 'poll', - 'summary', - 'summary_raw_html', - ] - stripFieldsList.forEach((p) => delete originalStatus[p]) - useStatusHistoryStore().openModal(originalStatus.id) + useStatusHistoryStore().openModal(status.id) return Promise.resolve() }, }, diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 196af90a8..9add0b702 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -342,10 +342,6 @@ export const parseStatus = (data) => { output.favoritedBy = [] output.rebloggedBy = [] - if (Object.hasOwn(data, 'originalStatus')) { - Object.assign(output, data.originalStatus) - } - return output } From 2deb74a6cfc24e2398f396f69eee88d3834e8e10 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 14:18:00 +0300 Subject: [PATCH 05/15] favorites tl fixes --- src/components/user_profile/user_profile.vue | 1 - src/stores/fetchers/timeline_fetcher.js | 2 +- src/stores/timelines.js | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/user_profile/user_profile.vue b/src/components/user_profile/user_profile.vue index f60521ffa..9a987cd09 100644 --- a/src/components/user_profile/user_profile.vue +++ b/src/components/user_profile/user_profile.vue @@ -87,7 +87,6 @@ v-if="favoritesTabVisible" key="favorites" :label="$t('user_card.favorites')" - :disabled="favorites.visibleStatusIds.size === 0" :title="$t('user_card.favorites')" :timeline-ref="{ name: 'favorites', argument: userId }" :argument="isUs ? undefined : userId" diff --git a/src/stores/fetchers/timeline_fetcher.js b/src/stores/fetchers/timeline_fetcher.js index ac2611607..54250318f 100644 --- a/src/stores/fetchers/timeline_fetcher.js +++ b/src/stores/fetchers/timeline_fetcher.js @@ -77,7 +77,7 @@ const timelineFetcher = (timeline, argument, credentials) => { return { statuses, pagination } }) .catch((error) => { - if (error.statusCode === 403 && timeline === 'favorites') { + if (error.statusCode === 403 && timeline.name === 'favorites') { useInstanceCapabilitiesStore().pleromaPublicFavouritesAvailable = false return } diff --git a/src/stores/timelines.js b/src/stores/timelines.js index 99864b162..562bec21d 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -80,7 +80,7 @@ export const useTimelinesStore = defineStore('timelines', { if (timeline.persistent && !persistent) return if ( - timelineName === 'favourites' && + timelineName === 'favorites' && !useInstanceCapabilitiesStore().pleromaPublicFavouritesAvailable ) { console.warn("Instance doesn't support public favorites timeline") From feb7135cc56ed6b02d6ce88f61c8d0541d0cc366 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 14:22:40 +0300 Subject: [PATCH 06/15] conversations cleanup --- src/stores/statuses.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/stores/statuses.js b/src/stores/statuses.js index 134fbacf2..8a5938211 100644 --- a/src/stores/statuses.js +++ b/src/stores/statuses.js @@ -536,7 +536,15 @@ export const useStatusesStore = defineStore('statuses', { wipeUserStatuses(userId) { const removed = this.statusesPerUser.get(userId) removed.forEach((statusId) => { + const status = this.allStatuses.get(statusId) this.allStatuses.delete(statusId) + const conversationSet = this.conversations.get( + status.statusnet_conversation_id, + ) + conversationSet.delete(statusId) + if (conversationSet.size === 0) { + this.conversations.delete(status.statusnet_conversation_id) + } }) this.statusesPerUser.delete(userId) return removed From f60a5c404e2c1f4f11503730be275a5ca1e686fa Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 14:23:41 +0300 Subject: [PATCH 07/15] born to notify, forced to wipe --- src/stores/users.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/stores/users.js b/src/stores/users.js index 2a88e9295..f41f75aa2 100644 --- a/src/stores/users.js +++ b/src/stores/users.js @@ -561,6 +561,7 @@ export const useUsersStore = defineStore('users', { const ids = useStatusesStore().wipeUserStatuses(id) useTimelinesStore().wipeStatuses(ids) + useNotificationsStore().wipeStatuses(ids) }) }, blockUsers(data = []) { From 8d6a93aa665afbd38dad7552b404ec7cf242cc23 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 14:32:05 +0300 Subject: [PATCH 08/15] who coded who to follow panel!? --- .../who_to_follow_panel.js | 90 +++++++++---------- 1 file changed, 41 insertions(+), 49 deletions(-) diff --git a/src/components/who_to_follow_panel/who_to_follow_panel.js b/src/components/who_to_follow_panel/who_to_follow_panel.js index bd0eaf5e1..107ec42f7 100644 --- a/src/components/who_to_follow_panel/who_to_follow_panel.js +++ b/src/components/who_to_follow_panel/who_to_follow_panel.js @@ -1,58 +1,20 @@ import { shuffle } from 'lodash' import { useInstanceStore } from 'src/stores/instance.js' -import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useOAuthStore } from 'src/stores/oauth.js' import { useUsersStore } from 'src/stores/users.js' import { fetchUser, suggestions } from 'src/api/public.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' -function showWhoToFollow(panel, reply) { - const shuffled = shuffle(reply) - - panel.usersToFollow.forEach((toFollow, index) => { - const user = shuffled[index] - const img = user.avatar || useInstanceStore().instanceIdentity.defaultAvatar - const name = user.acct - - toFollow.img = img - toFollow.name = name - - fetchUser({ - id: name, - credentials: useOAuthStore().token, - }).then((result) => { - const { data: externalUser } = result - useUsersStore().addNewUsers(result) - toFollow.id = externalUser.id - }) - }) -} - -function getWhoToFollow(panel) { - const credentials = useOAuthStore().token - if (credentials) { - panel.usersToFollow.forEach((toFollow) => { - toFollow.name = 'Loading...' - }) - suggestions({ credentials }).then(({ data: reply }) => { - showWhoToFollow(panel, reply) - }) - } -} - const WhoToFollowPanel = { data: () => ({ usersToFollow: [], }), computed: { - user: function () { + user() { return useUsersStore().currentUser.screen_name }, - suggestionsEnabled() { - return useInstanceCapabilitiesStore().suggestionsEnabled - }, }, methods: { userProfileLink(id, name) { @@ -62,23 +24,53 @@ const WhoToFollowPanel = { useInstanceStore().restrictedNicknames, ) }, - }, - watch: { - user: function () { - if (this.suggestionsEnabled) { - getWhoToFollow() - } + getWhoToFollow() { + this.usersToFollow.forEach((toFollow) => { + toFollow.name = 'Loading...' + }) + + suggestions({ credentials: useOAuthStore().token }).then( + ({ data: reply }) => { + this.showWhoToFollow(reply) + }, + ) + }, + showWhoToFollow(reply) { + const shuffled = shuffle(reply) + + this.usersToFollow.forEach((toFollow, index) => { + const user = shuffled[index] + const img = + user.avatar || useInstanceStore().instanceIdentity.defaultAvatar + const name = user.acct + + toFollow.img = img + toFollow.name = name + + fetchUser({ + id: name, + credentials: useOAuthStore().token, + }).then((result) => { + const { data: externalUser } = result + useUsersStore().addNewUsers(result) + toFollow.id = externalUser.id + }) + }) }, }, - mounted: function () { + watch: { + user() { + this.getWhoToFollow() + }, + }, + mounted() { this.usersToFollow = new Array(3).fill().map(() => ({ img: useInstanceStore().instanceIdentity.defaultAvatar, name: '', id: 0, })) - if (this.suggestionsEnabled) { - getWhoToFollow() - } + + this.getWhoToFollow() }, } From b485ccd82eb05d29db51fceb8c3a009991f50f06 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 14:33:11 +0300 Subject: [PATCH 09/15] detach socket --- src/components/chat_view/chat_view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/chat_view/chat_view.js b/src/components/chat_view/chat_view.js index 29b6c51a3..784af9ad3 100644 --- a/src/components/chat_view/chat_view.js +++ b/src/components/chat_view/chat_view.js @@ -127,6 +127,7 @@ const Chat = { if (this.testMode) return this.deactivate() + this.detachSocket() }, computed: { conversationId() { From ad5202776038ddfa1ec3feeefc36ed9b7888a0a6 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 14:38:00 +0300 Subject: [PATCH 10/15] push notifications fix --- src/lib/push_notifications_plugin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/push_notifications_plugin.js b/src/lib/push_notifications_plugin.js index 951eb2c4b..b3f6885d7 100644 --- a/src/lib/push_notifications_plugin.js +++ b/src/lib/push_notifications_plugin.js @@ -6,7 +6,7 @@ import { useUsersStore } from 'src/stores/users.js' export const piniaPushNotificationsPlugin = ({ store }) => { const validActions = { sync_config: new Set(['setPreference']), - interface: new Set(['setNotificationPermission', 'setLoginStatus']), + interface: new Set(['setNotificationPermission', 'onLogin', 'onLogout']), } if (!validActions[store.$id]) return // Not applicable to the store @@ -21,7 +21,7 @@ export const piniaPushNotificationsPlugin = ({ store }) => { useInterfaceStore().notificationPermission === 'granted' let permissionPresent = useInterfaceStore().notificationPermission !== undefined - let user = !!useUsersStore().currentUser + let user = useUsersStore().loggedIn if (store.$id === 'instance') { if (actionName === 'set' && args[0].path === 'vapidPublicKey') { From bb09d6a9d1017ac51838773d930dcb0cd1ebb6ba Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 15:02:11 +0300 Subject: [PATCH 11/15] chat updates --- src/services/chat_utils/chat_utils.js | 1 + src/stores/chats.js | 77 +++++++++------------------ 2 files changed, 26 insertions(+), 52 deletions(-) diff --git a/src/services/chat_utils/chat_utils.js b/src/services/chat_utils/chat_utils.js index a5c0d67cd..e99958110 100644 --- a/src/services/chat_utils/chat_utils.js +++ b/src/services/chat_utils/chat_utils.js @@ -4,6 +4,7 @@ import { useUsersStore } from 'src/stores/users.js' export const maybeShowChatNotification = (chat) => { if (!chat.lastMessage) return + if (chat.unread === 0) return if (useUsersStore().currentUser.id === chat.lastMessage.account_id) return const opts = { diff --git a/src/stores/chats.js b/src/stores/chats.js index 2f7d4f0b6..f795f9143 100644 --- a/src/stores/chats.js +++ b/src/stores/chats.js @@ -1,4 +1,4 @@ -import { find, omitBy, orderBy, sumBy } from 'lodash' +import { orderBy, sumBy } from 'lodash' import { defineStore } from 'pinia' import { maybeShowChatNotification } from '../services/chat_utils/chat_utils.js' @@ -10,28 +10,19 @@ import { useUsersStore } from 'src/stores/users.js' import { chats } from 'src/api/chats.js' -const emptyChatList = () => ({ - data: [], - idStore: {}, -}) - const defaultState = { - chatList: emptyChatList(), - chatListFetcher: null, -} - -const getChatById = (state, id) => { - return find(state.chatList.data, { id }) + data: new Map(), + fetcher: null, } export const useChatsStore = defineStore('chats', { state: () => ({ ...defaultState }), getters: { sortedChatList(state) { - return orderBy(state.chatList.data, ['updated_at'], ['desc']) + return orderBy([...state.data.values()], ['updated_at'], ['desc']) }, unreadChatsCount(state) { - return sumBy(state.chatList.data, 'unread') + return sumBy([...state.data.values()], 'unread') }, }, actions: { @@ -42,16 +33,19 @@ export const useChatsStore = defineStore('chats', { et, } - et.addEventListener('pleroma:chat_update', this.updateChat) + et.addEventListener('pleroma:chat_update', ({ data: { chatUpdate } }) => { + this.updateChat(chatUpdate) + }) useStreamingStore().addSubscriber(socket) }, startFetching() { - const fetcher = () => this.fetchChats() - this.setChatListFetcher(() => promiseInterval(fetcher, 5000)) + this.fetcher = () => promiseInterval(() => this.fetchChats(), 5000) + this.fetcher() }, stopFetching() { - this.setChatListFetcher(null) + this.fetcher?.stop() + this.fetcher = null }, async fetchChats() { this.addNewChats( @@ -60,16 +54,10 @@ export const useChatsStore = defineStore('chats', { }), ) }, - setChatListFetcher(fetcher) { - const prevFetcher = this.chatListFetcher - if (prevFetcher) { - prevFetcher.stop() - } - this.chatListFetcher = fetcher?.() - }, resetChats() { - this.chatList = emptyChatList() - this.setChatListFetcher(null) + this.data = new Map() + this.stopFetching() + this.startFetching() }, addNewChats(result) { useUsersStore().addNewUsers({ @@ -77,45 +65,30 @@ export const useChatsStore = defineStore('chats', { data: result.data.map((k) => k.account).filter(Boolean), }) - result.data.forEach((updatedChat) => { - const chat = getChatById(this, updatedChat.id) - - if (chat) { - chat.lastMessage = updatedChat.lastMessage - chat.unread = updatedChat.unread - chat.updated_at = updatedChat.updated_at - } else { - this.chatList.data.push(updatedChat) - this.chatList.idStore[updatedChat.id] = updatedChat - } - }) + // We do unshift in update so we reverse the chat list here + result.data.forEach(chat => this.updateChat(chat)) }, readChat(id) { - const chat = getChatById(this, id) + const chat = this.data.get(id) if (chat) { chat.unread = 0 + } else { + console.error(`Chat ${id} not found!`) } }, - updateChat({ data: { chatUpdate: updatedChat } }) { - const chat = getChatById(this, updatedChat.id) + updateChat(updatedChat) { + const chat = this.data.get(updatedChat.id) if (chat) { chat.lastMessage = updatedChat.lastMessage chat.unread = updatedChat.unread chat.updated_at = updatedChat.updated_at } else { - this.chatList.data.unshift(updatedChat) + this.data.set(updatedChat.id, updatedChat) } - maybeShowChatNotification(chat) - this.chatList.idStore[updatedChat.id] = updatedChat + maybeShowChatNotification(chat ?? updatedChat) }, deleteChat(id) { - this.chats.data = this.chats.data.filter( - (conversation) => conversation.last_status.id !== id, - ) - this.chats.idStore = omitBy( - this.chats.idStore, - (conversation) => conversation.last_status.id === id, - ) + this.data.delete(id) }, }, }) From 97e3f24bdf2529d72920319cfb4aedc7da40eb6c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 15:02:19 +0300 Subject: [PATCH 12/15] dang, ai was right --- src/stores/streaming.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/stores/streaming.js b/src/stores/streaming.js index 9233124b5..a360731bf 100644 --- a/src/stores/streaming.js +++ b/src/stores/streaming.js @@ -101,7 +101,7 @@ export const useStreamingStore = defineStore('streaming', { this.subscriptions.get(stream.name).delete(stream.argument) } - if (this.state === WSConnectionStatus.JOINED) { + if (stream && this.state === WSConnectionStatus.JOINED) { this.socket.unsubscribe(...this.getSubArgs(stream)) } }, @@ -131,7 +131,6 @@ export const useStreamingStore = defineStore('streaming', { }, getSubArgs(stream) { - if (stream === undefined) return [] const argumentKey = ARGUMENT_MAP[stream.name] const args = argumentKey ? { From c64f97ca5ed1d2a3e20dd736fffed599d95e0225 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 15:09:41 +0300 Subject: [PATCH 13/15] lint --- src/stores/chats.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/chats.js b/src/stores/chats.js index f795f9143..86ea310ce 100644 --- a/src/stores/chats.js +++ b/src/stores/chats.js @@ -66,7 +66,7 @@ export const useChatsStore = defineStore('chats', { }) // We do unshift in update so we reverse the chat list here - result.data.forEach(chat => this.updateChat(chat)) + result.data.forEach((chat) => this.updateChat(chat)) }, readChat(id) { const chat = this.data.get(id) From 5ff886ba943b7f436afe15ca6dc63865631146eb Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 15:15:35 +0300 Subject: [PATCH 14/15] fix tests --- test/unit/specs/stores/users.spec.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/unit/specs/stores/users.spec.js b/test/unit/specs/stores/users.spec.js index 7e196565d..285359c62 100644 --- a/test/unit/specs/stores/users.spec.js +++ b/test/unit/specs/stores/users.spec.js @@ -1124,6 +1124,12 @@ describe('Users store', () => { }, ) + vi.spyOn(useNotificationsStore(), 'wipeStatuses').mockImplementation( + async () => { + /* no-op */ + }, + ) + const store = useUsersStore() const { storeAction, apiUrl } = actionKeys(action) await store[storeAction](userId) @@ -1155,6 +1161,12 @@ describe('Users store', () => { }, ) + vi.spyOn(useNotificationsStore(), 'wipeStatuses').mockImplementation( + async () => { + /* no-op */ + }, + ) + const store = useUsersStore() const { storeAction, apiUrl } = actionKeys(action) await store[storeAction](userId, 20) From ae127a8d5184e92b7f0662c13b512b1f60c01f76 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Wed, 26 Aug 2026 15:22:50 +0300 Subject: [PATCH 15/15] comment --- src/components/chat_view/chat_view.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/chat_view/chat_view.js b/src/components/chat_view/chat_view.js index 784af9ad3..75ab21dd5 100644 --- a/src/components/chat_view/chat_view.js +++ b/src/components/chat_view/chat_view.js @@ -457,10 +457,16 @@ const Chat = { // Sanity check if (!this.isConversation && message.chat_id !== this.chat.id) { + // This is spammy, we get chat updates from a global chat update + // handler, which naturally receives updates for ALL chats. + // There is no way to subscribe to specific chat updates and listen + // to that in the API. + /* console.warn( `Chat message doesn't belong to current chat (id: ${this.chat.id})!!`, message, ) + */ return }