From 4d5fcff5141b910e98a2bbf1b7a7c3a43761e5db Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 24 Aug 2026 20:57:21 +0300 Subject: [PATCH 01/17] remove remaining currentUser.credentials --- .../who_to_follow_panel/who_to_follow_panel.js | 8 ++++---- src/services/status_poster/status_poster.service.js | 10 +++++----- src/stores/instance.js | 3 ++- src/stores/interface.js | 2 +- 4 files changed, 12 insertions(+), 11 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 b87004e44..0f7f93556 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 @@ -30,8 +30,8 @@ function showWhoToFollow(panel, reply) { }) } -function getWhoToFollow(panel) { - const credentials = panel.$useUsersStore().currentUser.credentials +function getWhoToFollow() { + const credentials = useOAuthStore().token if (credentials) { panel.usersToFollow.forEach((toFollow) => { toFollow.name = 'Loading...' @@ -66,7 +66,7 @@ const WhoToFollowPanel = { watch: { user: function () { if (this.suggestionsEnabled) { - getWhoToFollow(this) + getWhoToFollow() } }, }, @@ -77,7 +77,7 @@ const WhoToFollowPanel = { id: 0, })) if (this.suggestionsEnabled) { - getWhoToFollow(this) + getWhoToFollow() } }, } diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 00b8699ac..4c8872ce9 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -2,7 +2,7 @@ import { map } from 'lodash' import { useStatusesStore } from 'src/stores/statuses.js' import { useTimelinesStore } from 'src/stores/timelines.js' -import { useUsersStore } from 'src/stores/users.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { editStatus as apiEditStatus, @@ -28,7 +28,7 @@ const postStatus = ({ const mediaIds = map(media, 'id') return apiPostStatus({ - credentials: useUsersStore().currentUser.credentials, + credentials: useOAuthStore().token status, spoilerText, visibility, @@ -71,7 +71,7 @@ const editStatus = ({ return apiEditStatus({ id: statusId, - credentials: useUsersStore().currentUser.credentials, + credentials: useOAuthStore().token status, spoilerText, sensitive, @@ -101,12 +101,12 @@ const editStatus = ({ } const uploadMedia = ({ store, formData }) => { - const credentials = useUsersStore().currentUser.credentials + const credentials = useOAuthStore().token return apiUploadMedia({ credentials, formData }).then(({ data }) => data) } const setMediaDescription = ({ store, id, description }) => { - const credentials = useUsersStore().currentUser.credentials + const credentials = useOAuthStore().token return apiSetMediaDescription({ credentials, id, description }).then( ({ data }) => data, ) diff --git a/src/stores/instance.js b/src/stores/instance.js index 123f0b819..12480e8c1 100644 --- a/src/stores/instance.js +++ b/src/stores/instance.js @@ -14,6 +14,7 @@ import { import { useInterfaceStore } from 'src/stores/interface.js' import { useUsersStore } from 'src/stores/users.js' +import { useOAuthStore } from 'src/stores/oauth.js' import { fetchKnownDomains } from 'src/api/public.js' @@ -213,7 +214,7 @@ export const useInstanceStore = defineStore('instance', { async getKnownDomains() { try { const { data } = await fetchKnownDomains({ - credentials: useUsersStore().currentUser.credentials, + credentials: useOAuthStore().token }) this.knownDomains = data } catch (e) { diff --git a/src/stores/interface.js b/src/stores/interface.js index 0eba6f158..5dd4fa8df 100644 --- a/src/stores/interface.js +++ b/src/stores/interface.js @@ -854,7 +854,7 @@ export const useInterfaceStore = defineStore('interface', { }, unregisterPushNotifications() { - const token = this.currentUser.credentials + const token = useOAuthStore().token unregisterPushNotifications(token) }, From 0a57e27cdc29ecdea55cf10eeb8b2dd12b5b1fb2 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 17:20:59 +0300 Subject: [PATCH 02/17] fix --- src/services/status_poster/status_poster.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/status_poster/status_poster.service.js b/src/services/status_poster/status_poster.service.js index 4c8872ce9..41038573d 100644 --- a/src/services/status_poster/status_poster.service.js +++ b/src/services/status_poster/status_poster.service.js @@ -28,7 +28,7 @@ const postStatus = ({ const mediaIds = map(media, 'id') return apiPostStatus({ - credentials: useOAuthStore().token + credentials: useOAuthStore().token, status, spoilerText, visibility, @@ -71,7 +71,7 @@ const editStatus = ({ return apiEditStatus({ id: statusId, - credentials: useOAuthStore().token + credentials: useOAuthStore().token, status, spoilerText, sensitive, From 24fb1300b509c7bc0091a987758d8055e96cf6f9 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 17:21:14 +0300 Subject: [PATCH 03/17] Reverse changes related to mentionlink avatar, out of scope and too complicated --- changelog.d/avatar_mentions.change | 1 - src/components/mention_link/mention_link.vue | 11 +++-------- src/components/user_avatar/user_avatar.js | 2 +- src/components/user_avatar/user_avatar.vue | 1 - test/unit/specs/components/rich_content.spec.js | 6 ------ 5 files changed, 4 insertions(+), 17 deletions(-) delete mode 100644 changelog.d/avatar_mentions.change diff --git a/changelog.d/avatar_mentions.change b/changelog.d/avatar_mentions.change deleted file mode 100644 index f75f02ea4..000000000 --- a/changelog.d/avatar_mentions.change +++ /dev/null @@ -1 +0,0 @@ -If user avatars next to mentions are enabled it will show empty placeholder avatar next to label while user is being fetched, to avoid jumps diff --git a/src/components/mention_link/mention_link.vue b/src/components/mention_link/mention_link.vue index 3973522b0..33f0d9db7 100644 --- a/src/components/mention_link/mention_link.vue +++ b/src/components/mention_link/mention_link.vue @@ -8,20 +8,15 @@ :href="url" class="original" target="_blank" - > - - - + v-html="content" + /> { '', '', '', - '', 'https://', '', 'lol.tld/', '', '', - '', '', '', '', @@ -420,25 +418,21 @@ describe('RichContent', () => { '', '', '', - '', 'https://', '', 'lol.tld/', '', '', - '', '', '', '', '', '', - '', 'https://', '', 'lol.tld/', '', '', - '', '', '', '', From 39e385bb484d49a723169f0ae437321ea5984af3 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 17:51:23 +0300 Subject: [PATCH 04/17] fixes for virtual scrolling --- src/components/conversation/conversation.js | 6 +++--- src/components/conversation/conversation.vue | 3 +-- src/components/status/status.js | 11 ++++++----- src/components/status/status.vue | 1 + 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 3202a79c9..8fd39aa1d 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -155,9 +155,9 @@ const conversation = { return this.otherRepliesButtonPosition === 'inside' }, suspendable() { - return this.unsuspendibleIds.size > 0 + return this.unsuspendibleIds.size === 0 }, - hideStatus() { + hide() { return this.virtualHidden && this.suspendable }, originalStatusId() { @@ -365,7 +365,6 @@ const conversation = { return !!(this.expanded || this.isPage) }, hiddenStyle() { - if (this.isExpanded || !this.virtualHidden) return {} return { height: this.virtualHeight + 'px' } }, threadDisplayStatus() { @@ -621,6 +620,7 @@ const conversation = { } }, updateVirtualHeight() { + if (this.hide) return // no updates when not rendering this.$nextTick(() => { this.virtualHeight = this.$refs.body.getBoundingClientRect().height this.$emit('update:virtualHeight', { diff --git a/src/components/conversation/conversation.vue b/src/components/conversation/conversation.vue index cc1376978..94c4db188 100644 --- a/src/components/conversation/conversation.vue +++ b/src/components/conversation/conversation.vue @@ -1,7 +1,6 @@ From ac962a358980ae08e100191529260ff66e4c9a1e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 18:01:01 +0300 Subject: [PATCH 05/17] fix search --- src/components/search/search.vue | 2 +- src/stores/search.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/search/search.vue b/src/components/search/search.vue index 1f3982606..393b73fd1 100644 --- a/src/components/search/search.vue +++ b/src/components/search/search.vue @@ -119,7 +119,7 @@ > #{{ hashtag.name }} diff --git a/src/stores/search.js b/src/stores/search.js index ad85712c4..cd7862d23 100644 --- a/src/stores/search.js +++ b/src/stores/search.js @@ -19,7 +19,7 @@ export const useSearchStore = defineStore('search', { credentials: useOAuthStore().token, }) - const { accounts, statuses } = data + const { accounts, statuses, hashtags } = data useUsersStore().addNewUsers({ ...rest, @@ -36,6 +36,7 @@ export const useSearchStore = defineStore('search', { useStatusesStore().allStatuses.get(s.id), ) output.accounts = accounts.map((s) => useUsersStore().findUser(s.id)) + output.hashtags = hashtags ?? [] return output }, From a30176bfc2fdc0bd24ee97818802075e9ecf15a6 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 18:01:07 +0300 Subject: [PATCH 06/17] fix marking notification as seen --- src/stores/notifications.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/notifications.js b/src/stores/notifications.js index a12ce8133..eb9257602 100644 --- a/src/stores/notifications.js +++ b/src/stores/notifications.js @@ -223,7 +223,7 @@ export const useNotificationsStore = defineStore('notifications', { case 'follow_request': break default: - this.markSingleNotificationAsSeen({ id }) + this.markSingleNotificationAsSeen(id) } } }, From 16e89d0a90cfed0438298c41786e659a5c8a4b63 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 18:01:17 +0300 Subject: [PATCH 07/17] fix unwrapped refs --- src/components/notifications/notifications.js | 4 ++-- src/components/timeline/timeline.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/notifications/notifications.js b/src/components/notifications/notifications.js index e642fc554..2272c78af 100644 --- a/src/components/notifications/notifications.js +++ b/src/components/notifications/notifications.js @@ -121,10 +121,10 @@ const Notifications = { ) }, loading() { - return useNotificationsStore().fetcher.loading.value + return useNotificationsStore().fetcher.loading }, bottomedOut() { - return useNotificationsStore().fetcher.bottomedOut.value + return useNotificationsStore().fetcher.bottomedOut }, noHeading() { const { layoutType } = useInterfaceStore() diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 029693651..3c7e33060 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -239,7 +239,7 @@ const Timeline = { const bodyBRect = document.body.getBoundingClientRect() const height = Math.max(bodyBRect.height, -bodyBRect.y) if ( - !this.timeline.fetcher.loadingOlder.value && + !this.timeline.fetcher.loadingOlder && window.innerHeight + window.pageYOffset >= height - 750 ) { this.fetchOlderStatuses() From 0a46ce3d6d4441ca03bee9b66badee5765c752ac Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 18:03:42 +0300 Subject: [PATCH 08/17] don't reset socket for statuses store --- src/stores/statuses.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/stores/statuses.js b/src/stores/statuses.js index 8836a0a77..6a2ee1cc3 100644 --- a/src/stores/statuses.js +++ b/src/stores/statuses.js @@ -64,15 +64,9 @@ export const useStatusesStore = defineStore('statuses', { this.socket = socket }, resetStatuses() { - this.socket.et.removeEventListener('update', this.socket.handleUpdate) - this.socket.et.removeEventListener( - 'status.update', - this.socket.handleUpdate, - ) - this.socket.et.removeEventListener('delete', this.socket.handleDelete) - const emptyState = defaultState() Object.entries(emptyState).forEach(([key, value]) => { + if (key === 'socket') return this[key] = value }) }, From c051f5c01d3acb58bab28be714858deedc0b1995 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 18:05:52 +0300 Subject: [PATCH 09/17] improve merge logic --- src/stores/statuses.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/statuses.js b/src/stores/statuses.js index 6a2ee1cc3..0bcbc29f6 100644 --- a/src/stores/statuses.js +++ b/src/stores/statuses.js @@ -148,7 +148,7 @@ export const useStatusesStore = defineStore('statuses', { const newStatus = { ...old, - ...neu, + ...Object.fromEntries(Object.entries(neu).filter(([, v]) => v !== undefined)), user, } From b18c3733d66e064e50ba21770c1aeda031a965d4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 18:13:38 +0300 Subject: [PATCH 10/17] fix streaming --- src/stores/streaming.js | 1 + src/stores/timelines.js | 9 ++++----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/stores/streaming.js b/src/stores/streaming.js index 5129a571d..1b4d07292 100644 --- a/src/stores/streaming.js +++ b/src/stores/streaming.js @@ -131,6 +131,7 @@ export const useStreamingStore = defineStore('streaming', { }, getSubArgs(stream) { + if (stream === undefined) return undefined const argumentKey = ARGUMENT_MAP[stream.name] const args = argumentKey ? { diff --git a/src/stores/timelines.js b/src/stores/timelines.js index fe098587a..a6fdcc64f 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -105,10 +105,9 @@ export const useTimelinesStore = defineStore('timelines', { const openHandler = () => this.onStreamConnect(timelineName, argument) const closeHandler = () => this.onStreamDisconnect(timelineName, argument) - const messageHandler = - () => - ({ detail: message }) => - this.onStreamMessage(timelineName, argument, message) + const messageHandler = (message) => { + this.onStreamMessage(timelineName, argument, message) + } et.addEventListener('open', openHandler) et.addEventListener('close', closeHandler) @@ -231,7 +230,7 @@ export const useTimelinesStore = defineStore('timelines', { }, onStreamMessage(timeline, argument, event) { this.addStatusesToTimeline(timeline, argument, { - statuses: [event.data.status.id], + statuses: event.data.map(({ id }) => id), }) }, From 1fac5b006d9d9efd26420bcccdbb387360a525ef Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 19:36:24 +0300 Subject: [PATCH 11/17] improved login/logout process, eliminating 403 errors --- src/components/chat_view/chat_view.js | 7 ++- src/stores/chats.js | 5 ++- src/stores/interface.js | 5 ++- src/stores/notifications.js | 40 ++++++++++++++--- src/stores/statuses.js | 1 + src/stores/timelines.js | 65 +++++++++++++++++++++++++-- src/stores/users.js | 52 ++++++++++++++++++--- 7 files changed, 155 insertions(+), 20 deletions(-) diff --git a/src/components/chat_view/chat_view.js b/src/components/chat_view/chat_view.js index ab655cc39..44eaf57cf 100644 --- a/src/components/chat_view/chat_view.js +++ b/src/components/chat_view/chat_view.js @@ -268,14 +268,17 @@ const Chat = { }, attachSocket() { const et = new EventTarget() - const socket = { et } + const socket = { + name: 'chatview', + et, + } et.addEventListener('update', this.onStreamMessage) et.addEventListener('open', this.onStreamConnect) et.addEventListener('close', this.onStreamDisconnect) - useStreamingStore().addSubscriber(socket) this.socket = socket + useStreamingStore().addSubscriber(this.socket) }, detachSocket() { const { et } = this.socket diff --git a/src/stores/chats.js b/src/stores/chats.js index 9da01c76b..66e483b18 100644 --- a/src/stores/chats.js +++ b/src/stores/chats.js @@ -37,7 +37,10 @@ export const useChatsStore = defineStore('chats', { actions: { attachSocket() { const et = new EventTarget() - const socket = { et } + const socket = { + name: 'chats', + et, + } et.addEventListener('pleroma:chat_update', this.updateChat) diff --git a/src/stores/interface.js b/src/stores/interface.js index 5dd4fa8df..e87a0ecd9 100644 --- a/src/stores/interface.js +++ b/src/stores/interface.js @@ -96,7 +96,10 @@ export const useInterfaceStore = defineStore('interface', { actions: { attachSocket() { const et = new EventTarget() - const socket = { et } + const socket = { + name: 'interface', + et, + } et.addEventListener('open', this.onStreamConnect) et.addEventListener('close', this.onStreamDisconnect) diff --git a/src/stores/notifications.js b/src/stores/notifications.js index eb9257602..3405fb352 100644 --- a/src/stores/notifications.js +++ b/src/stores/notifications.js @@ -31,7 +31,9 @@ export const defaultState = () => ({ statusIdStore: new Set(), socket: null, streaming: false, + fetching: true, fetcher: null, + paused: false, }) export const useNotificationsStore = defineStore('notifications', { @@ -40,14 +42,29 @@ export const useNotificationsStore = defineStore('notifications', { // Init attachSocket() { const et = new EventTarget() - const socket = { et } + const socket = { + name: 'notifications', + et, + } et.addEventListener('notification', this.addNewNotifications) et.addEventListener('open', this.onStreamConnect) et.addEventListener('close', this.onStreamDisconnect) - useStreamingStore().addSubscriber(socket) this.socket = socket + useStreamingStore().addSubscriber(this.socket) + }, + pause() { + this.paused = true + if (this.fetcher && this.fetching) { + this.stopFetching('Notifications paused') + } + }, + resume() { + this.paused = false + if (this.fetcher && this.fetching) { + this.startFetching('Notifications resumed') + } }, activate() { this.attachSocket() @@ -62,7 +79,7 @@ export const useNotificationsStore = defineStore('notifications', { this.startFetching('Notifications activated') }, deactivate() { - if (!this.streaming) { + if (this.fetching) { this.stopFetching('Notifications deactivated') } @@ -77,6 +94,7 @@ export const useNotificationsStore = defineStore('notifications', { Object.keys(blankState).forEach((k) => { this[k] = blankState[k] }) + console.log('[Notifications] Deactivated', this.fetcher) }, // Poll & Push @@ -91,20 +109,30 @@ export const useNotificationsStore = defineStore('notifications', { this.startFetching('Socket disconnected') }, startFetching(reason) { + if (this.paused) { + console.debug( + '[Notificatiosn] NOT Starting notifications fetcher because it is paused', + 'Original Reason:', + reason, + ) + return + } console.debug( - '[Notifications] Starting fetching notifications', + '[Notifications] Starting notifications fetcher', 'Reason:', reason, ) this.fetcher.startFetching() + this.fetching = true }, stopFetching(reason) { + this.fetcher.stopFetching() + this.fetching = false console.debug( - '[Notifications] Stopped fetching notifications', + '[Notifications] Stopped notifications fetcher', 'Reason:', reason, ) - this.fetcher.stopFetching() }, // Updates diff --git a/src/stores/statuses.js b/src/stores/statuses.js index 0bcbc29f6..337cb399d 100644 --- a/src/stores/statuses.js +++ b/src/stores/statuses.js @@ -49,6 +49,7 @@ export const useStatusesStore = defineStore('statuses', { data.forEach((id) => this.setDeleted(id)) const socket = { + name: 'statuses', et, handlers: { handleUpdate, diff --git a/src/stores/timelines.js b/src/stores/timelines.js index a6fdcc64f..190396f48 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -16,9 +16,11 @@ const emptyTl = (name, argument = null) => { maxId: '', minId: '', streaming: false, + fetching: false, reloadNeeded: false, fetcher: null, socket: null, + paused: false, } const property = ARGUMENT_MAP[name] @@ -114,6 +116,7 @@ export const useTimelinesStore = defineStore('timelines', { et.addEventListener('update', messageHandler) timeline.socket = { + name: 'timelines', stream: { name: streamName, argument, @@ -132,7 +135,7 @@ export const useTimelinesStore = defineStore('timelines', { deactivate(timelineName, persistent) { const timeline = this[timelineName] if (timeline.persistent && !persistent) return - if (!timeline.streaming) { + if (timeline.fetching) { this.stopFetchingTimeline(timelineName, 'Timeline deactivation') } @@ -176,6 +179,48 @@ export const useTimelinesStore = defineStore('timelines', { }) }, + // Pause + pause(name) { + const timeline = this[name] + timeline.paused = true + console.debug( + '[Timelines] Pausing timeline', + name, + ) + if (timeline.fetcher && timeline.fetching) { + timeline.fetcher.stopFetching() + } + }, + resume(name) { + const timeline = this[name] + timeline.paused = false + console.debug( + '[Timelines] Resuming timeline', + name, + ) + if (timeline.fetcher && timeline.fetching) { + timeline.fetcher.startFetching() + } + }, + pauseAll() { + TIMELINES.forEach((name) => { + try { + this.pause(name) + } catch (e) { + console.error(`[Timelines] Failed to pause timeline ${name}:`, e) + } + }) + }, + resumeAll() { + TIMELINES.forEach((name) => { + try { + this.resume(name) + } catch (e) { + console.error(`[Timelines] Failed to pause timeline ${name}:`, e) + } + }) + }, + // Update stuff addStatusesToTimeline( timelineName, @@ -246,15 +291,28 @@ export const useTimelinesStore = defineStore('timelines', { this.startFetchingTimeline(timeline, argument, 'Socket disconnected') }, startFetchingTimeline(timelineName, argument, reason) { + const timeline = this[timelineName] + console.log('[Timelines]', toValue(timeline)) + if (timeline.paused) { + console.debug( + '[Timelines] NOT Starting timeline fetcher because it is paused', + timelineName, + argument, + 'Original Reason:', + reason, + ) + return + } + console.debug( - '[Timelines] Starting fetching timeline', + '[Timelines] Starting timeline fetcher', timelineName, argument, 'Reason:', reason, ) - const timeline = this[timelineName] timeline.fetcher.startFetching() + timeline.fetching = true }, stopFetchingTimeline(timelineName, reason) { const timeline = this[timelineName] @@ -274,6 +332,7 @@ export const useTimelinesStore = defineStore('timelines', { 'Reason:', reason, ) + timeline.fetching = false } }, diff --git a/src/stores/users.js b/src/stores/users.js index 10d9222c9..60ed893a5 100644 --- a/src/stores/users.js +++ b/src/stores/users.js @@ -2,6 +2,8 @@ import Cookies from 'js-cookie' import { last } from 'lodash' import { defineStore } from 'pinia' +import { WSConnectionStatus } from 'src/api/websocket.js' + import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders.js' import { useChatsStore } from 'src/stores/chats.js' @@ -708,6 +710,16 @@ export const useUsersStore = defineStore('users', { const store = window.vuex const oauth = useOAuthStore() + // Pause fetching + useNotificationsStore().pause() + useTimelinesStore().pauseAll() + + // Pause-less stores + useAnnouncementsStore().stopFetching() + useListsStore().stopFetching() + useBookmarkFoldersStore().stopFetching() + store?.dispatch('stopFetchingFollowRequests') + // NOTE: No need to verify the app still exists, because if it doesn't, // the token will be invalid too return oauth @@ -722,6 +734,8 @@ export const useUsersStore = defineStore('users', { return revokeToken(params) }) .then(() => { + oauth.clearToken() + this.currentUser = null this.lastLoginName = null @@ -729,21 +743,45 @@ export const useUsersStore = defineStore('users', { this.usersByName = new Map() this.usersByURL = new Map() this.relationships = new Map() + useNotificationsStore().deactivate() - useAnnouncementsStore().stopFetching() - useListsStore().stopFetching() - useBookmarkFoldersStore().stopFetching() - store?.dispatch('stopFetchingFollowRequests') useTimelinesStore().deactivateAll() + + // Full reset on logout success useStatusesStore().resetStatuses() - if (useMergedConfigStore().mergedConfig.useStreamingApi) { + useTimelinesStore().deactivateAll() + useChatsStore().resetChats() + + // Socket is most likely already closed by server + if ( + useMergedConfigStore().mergedConfig.useStreamingApi + && useStreamingStore().state !== WSConnectionStatus.CLOSED + ) { useStreamingStore().stopSocket() } - useChatsStore().resetChats() - oauth.clearToken() + Cookies.remove('__Host-pleroma_key', { path: '/' }) useInterfaceStore().onLogout() }) + .catch((e) => { + useInterfaceStore().pushGlobalNotice({ + messageKey: 'user.logout_failure', + messageArgs: { + error: e, + }, + level: 'error', + }) + console.error('Logout error!', e) + + useAnnouncementsStore().startFetching() + useListsStore().startFetching() + useBookmarkFoldersStore().startFetching() + store?.dispatch('startFetchingFollowRequests') + }) + .finally(() => { + useNotificationsStore().resume() + useTimelinesStore().resumeAll() + }) }, }, persist: { From a41aaf1228a47f4f5f3d1f71bffac481081f9679 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 20:15:37 +0300 Subject: [PATCH 12/17] more login/logout woes --- src/stores/users.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/stores/users.js b/src/stores/users.js index 60ed893a5..9b824bf04 100644 --- a/src/stores/users.js +++ b/src/stores/users.js @@ -617,12 +617,15 @@ export const useUsersStore = defineStore('users', { user.domainMutes = new Set() this.lastLoginName = user.screen_name - this.currentUser = user + useTimelinesStore().deactivateAll() + useStatusesStore().resetStatuses() this.users = new Map() this.usersByName = new Map() this.usersByURL = new Map() this.relationships = new Map() + this.currentUser = user + this.addNewUsers({ data: user, ...rest }) useInterfaceStore().onLogin() useSyncConfigStore() @@ -636,7 +639,6 @@ export const useUsersStore = defineStore('users', { }) useUserHighlightStore().initUserHighlight(user) - this.addNewUsers({ data: user, ...rest }) useEmojiStore().fetchEmoji() @@ -662,8 +664,8 @@ export const useUsersStore = defineStore('users', { } // DMs and Home - useNotificationsStore().activate() useTimelinesStore().activatePersistents() + useNotificationsStore().activate() if (useInstanceCapabilitiesStore().pleromaChatMessagesAvailable) { // Start fetching chats @@ -739,19 +741,18 @@ export const useUsersStore = defineStore('users', { this.currentUser = null this.lastLoginName = null + useNotificationsStore().deactivate() + + // Full reset on logout success + useTimelinesStore().deactivateAll() + useStatusesStore().resetStatuses() + useChatsStore().resetChats() + this.users = new Map() this.usersByName = new Map() this.usersByURL = new Map() this.relationships = new Map() - useNotificationsStore().deactivate() - useTimelinesStore().deactivateAll() - - // Full reset on logout success - useStatusesStore().resetStatuses() - useTimelinesStore().deactivateAll() - useChatsStore().resetChats() - // Socket is most likely already closed by server if ( useMergedConfigStore().mergedConfig.useStreamingApi From 6d958515561530662311105a1e84140ac2aff66b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 20:23:34 +0300 Subject: [PATCH 13/17] fix emoji reactions --- src/components/emoji_reactions/emoji_reactions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/emoji_reactions/emoji_reactions.js b/src/components/emoji_reactions/emoji_reactions.js index 3077ed212..6296e3c9a 100644 --- a/src/components/emoji_reactions/emoji_reactions.js +++ b/src/components/emoji_reactions/emoji_reactions.js @@ -62,7 +62,7 @@ const EmojiReactions = { }, async fetchEmojiReactionsIfMissing() { const hasNoAccounts = this.status.emoji_reactions.find((r) => !r.accounts) - if (!hasNoAccounts) { + if (hasNoAccounts) { return await useStatusesStore().fetchEmojiReactions(this.status.id) } }, From 283b91826fd5525365634a9711be01ab72411fda Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 20:32:15 +0300 Subject: [PATCH 14/17] attach chats socket --- src/boot/after_store.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/boot/after_store.js b/src/boot/after_store.js index f411235f3..3f2033ecc 100644 --- a/src/boot/after_store.js +++ b/src/boot/after_store.js @@ -29,6 +29,7 @@ import { import routes from './routes' import { useAuthFlowStore } from 'src/stores/auth_flow' +import { useChatsStore } from 'src/stores/chats.js' import { useEmojiStore } from 'src/stores/emoji.js' import { useI18nStore } from 'src/stores/i18n' import { useInstanceStore } from 'src/stores/instance.js' @@ -593,6 +594,7 @@ const afterStoreSetup = async ({ pinia, store, storageError, i18n }) => { useI18nStore().setI18n(i18n) // Global WS handlers + useChatsStore().attachSocket() useInterfaceStore().attachSocket() useStatusesStore().attachSocket() From f030328cd108a47a49a622b1b4c2b6fdbce8682f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 21:09:05 +0300 Subject: [PATCH 15/17] fix chats --- src/components/chat_view/chat_view.js | 19 ++++++++++++++----- src/stores/chats.js | 2 +- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/components/chat_view/chat_view.js b/src/components/chat_view/chat_view.js index 44eaf57cf..149bded2a 100644 --- a/src/components/chat_view/chat_view.js +++ b/src/components/chat_view/chat_view.js @@ -89,14 +89,15 @@ const Chat = { fetcher: null, socket: null, streaming: false, + fetching: true, errorLoadingChat: false, messageRetriers: {}, idempotencyKeyIndex: {}, } }, - created() { + async created() { if (this.testMode) return - this.activate() + await this.activate() this.attachSocket() }, mounted() { @@ -243,8 +244,8 @@ const Chat = { accountId: this.chatUserId, credentials: useOAuthStore().token, }) - useUsersStore().addNewUsers(result) const { data } = result + useUsersStore().addNewUsers({ ...result, data: data.account }) data.account = useUsersStore().findUser(data.account.id) this.chat = data } catch (e) { @@ -262,8 +263,8 @@ const Chat = { }, deactivate() { this.clear() - if (!this.streaming) { - this.stopFetching() + if (this.fetching) { + this.stopFetching('Chat deactivated') } }, attachSocket() { @@ -274,6 +275,7 @@ const Chat = { } et.addEventListener('update', this.onStreamMessage) + et.addEventListener('pleroma:chat_update', this.onChatUpdate) et.addEventListener('open', this.onStreamConnect) et.addEventListener('close', this.onStreamDisconnect) @@ -284,6 +286,7 @@ const Chat = { const { et } = this.socket et.removeEventListener('update', this.onStreamMessage) + et.removeEventListener('pleroma:chat_update', this.onChatUpdate) et.removeEventListener('open', this.onStreamConnect) et.removeEventListener('close', this.onStreamDisconnect) @@ -306,11 +309,13 @@ const Chat = { 5000, ) this.fetchChat({ isFirstFetch }) + this.fetching = true }, stopFetching(reason) { console.debug('[Chat View] Stopped fetching', 'Reason:', reason) this.fetcher.stop() this.fetcher = null + this.fetching = false }, // Actions @@ -440,6 +445,10 @@ const Chat = { ) this.addMessages({ messages }) }, + onChatUpdate({ data: { chatUpdate } }) { + const messages = [chatUpdate.lastMessage] + this.addMessages({ messages }) + }, addMessages({ messages: newMessages }) { for (let i = 0; i < newMessages.length; i++) { const message = newMessages[i] diff --git a/src/stores/chats.js b/src/stores/chats.js index 66e483b18..47e08b738 100644 --- a/src/stores/chats.js +++ b/src/stores/chats.js @@ -101,7 +101,7 @@ export const useChatsStore = defineStore('chats', { chat.unread = 0 } }, - updateChat({ chat: updatedChat }) { + updateChat({ data: { chatUpdate: updatedChat } }) { const chat = getChatById(this, updatedChat.id) if (chat) { chat.lastMessage = updatedChat.lastMessage From a659063fc29cfa57a08e0988d3a64de07d3df378 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 21:30:21 +0300 Subject: [PATCH 16/17] fix chat notifications --- src/services/chat_utils/chat_utils.js | 3 ++- src/stores/chats.js | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/services/chat_utils/chat_utils.js b/src/services/chat_utils/chat_utils.js index d99c17ed8..a5c0d67cd 100644 --- a/src/services/chat_utils/chat_utils.js +++ b/src/services/chat_utils/chat_utils.js @@ -11,13 +11,14 @@ export const maybeShowChatNotification = (chat) => { title: chat.account.name, icon: chat.account.profile_image_url, body: chat.lastMessage.content, + type: 'chatMention', } if (chat.lastMessage.attachment?.type === 'image') { opts.image = chat.lastMessage.attachment.preview_url } - showDesktopNotification(window.vuex.state, opts) + showDesktopNotification(opts) } export const buildFakeMessage = ({ diff --git a/src/stores/chats.js b/src/stores/chats.js index 47e08b738..82bb638dd 100644 --- a/src/stores/chats.js +++ b/src/stores/chats.js @@ -86,9 +86,6 @@ export const useChatsStore = defineStore('chats', { chat.lastMessage = updatedChat.lastMessage chat.unread = updatedChat.unread chat.updated_at = updatedChat.updated_at - if (isNewMessage && chat.unread) { - maybeShowChatNotification(chat) - } } else { this.chatList.data.push(updatedChat) this.chatList.idStore[updatedChat.id] = updatedChat @@ -107,10 +104,10 @@ export const useChatsStore = defineStore('chats', { chat.lastMessage = updatedChat.lastMessage chat.unread = updatedChat.unread chat.updated_at = updatedChat.updated_at - } - if (!chat) { + } else { this.chatList.data.unshift(updatedChat) } + maybeShowChatNotification(chat) this.chatList.idStore[updatedChat.id] = updatedChat }, deleteChat(id) { From 61f25e4f0f226be85d520870840c458425458595 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 25 Aug 2026 21:42:45 +0300 Subject: [PATCH 17/17] fix marking chat as read --- src/components/chat_view/chat_view.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/chat_view/chat_view.js b/src/components/chat_view/chat_view.js index 149bded2a..29b6c51a3 100644 --- a/src/components/chat_view/chat_view.js +++ b/src/components/chat_view/chat_view.js @@ -248,6 +248,7 @@ const Chat = { useUsersStore().addNewUsers({ ...result, data: data.account }) data.account = useUsersStore().findUser(data.account.id) this.chat = data + this.maxId = this.chat.lastMessage?.id } catch (e) { console.error('Error creating or getting a chat', e) this.errorLoadingChat = true @@ -255,10 +256,10 @@ const Chat = { } if (this.isConversation || this.chat) { + this.startFetching('Chat activated', true) this.$nextTick(() => { this.scrollDown({ forceRead: true }) }) - this.startFetching('Chat activated', true) } }, deactivate() {