From f2f15a0c02be1184ec979b76807e2b02ed696c9f Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 31 Aug 2026 19:33:55 +0300 Subject: [PATCH 01/18] option to allow overwriting timeline extremes with newer values --- src/stores/timelines.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/stores/timelines.js b/src/stores/timelines.js index 6dda2f23b..0974e1f00 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -414,7 +414,7 @@ export const useTimelinesStore = defineStore('timelines', { }, // Queues & Timeline manip - updateTimelineExtremes(timeline, pagination = {}) { + updateTimelineExtremes(timeline, pagination = {}, force = false) { // Can't use Math.min/max because it doesn't work with string (duh) const minNew = pagination.maxId ?? last(timeline.order) ?? '' const maxNew = pagination.minId ?? first(timeline.order) ?? '' @@ -422,10 +422,10 @@ export const useTimelinesStore = defineStore('timelines', { const newer = maxNew > timeline.maxId const older = minNew < timeline.minId - if (newer || timeline.maxId === '') { + if (force || newer || timeline.maxId === '') { timeline.maxId = maxNew } - if (older || timeline.minId === '') { + if (force || older || timeline.minId === '') { timeline.minId = minNew } @@ -442,7 +442,7 @@ export const useTimelinesStore = defineStore('timelines', { timeline.visibleStatusIds = new Set([ ...timeline.order.filter((id) => !timeline.ignoredIds.has(id)), ]) - this.updateTimelineExtremes(timeline) + this.updateTimelineExtremes(timeline, {}, true) }, syncOrder(timeline) { timeline.order = timeline.order.filter((id) => timeline.statusIds.has(id)) From 32c27b85b0623750115e3ed56e9777873a0a111b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 31 Aug 2026 20:02:21 +0300 Subject: [PATCH 02/18] fix creating a new chat --- src/components/chat_new/chat_new.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/chat_new/chat_new.js b/src/components/chat_new/chat_new.js index b683a6eb7..4f015db6a 100644 --- a/src/components/chat_new/chat_new.js +++ b/src/components/chat_new/chat_new.js @@ -51,7 +51,7 @@ const chatNew = { this.$emit('cancel') }, goToChat(user) { - this.$router.push({ name: 'chat', params: { recipient_id: user.id } }) + this.$router.push({ name: 'chat', params: { chatUserId: user.id } }) }, onInput() { this.search(this.query) From 8e63f189fcaae7462ec8fbd9a1db51bbab9414d4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Mon, 31 Aug 2026 20:07:34 +0300 Subject: [PATCH 03/18] cleanup --- src/components/timeline_menu/timeline_menu.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/timeline_menu/timeline_menu.js b/src/components/timeline_menu/timeline_menu.js index ea0d9a884..185a127c4 100644 --- a/src/components/timeline_menu/timeline_menu.js +++ b/src/components/timeline_menu/timeline_menu.js @@ -68,7 +68,6 @@ const TimelineMenu = { return '#' + this.$route.params.tag } if (route === 'lists-timeline') { - console.log(useListsStore, this.$route.params.id) return useListsStore().findListTitle(this.$route.params.id) } if (route === 'bookmark-folder') { From 590ca6fb6f7afd736e31beafbacce4071f9da467 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 11:09:22 +0300 Subject: [PATCH 04/18] =?UTF-8?q?fix=20repr=C3=B6=C3=B6ts=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stores/timelines.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/timelines.js b/src/stores/timelines.js index 0974e1f00..71ecc73cc 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -347,7 +347,7 @@ export const useTimelinesStore = defineStore('timelines', { // If it's the only reprööt then we've never seen post before if (knownRepeats.size === 1) return false // If we're working on oldest known reprööt then we've never seen it before - return first(knownRepeats) !== statusId + return knownRepeats.values().next().value !== statusId }, // Poll & Push From 725b2f5387b362e892b0dad16b24de0d2f4fb0a4 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 11:19:22 +0300 Subject: [PATCH 05/18] bottomedOut/loadOlder resets --- src/stores/fetchers/timeline_fetcher.js | 9 ++++++++- src/stores/timelines.js | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/stores/fetchers/timeline_fetcher.js b/src/stores/fetchers/timeline_fetcher.js index 494377b94..7fae0800d 100644 --- a/src/stores/fetchers/timeline_fetcher.js +++ b/src/stores/fetchers/timeline_fetcher.js @@ -52,7 +52,11 @@ const timelineFetcher = (timeline, argument, credentials) => { const numStatusesBeforeFetch = timeline.statusIds.size - if (older && bottomedOut.value) return + if (older && bottomedOut.value) { + loadingOlder.value = false + return + } + return fetchTimeline(args) .then(({ data, pagination, timestamp }) => { // No statuses for timeline, ever. @@ -135,6 +139,9 @@ const timelineFetcher = (timeline, argument, credentials) => { loadingOlder, loadingNewer, bottomedOut, + resetBottomedOut: () => { + bottomedOut.value = false + }, } } diff --git a/src/stores/timelines.js b/src/stores/timelines.js index 71ecc73cc..e47da2119 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -196,6 +196,7 @@ export const useTimelinesStore = defineStore('timelines', { timeline.maxId = '' timeline.minId = '' timeline.reloadNeeded = false + timeline.fetcher.resetBottomedOut() }, activatePersistents() { TIMELINES.forEach((name) => { @@ -443,6 +444,7 @@ export const useTimelinesStore = defineStore('timelines', { ...timeline.order.filter((id) => !timeline.ignoredIds.has(id)), ]) this.updateTimelineExtremes(timeline, {}, true) + timeline.fetcher.resetBottomedOut() }, syncOrder(timeline) { timeline.order = timeline.order.filter((id) => timeline.statusIds.has(id)) From b75bd5ae735a7392fbe6c90b34b270607c81a103 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 11:20:40 +0300 Subject: [PATCH 06/18] update extremes AFTER insert --- src/stores/timelines.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/stores/timelines.js b/src/stores/timelines.js index e47da2119..8d69139bc 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -279,10 +279,6 @@ export const useTimelinesStore = defineStore('timelines', { return } - if (!noIdUpdate) { - this.updateTimelineExtremes(timeline, pagination) - } - const filtered = statuses.filter((id) => !timeline.statusIds.has(id)) if (older) { timeline.order.push(...filtered) @@ -310,6 +306,10 @@ export const useTimelinesStore = defineStore('timelines', { } } }) + + if (!noIdUpdate) { + this.updateTimelineExtremes(timeline, pagination) + } }, onStreamMessage(timelineName, argument, event) { this.addStatusesToTimeline(timelineName, argument, { From 43ddbfd652484e1d0250dbd6fcf1aee6333204b0 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 11:39:33 +0300 Subject: [PATCH 07/18] guard against undefined pagination --- .../entity_normalizer/entity_normalizer.service.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 9add0b702..2e6caf787 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -385,10 +385,11 @@ export const parseLinkHeaderPagination = (linkHeader, opts = {}) => { const maxId = parsedLinkHeader.next?.max_id const minId = parsedLinkHeader.prev?.min_id - return { - maxId: flakeId ? maxId : Number.parseInt(maxId, 10), - minId: flakeId ? minId : Number.parseInt(minId, 10), - } + const result = {} + if (maxId !== undefined) result.maxId = flakeId ? maxId : Number.parseInt(maxId, 10) + if (minId !== undefined) result.minId = flakeId ? minId : Number.parseInt(minId, 10) + + return result } export const parseChat = (chat) => { From 64cc748a9ca5f9dc0027d8b2504ef9f032a92469 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 11:50:47 +0300 Subject: [PATCH 08/18] handle some edge cases for socket lifecycle --- src/components/settings_modal/tabs/general_tab.js | 3 +++ src/stores/streaming.js | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/components/settings_modal/tabs/general_tab.js b/src/components/settings_modal/tabs/general_tab.js index c090557cf..f875a9677 100644 --- a/src/components/settings_modal/tabs/general_tab.js +++ b/src/components/settings_modal/tabs/general_tab.js @@ -72,6 +72,9 @@ const GeneralTab = { useLocalConfigStore().set({ path, value }) }, toggleStreaming(value) { + // Streaming is not available for the unauthenticated + if (!useOAuthStore().token) return + if (value) { useStreamingStore().initSocket() } else { diff --git a/src/stores/streaming.js b/src/stores/streaming.js index aac1fa860..f891bc648 100644 --- a/src/stores/streaming.js +++ b/src/stores/streaming.js @@ -130,6 +130,9 @@ export const useStreamingStore = defineStore('streaming', { stopSocket() { this.socket.close() this.state = WSConnectionStatus.CLOSED + this.retrying = false + this.retryMultiplier = 1 + this.error = null }, getSubArgs(stream) { @@ -229,6 +232,8 @@ export const useStreamingStore = defineStore('streaming', { ) setTimeout(() => { + if (this.retrying) return // retry aborted (i.e. due to logout) + this.initSocket() }, retryTimeout(this.retryMultiplier)) From 5ea41244c1561e49027f90b07df31de3d1c7a40c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 11:51:54 +0300 Subject: [PATCH 09/18] guard --- src/stores/streaming.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/stores/streaming.js b/src/stores/streaming.js index f891bc648..5fec7a788 100644 --- a/src/stores/streaming.js +++ b/src/stores/streaming.js @@ -108,6 +108,8 @@ export const useStreamingStore = defineStore('streaming', { } }, initSocket(initial) { + if (this.socket) throw new Error('Socket already exists!') + this.state = initial ? WSConnectionStatus.STARTING_INITIAL : WSConnectionStatus.STARTING From b3754b26c43d359a121cec5610a2240054346456 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 13:07:22 +0300 Subject: [PATCH 10/18] better handling of user list popover --- src/components/user_list_popover/user_list_popover.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js index aa530e3f4..baae422f1 100644 --- a/src/components/user_list_popover/user_list_popover.js +++ b/src/components/user_list_popover/user_list_popover.js @@ -24,10 +24,14 @@ const UserListPopover = { UserAvatar, }, computed: { - usersCapped() { + users() { return [...this.userIds] - .slice(0, 16) .map((id) => useUsersStore().findUser(id)) + .filter(Boolean) + }, + usersCapped() { + return [...this.users] + .slice(0, 16) }, allowNonSquareEmoji() { return useMergedConfigStore().mergedConfig.nonSquareEmoji From abe6af133f66ed2f518a9cd146dea5efef58136e Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 13:11:52 +0300 Subject: [PATCH 11/18] guard for virtual height --- src/components/conversation/conversation.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/conversation/conversation.js b/src/components/conversation/conversation.js index 234c8308c..4eb29bcd6 100644 --- a/src/components/conversation/conversation.js +++ b/src/components/conversation/conversation.js @@ -622,6 +622,7 @@ const conversation = { }, updateVirtualHeight() { if (this.hide) return // no updates when not rendering + if (!this.status) return // not loaded yet this.$nextTick(() => { this.virtualHeight = this.$refs.body.getBoundingClientRect().height this.$emit('update:virtualHeight', { From 3589a5302ef6ef895e44663722929cb9155ec7e7 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 13:17:31 +0300 Subject: [PATCH 12/18] better clarification + thread_muting handling (fixed typo) --- src/components/status/status.js | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/components/status/status.js b/src/components/status/status.js index f05742106..814b3e3c8 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -136,13 +136,30 @@ const Status = { useScrobblesStore().getLatestScrobble(this.status.user.id) }, computed: { + // Whatever we're given to work with status() { return this.statusoid ?? useStatusesStore().allStatuses.get(this.statusId) }, + // Status repeated repeatedStatus() { if (this.status.retweeted_status === undefined) return undefined return useStatusesStore().allStatuses.get(this.status.retweeted_status.id) }, + // THE repeat + repeatStatus() { + if (this.isRepeat) { + return this.status + } else { + return null + } + }, + mainStatus() { + if (this.isRepeat) { + return this.repeatedStatus + } else { + return this.status + } + }, repeater() { return useUsersStore().findUser(this.status.user.id) }, @@ -151,7 +168,7 @@ const Status = { }, showReasonMutedThread() { return ( - (this.mainStatus.thread_muted || this.mainSatus.reblog?.thread_muted) && + (this.mainStatus.thread_muted || this.repeatStatus?.thread_muted) && !this.inConversation ) }, @@ -217,13 +234,6 @@ const Status = { this.repeater.screen_name, ) }, - mainStatus() { - if (this.isRepeat) { - return this.repeatedStatus - } else { - return this.status - } - }, loggedIn() { return !!this.currentUser }, From 3b856e94da9a08aac10968ef9fd71db56a4a2c1c Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 13:20:51 +0300 Subject: [PATCH 13/18] lint --- src/components/user_list_popover/user_list_popover.js | 3 +-- src/services/entity_normalizer/entity_normalizer.service.js | 6 ++++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/user_list_popover/user_list_popover.js b/src/components/user_list_popover/user_list_popover.js index baae422f1..f5a403be2 100644 --- a/src/components/user_list_popover/user_list_popover.js +++ b/src/components/user_list_popover/user_list_popover.js @@ -30,8 +30,7 @@ const UserListPopover = { .filter(Boolean) }, usersCapped() { - return [...this.users] - .slice(0, 16) + return [...this.users].slice(0, 16) }, allowNonSquareEmoji() { return useMergedConfigStore().mergedConfig.nonSquareEmoji diff --git a/src/services/entity_normalizer/entity_normalizer.service.js b/src/services/entity_normalizer/entity_normalizer.service.js index 2e6caf787..88e2fd0a5 100644 --- a/src/services/entity_normalizer/entity_normalizer.service.js +++ b/src/services/entity_normalizer/entity_normalizer.service.js @@ -386,8 +386,10 @@ export const parseLinkHeaderPagination = (linkHeader, opts = {}) => { const minId = parsedLinkHeader.prev?.min_id const result = {} - if (maxId !== undefined) result.maxId = flakeId ? maxId : Number.parseInt(maxId, 10) - if (minId !== undefined) result.minId = flakeId ? minId : Number.parseInt(minId, 10) + if (maxId !== undefined) + result.maxId = flakeId ? maxId : Number.parseInt(maxId, 10) + if (minId !== undefined) + result.minId = flakeId ? minId : Number.parseInt(minId, 10) return result } From 5788d3e8ec721eeb60def706cab21c310c7a31f9 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 13:46:30 +0300 Subject: [PATCH 14/18] P3 fixes --- .../quick_filter_settings.js | 4 +-- .../settings_modal/tabs/clutter_tab.js | 4 +-- .../settings_modal/tabs/filtering_tab.js | 4 +-- src/components/timeline/timeline.js | 7 ++++- src/i18n/en.json | 1 - src/stores/fetchers/notifications_fetcher.js | 6 ++-- src/stores/interface.js | 9 +----- src/stores/streaming.js | 1 + src/stores/timelines.js | 7 +++-- src/stores/users.js | 28 +++++++++++-------- 10 files changed, 38 insertions(+), 33 deletions(-) diff --git a/src/components/quick_filter_settings/quick_filter_settings.js b/src/components/quick_filter_settings/quick_filter_settings.js index a629266f8..db4b81af8 100644 --- a/src/components/quick_filter_settings/quick_filter_settings.js +++ b/src/components/quick_filter_settings/quick_filter_settings.js @@ -5,9 +5,9 @@ import Popover from 'src/components/popover/popover.vue' import { useInterfaceStore } from 'src/stores/interface.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useStatusesStore } from 'src/stores/statuses.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { useUsersStore } from 'src/stores/users.js' +import { useTimelinesStore } from 'src/stores/timelines.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons' @@ -28,7 +28,7 @@ const QuickFilterSettings = { path: 'replyVisibility', value: visibility, }) - useStatusesStore().requireReloadAll() + useTimelinesStore().requireReloadAll() }, openTab(tab) { useInterfaceStore().openSettingsModalTab(tab) diff --git a/src/components/settings_modal/tabs/clutter_tab.js b/src/components/settings_modal/tabs/clutter_tab.js index 18300231c..1b3a41285 100644 --- a/src/components/settings_modal/tabs/clutter_tab.js +++ b/src/components/settings_modal/tabs/clutter_tab.js @@ -11,7 +11,7 @@ import UnitSetting from '../helpers/unit_setting.vue' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' -import { useStatusesStore } from 'src/stores/statuses.js' +import { useTimelinesStore } from 'src/stores/timelines.js' const ClutterTab = { components: { @@ -36,7 +36,7 @@ const ClutterTab = { // Updating nested properties watch: { replyVisibility() { - useStatusesStore().requireReloadAll() + useTimelinesStore().requireReloadAll() }, }, } diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index fbc7b8902..1152aa697 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -14,7 +14,7 @@ import UnitSetting from '../helpers/unit_setting.vue' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useStatusesStore } from 'src/stores/statuses.js' +import { useTimelinesStore } from 'src/stores/timelines.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' import { @@ -266,7 +266,7 @@ const FilteringTab = { // Updating nested properties watch: { replyVisibility() { - useStatusesStore().requireReloadAll() + useTimelinesStore().requireReloadAll() }, muteFiltersObject() { this.muteFiltersDraftObject = cloneDeep( diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 85f3172dd..97558bc01 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -102,7 +102,12 @@ const Timeline = { } }, statusesToDisplay() { - if (!this.virtualScrollingEnabled) return this.visibleStatusIds + if (!this.virtualScrollingEnabled) { + return new Set( + this.filteredVisibleStatuses.map(({ id }) => id), + ) + } + const amount = this.timeline.visibleStatusIds.size const statusesPerSide = Math.ceil(Math.max(3, window.innerHeight / 80)) const min = Math.max(0, this.virtualScrollIndex - statusesPerSide) diff --git a/src/i18n/en.json b/src/i18n/en.json index 8c7379742..0e861fe77 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1634,7 +1634,6 @@ "no_statuses": "No statuses", "socket_reconnected": "Realtime connection established", "socket_disconnected": "Realtime connection unavaialable", - "socket_closed": "Realtime connection closed", "socket_broke": "Realtime connection lost: CloseEvent code {0}", "quick_view_settings": "Quick view settings", "quick_filter_settings": "Quick filter settings", diff --git a/src/stores/fetchers/notifications_fetcher.js b/src/stores/fetchers/notifications_fetcher.js index a31877072..cde444d91 100644 --- a/src/stores/fetchers/notifications_fetcher.js +++ b/src/stores/fetchers/notifications_fetcher.js @@ -36,7 +36,7 @@ const notificationsFetcher = (credentials) => { const notifications = response.data if (older && notifications.length === 0) bottomedOut.value = true - useNotificationsStore().addNewNotifications(response) + useNotificationsStore().addNewNotifications(response, older) } catch (error) { if ( error.statusCode === 400 && @@ -78,7 +78,7 @@ const notificationsFetcher = (credentials) => { args.timeline = 'notifications' if (older) { - if (timelineData.minId !== Number.POSITIVE_INFINITY) { + if (timelineData.minId !== '') { args.maxId = timelineData.minId } return await fetchNotifications({ args, older }) @@ -86,7 +86,7 @@ const notificationsFetcher = (credentials) => { // fetch new notifications if ( sinceId === undefined && - timelineData.maxId !== Number.POSITIVE_INFINITY + timelineData.maxId !== '' ) { args.sinceId = timelineData.maxId } else if (sinceId !== null) { diff --git a/src/stores/interface.js b/src/stores/interface.js index e87a0ecd9..f2b2d86c7 100644 --- a/src/stores/interface.js +++ b/src/stores/interface.js @@ -134,14 +134,7 @@ export const useInterfaceStore = defineStore('interface', { 1001, // Going away ]) const { code } = closeEvent.original - if (intendedCodes.has(code)) { - this.pushGlobalNotice({ - level: 'success', - messageKey: 'timeline.socket_closed', - messageArgs: [code], - timeout: 5000, - }) - } else { + if (!intendedCodes.has(code)) { this.pushGlobalNotice({ level: 'error', messageKey: 'timeline.socket_broke', diff --git a/src/stores/streaming.js b/src/stores/streaming.js index 5fec7a788..dae047f46 100644 --- a/src/stores/streaming.js +++ b/src/stores/streaming.js @@ -131,6 +131,7 @@ export const useStreamingStore = defineStore('streaming', { }, stopSocket() { this.socket.close() + this.socket = null this.state = WSConnectionStatus.CLOSED this.retrying = false this.retryMultiplier = 1 diff --git a/src/stores/timelines.js b/src/stores/timelines.js index 8d69139bc..801773874 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -81,6 +81,7 @@ export const ARGUMENT_MAP = { user: 'userId', userPinned: 'userId', media: 'userId', + favorites: 'userId', } const TIMELINES = new Set([ @@ -180,7 +181,7 @@ export const useTimelinesStore = defineStore('timelines', { timeline.socket.handlers timeline.socket.et.removeEventListener('open', openHandler) timeline.socket.et.removeEventListener('close', closeHandler) - timeline.socket.et.removeEventListener('message', messageHandler) + timeline.socket.et.removeEventListener('update', messageHandler) } this[timelineName] = emptyTl(timelineName) @@ -267,8 +268,6 @@ export const useTimelinesStore = defineStore('timelines', { if (statuses.length === 0) return const timeline = this[timelineName] - this.populateRepeats(timeline, repeats) - // This makes sure that user timeline won't get data meant for other // user. I.e. opening different user profiles makes request which could // return data late after user already viewing different user profile @@ -279,6 +278,8 @@ export const useTimelinesStore = defineStore('timelines', { return } + this.populateRepeats(timeline, repeats) + const filtered = statuses.filter((id) => !timeline.statusIds.has(id)) if (older) { timeline.order.push(...filtered) diff --git a/src/stores/users.js b/src/stores/users.js index 90c87c0f3..d3c8ad6cf 100644 --- a/src/stores/users.js +++ b/src/stores/users.js @@ -276,15 +276,21 @@ export const useUsersStore = defineStore('users', { const result = await promise - if (result) { - const { id, screen_name } = result + try { + if (result) { + const { id, screen_name } = result - // Save promise for future use - this.fetchesIds.set(id, promise) - this.fetchesNames.set(screen_name, promise) - return this.users.get(id) - } else { - return null + // Save promise for future use + this.fetchesIds.set(id, promise) + this.fetchesNames.set(screen_name, promise) + return this.users.get(id) + } else { + return null + } + } catch (e) { + console.error(`Failed fetching user ${identifier}`, e) + map.delete(identifier) + throw e } }, async fetchUser(id) { @@ -518,7 +524,7 @@ export const useUsersStore = defineStore('users', { /// Mute muteUser(id, expiresIn = 0) { - const predictedRelationship = this.relationships[id] || { id } + const predictedRelationship = this.relationships.get(id) || { id } predictedRelationship.muting = true this.updateUserRelationships({ optimism: true, @@ -537,7 +543,7 @@ export const useUsersStore = defineStore('users', { return Promise.all(data.map((d) => this.muteUser(d))) }, unmuteUser(id) { - const predictedRelationship = this.relationships[id] || { id } + const predictedRelationship = this.relationships.get(id) || { id } predictedRelationship.muting = false this.updateUserRelationships({ optimism: true, @@ -554,7 +560,7 @@ export const useUsersStore = defineStore('users', { /// Block blockUser(id, expiresIn = 0) { - const predictedRelationship = this.relationships[id] || { id } + const predictedRelationship = this.relationships.get(id) || { id } this.updateUserRelationships({ optimism: true, data: [predictedRelationship], From 39f7d522e3e4917186ae0106ef5e312676d7cafe Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 13:50:21 +0300 Subject: [PATCH 15/18] logged-out state fixes --- src/components/follow_card/follow_card.js | 2 +- src/components/settings_modal/tabs/appearance_tab.js | 4 ++-- src/components/settings_modal/tabs/appearance_tab.vue | 4 ++-- src/components/settings_modal/tabs/general_tab.js | 2 +- src/components/user_card/user_card.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/follow_card/follow_card.js b/src/components/follow_card/follow_card.js index 9a8b8746f..3d9d895f0 100644 --- a/src/components/follow_card/follow_card.js +++ b/src/components/follow_card/follow_card.js @@ -15,7 +15,7 @@ const FollowCard = { }, computed: { isMe() { - return useUsersStore().currentUser.id === this.user.id + return useUsersStore().currentUser?.id === this.user.id }, loggedIn() { return useUsersStore().currentUser diff --git a/src/components/settings_modal/tabs/appearance_tab.js b/src/components/settings_modal/tabs/appearance_tab.js index 68c498624..f3bcf19c0 100644 --- a/src/components/settings_modal/tabs/appearance_tab.js +++ b/src/components/settings_modal/tabs/appearance_tab.js @@ -221,7 +221,7 @@ const AppearanceTab = { }, computed: { isDefaultBackground() { - return !useUsersStore().currentUser.background_image + return !useUsersStore().currentUser?.background_image }, switchInProgress() { return useInterfaceStore().themeChangeInProgress @@ -283,7 +283,7 @@ const AppearanceTab = { instanceWallpaperUsed() { return ( useInstanceStore().instanceIdentity.background && - !useUsersStore().currentUser.background_image + !useUsersStore().currentUser?.background_image ) }, customThemeVersion() { diff --git a/src/components/settings_modal/tabs/appearance_tab.vue b/src/components/settings_modal/tabs/appearance_tab.vue index 1343a36d5..e01a2b803 100644 --- a/src/components/settings_modal/tabs/appearance_tab.vue +++ b/src/components/settings_modal/tabs/appearance_tab.vue @@ -162,9 +162,9 @@
Date: Tue, 1 Sep 2026 13:55:00 +0300 Subject: [PATCH 16/18] fix reloadNeeded --- src/stores/timelines.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/stores/timelines.js b/src/stores/timelines.js index 801773874..afee57ddd 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -454,8 +454,10 @@ export const useTimelinesStore = defineStore('timelines', { this[timeline].reloadNeeded = true }, requireReloadAll() { - Object.keys(this).forEach((timeline) => { - this[timeline].reloadNeeded = true + TIMELINES.forEach((timelineName) => { + const timeline = this[timelineName] + + timeline.reloadNeeded = true }) }, From 9a263692eafb859e90639efe8fbef39039b0f02b Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 13:56:08 +0300 Subject: [PATCH 17/18] lint --- .../quick_filter_settings/quick_filter_settings.js | 2 +- src/components/settings_modal/tabs/filtering_tab.js | 2 +- src/components/timeline/timeline.js | 4 +--- src/stores/fetchers/notifications_fetcher.js | 5 +---- 4 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/components/quick_filter_settings/quick_filter_settings.js b/src/components/quick_filter_settings/quick_filter_settings.js index db4b81af8..13e97452e 100644 --- a/src/components/quick_filter_settings/quick_filter_settings.js +++ b/src/components/quick_filter_settings/quick_filter_settings.js @@ -6,8 +6,8 @@ import { useInterfaceStore } from 'src/stores/interface.js' import { useLocalConfigStore } from 'src/stores/local_config.js' import { useMergedConfigStore } from 'src/stores/merged_config.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' -import { useUsersStore } from 'src/stores/users.js' import { useTimelinesStore } from 'src/stores/timelines.js' +import { useUsersStore } from 'src/stores/users.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faFilter, faFont, faWrench } from '@fortawesome/free-solid-svg-icons' diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index 1152aa697..601483106 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -14,8 +14,8 @@ import UnitSetting from '../helpers/unit_setting.vue' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface' import { useMergedConfigStore } from 'src/stores/merged_config.js' -import { useTimelinesStore } from 'src/stores/timelines.js' import { useSyncConfigStore } from 'src/stores/sync_config.js' +import { useTimelinesStore } from 'src/stores/timelines.js' import { newExporter, diff --git a/src/components/timeline/timeline.js b/src/components/timeline/timeline.js index 97558bc01..38ee99931 100644 --- a/src/components/timeline/timeline.js +++ b/src/components/timeline/timeline.js @@ -103,9 +103,7 @@ const Timeline = { }, statusesToDisplay() { if (!this.virtualScrollingEnabled) { - return new Set( - this.filteredVisibleStatuses.map(({ id }) => id), - ) + return new Set(this.filteredVisibleStatuses.map(({ id }) => id)) } const amount = this.timeline.visibleStatusIds.size diff --git a/src/stores/fetchers/notifications_fetcher.js b/src/stores/fetchers/notifications_fetcher.js index cde444d91..5403d59c9 100644 --- a/src/stores/fetchers/notifications_fetcher.js +++ b/src/stores/fetchers/notifications_fetcher.js @@ -84,10 +84,7 @@ const notificationsFetcher = (credentials) => { return await fetchNotifications({ args, older }) } else { // fetch new notifications - if ( - sinceId === undefined && - timelineData.maxId !== '' - ) { + if (sinceId === undefined && timelineData.maxId !== '') { args.sinceId = timelineData.maxId } else if (sinceId !== null) { args.sinceId = sinceId From 53498072e59c6d09e94b419cda925bb651cee845 Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Tue, 1 Sep 2026 15:31:33 +0300 Subject: [PATCH 18/18] fix repeat deduplication logic --- src/stores/timelines.js | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/src/stores/timelines.js b/src/stores/timelines.js index afee57ddd..86a2c617e 100644 --- a/src/stores/timelines.js +++ b/src/stores/timelines.js @@ -287,24 +287,30 @@ export const useTimelinesStore = defineStore('timelines', { timeline.order.unshift(...filtered) } + const newStatuses = new Set() + statuses.forEach((statusId) => { const isNew = !timeline.statusIds.has(statusId) timeline.statusIds.add(statusId) if (isNew) { - const seenBefore = this.checkSeenBefore(timeline, statusId) - if (!seenBefore) { - if (showImmediately) { - // Add it directly to the visibleStatuses, don't change - // newStatusCount - timeline.visibleStatusIds.add(statusId) - } else { - // Just change newStatuscount - timeline.newStatusCount += 1 - } + newStatuses.add(statusId) + } + }) + + newStatuses.forEach((statusId) => { + const seenBefore = this.checkSeenBefore(timeline, statusId) + if (!seenBefore) { + if (showImmediately) { + // Add it directly to the visibleStatuses, don't change + // newStatusCount + timeline.visibleStatusIds.add(statusId) } else { - timeline.ignoredIds.add(statusId) + // Just change newStatuscount + timeline.newStatusCount += 1 } + } else { + timeline.ignoredIds.add(statusId) } })