From a17fb57446f48961ddc00b246fbe71c681c09b3a Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 6 Jul 2026 12:06:27 +0200 Subject: [PATCH 01/18] Fix pinned post button width (#39724) --- .../mastodon/features/account_timeline/styles.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/javascript/mastodon/features/account_timeline/styles.module.scss b/app/javascript/mastodon/features/account_timeline/styles.module.scss index 9773861d22..bb90dbbb82 100644 --- a/app/javascript/mastodon/features/account_timeline/styles.module.scss +++ b/app/javascript/mastodon/features/account_timeline/styles.module.scss @@ -99,7 +99,7 @@ margin: 12px 16px; padding: 8px; transition: border-color 0.2s ease-in-out; - width: calc(100% - 48px); + width: calc(100% - 32px); &:hover, &:focus { From ea05e85eee5b5a2a12c068ef75c985d3bde29d61 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 7 Jul 2026 10:22:54 +0200 Subject: [PATCH 02/18] Fix relative times (#39742) --- app/javascript/mastodon/utils/time.test.ts | 8 +++++--- app/javascript/mastodon/utils/time.ts | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/app/javascript/mastodon/utils/time.test.ts b/app/javascript/mastodon/utils/time.test.ts index f1b206b424..add0c7d3cd 100644 --- a/app/javascript/mastodon/utils/time.test.ts +++ b/app/javascript/mastodon/utils/time.test.ts @@ -9,13 +9,15 @@ describe('relativeTimeParts', () => { // Past [-30 * SECOND, { value: -30, unit: 'second' }], - [-90 * SECOND, { value: -2, unit: 'minute' }], + [-60 * SECOND, { value: -1, unit: 'minute' }], + [-90 * SECOND, { value: -1, unit: 'minute' }], [-30 * MINUTE, { value: -30, unit: 'minute' }], - [-90 * MINUTE, { value: -2, unit: 'hour' }], + [-60 * MINUTE, { value: -1, unit: 'hour' }], + [-90 * MINUTE, { value: -1, unit: 'hour' }], [-5 * HOUR, { value: -5, unit: 'hour' }], [-24 * HOUR, { value: -1, unit: 'day' }], [-36 * HOUR, { value: -1, unit: 'day' }], - [-47 * HOUR, { value: -2, unit: 'day' }], + [-47 * HOUR, { value: -1, unit: 'day' }], [-3 * DAY, { value: -3, unit: 'day' }], // Future diff --git a/app/javascript/mastodon/utils/time.ts b/app/javascript/mastodon/utils/time.ts index c7ed115d24..c2bbdc747f 100644 --- a/app/javascript/mastodon/utils/time.ts +++ b/app/javascript/mastodon/utils/time.ts @@ -16,21 +16,29 @@ export function relativeTimeParts( ): { value: number; unit: TimeUnit; delta: number } { const delta = ts - now; const absDelta = Math.abs(delta); + const sign = Math.sign(delta); if (absDelta < MINUTE) { - return { value: Math.floor(delta / SECOND), unit: 'second', delta }; + return { + value: sign * Math.floor(absDelta / SECOND), + unit: 'second', + delta, + }; } if (absDelta < HOUR) { - return { value: Math.floor(delta / MINUTE), unit: 'minute', delta }; + return { + value: sign * Math.floor(absDelta / MINUTE), + unit: 'minute', + delta, + }; } if (absDelta < DAY) { - return { value: Math.floor(delta / HOUR), unit: 'hour', delta }; + return { value: sign * Math.floor(absDelta / HOUR), unit: 'hour', delta }; } - // Round instead of use floor as days are big enough that the value is usually off by a few hours. - return { value: Math.round(delta / DAY), unit: 'day', delta }; + return { value: sign * Math.floor(absDelta / DAY), unit: 'day', delta }; } export function isToday(ts: number, now = Date.now()): boolean { From f2e61fbd56540e9b30f617c3e0ba970983e66efc Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 13 Jul 2026 08:38:23 +0200 Subject: [PATCH 03/18] Fix error handling in `ActivityPub::ProcessFeaturedItemService` (#39787) --- .../process_featured_item_service.rb | 4 ++-- .../process_featured_item_service_spec.rb | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/services/activitypub/process_featured_item_service.rb b/app/services/activitypub/process_featured_item_service.rb index b9769cc037..fea25ca332 100644 --- a/app/services/activitypub/process_featured_item_service.rb +++ b/app/services/activitypub/process_featured_item_service.rb @@ -66,8 +66,8 @@ class ActivityPub::ProcessFeaturedItemService return false if local_actor_uri? return false if Account.exists?(uri: @actor_uri) - object_json = fetch_resource(@actor_uri, true) - (Array(object_json['type']) & ActivityPub::FetchRemoteActorService::SUPPORTED_TYPES).empty? + object_json = fetch_resource(@actor_uri, true, raise_on_error: :temporary) + object_json.nil? || (as_array(object_json['fetch']) & ActivityPub::FetchRemoteActorService::SUPPORTED_TYPES).empty? end def verify_authorization! diff --git a/spec/services/activitypub/process_featured_item_service_spec.rb b/spec/services/activitypub/process_featured_item_service_spec.rb index 5f071e364e..3b0f633fe0 100644 --- a/spec/services/activitypub/process_featured_item_service_spec.rb +++ b/spec/services/activitypub/process_featured_item_service_spec.rb @@ -143,6 +143,28 @@ RSpec.describe ActivityPub::ProcessFeaturedItemService do end.to_not change(CollectionItem, :count) end end + + context 'when featured object cannot be fetched' do + let(:hashtag_json) do + { + 'id' => 'https://example.com/hashtags/people', + 'type' => 'Hashtag', + 'name' => '#people', + } + end + let(:featured_object_uri) { hashtag_json['id'] } + + before do + stub_request(:get, featured_object_uri) + .to_return(status: 404) + end + + it 'does not create a collection item and returns `nil`' do + expect do + expect(subject.call(collection, object, position:)).to be_nil + end.to_not change(CollectionItem, :count) + end + end end context 'when only the id of the collection item is given' do From ce8ce8dac142f1f79caf00a219bb8daeb03881e3 Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 13 Jul 2026 14:14:10 +0200 Subject: [PATCH 04/18] Fix account language selector (#39801) --- .../subscribed_languages_modal/index.jsx | 16 ++++++++++------ app/javascript/mastodon/selectors/timelines.ts | 6 ++++++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx index 8d5665b1c4..31e4a2a99c 100644 --- a/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx +++ b/app/javascript/mastodon/features/subscribed_languages_modal/index.jsx @@ -15,16 +15,21 @@ import { IconButton } from 'mastodon/components/icon_button'; import { injectIntl } from '@/mastodon/components/intl'; import Option from 'mastodon/features/report/components/option'; import { languages as preloadedLanguages } from 'mastodon/initial_state'; +import { selectTimelinesByAccount } from '@/mastodon/selectors/timelines'; const messages = defineMessages({ close: { id: 'lightbox.close', defaultMessage: 'Close' }, }); -const getAccountLanguages = createSelector([ - (state, accountId) => state.getIn(['timelines', `account:${accountId}`, 'items'], ImmutableList()), - state => state.get('statuses'), -], (statusIds, statuses) => - ImmutableSet(statusIds.map(statusId => statuses.get(statusId)).filter(status => !status.get('reblog')).map(status => status.get('language')))); +const getAccountLanguages = createSelector( + [selectTimelinesByAccount, (state) => state.get('statuses')], + (timelines, statuses) => ImmutableSet( + timelines + .reduce((statusIds, timeline) => statusIds.concat(timeline.get('items')), ImmutableList()) + .map(statusId => statuses.get(statusId)) + .filter(status => !status.get('reblog')) + .map(status => status.get('language')) + )); const mapStateToProps = (state, { accountId }) => ({ acct: state.getIn(['accounts', accountId, 'acct']), @@ -37,7 +42,6 @@ const mapDispatchToProps = (dispatch, { accountId }) => ({ onSubmit (languages) { dispatch(followAccount(accountId, { languages })); }, - }); class SubscribedLanguagesModal extends ImmutablePureComponent { diff --git a/app/javascript/mastodon/selectors/timelines.ts b/app/javascript/mastodon/selectors/timelines.ts index af1a4ebb21..7f6b9ecb49 100644 --- a/app/javascript/mastodon/selectors/timelines.ts +++ b/app/javascript/mastodon/selectors/timelines.ts @@ -32,6 +32,12 @@ export const selectTimelineByParams = createAppSelector( (key, timelines) => toTypedTimeline(timelines.get(key)), ); +export const selectTimelinesByAccount = createAppSelector( + [(state) => state.timelines, (_, accountId: string) => accountId], + (timelines, accountId) => + timelines.filter((_, key) => key.startsWith(`account:${accountId}:`)), +); + export function toTypedTimeline(timeline?: ImmutableMap) { if (!timeline) { return null; From e23c2d246caddaf6b029b31294b1d1dd19e6e82c Mon Sep 17 00:00:00 2001 From: David Roetzel Date: Wed, 15 Jul 2026 13:04:10 +0200 Subject: [PATCH 05/18] Remove collections when user is suspended (#39809) --- app/services/delete_account_service.rb | 1 + spec/services/delete_account_service_spec.rb | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/app/services/delete_account_service.rb b/app/services/delete_account_service.rb index fa19d6ee50..b96e816b34 100644 --- a/app/services/delete_account_service.rb +++ b/app/services/delete_account_service.rb @@ -10,6 +10,7 @@ class DeleteAccountService < BaseService aliases block_relationships blocked_by_relationships + collections conversation_mutes conversations custom_filters diff --git a/spec/services/delete_account_service_spec.rb b/spec/services/delete_account_service_spec.rb index da02d2bd59..60bcf48df3 100644 --- a/spec/services/delete_account_service_spec.rb +++ b/spec/services/delete_account_service_spec.rb @@ -47,6 +47,7 @@ RSpec.describe DeleteAccountService do expect { poll.reload }.to raise_error(ActiveRecord::RecordNotFound) expect { poll_vote.reload }.to raise_error(ActiveRecord::RecordNotFound) expect { account_note.reload }.to raise_error(ActiveRecord::RecordNotFound) + expect { collection.reload }.to raise_error(ActiveRecord::RecordNotFound) end def expect_deletion_of_associated_target_records @@ -74,6 +75,7 @@ RSpec.describe DeleteAccountService do it_behaves_like 'common behavior' do let(:account) { Fabricate(:account) } let(:local_follower) { Fabricate(:account) } + let!(:collection) { Fabricate(:collection, account:) } # rubocop:disable RSpec/LetSetup it 'sends a delete actor activity to all known inboxes' do subject @@ -92,6 +94,8 @@ RSpec.describe DeleteAccountService do let(:account) { Fabricate(:account, inbox_url: 'https://bob.com/inbox', protocol: :activitypub, domain: 'bob.com') } let(:local_follower) { Fabricate(:account) } + let!(:collection) { Fabricate(:remote_collection, account:) } # rubocop:disable RSpec/LetSetup + it 'sends expected activities to followed and follower inboxes' do subject From 56c7a3acbe1788494bc964a0814dd0b7ce611e5d Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 14 Jul 2026 15:58:49 +0200 Subject: [PATCH 06/18] Fix very wide images overflowing posts horizontally (#39812) --- app/javascript/styles/mastodon/components.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index d9c5d9de6f..099262f795 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -7255,6 +7255,7 @@ a.status-card { border-radius: 8px; position: relative; width: 100%; + max-width: 100%; min-height: 64px; display: grid; grid-template-columns: 1fr 1fr; From f426d75cdaf25f45ad6a5c060ebf704aa083644f Mon Sep 17 00:00:00 2001 From: diondiondion Date: Tue, 14 Jul 2026 17:15:54 +0200 Subject: [PATCH 07/18] Fix pagination overlapping announcement reactions bar (#39814) --- app/javascript/styles/mastodon/components.scss | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 099262f795..98888c5915 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -8574,7 +8574,7 @@ noscript { } &__pagination { - padding: 15px; + padding: 0 12px 15px 0; color: var(--color-text-secondary); position: absolute; bottom: 3px; @@ -8599,7 +8599,10 @@ noscript { align-items: center; margin-top: 15px; margin-inline-start: -2px; - width: calc(100% - (90px - 33px)); + + --pagination-width: 70px; + + width: calc(100% - var(--pagination-width)); &__item { flex-shrink: 0; From ef1a880fe10b7797f4656fcedcee630eb2f4bc33 Mon Sep 17 00:00:00 2001 From: Shlee Date: Wed, 15 Jul 2026 22:16:47 +0930 Subject: [PATCH 08/18] Fix: Admin invite autofollow option is ignored (#39819) --- app/controllers/admin/invites_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/admin/invites_controller.rb b/app/controllers/admin/invites_controller.rb index ac4ee35271..196b8c5711 100644 --- a/app/controllers/admin/invites_controller.rb +++ b/app/controllers/admin/invites_controller.rb @@ -40,7 +40,7 @@ module Admin def resource_params params - .expect(invite: [:max_uses, :expires_in]) + .expect(invite: [:max_uses, :expires_in, :autofollow]) end def filtered_invites From 3ed1ad7cdbaa558943067270676881d480b682b3 Mon Sep 17 00:00:00 2001 From: Shlee Date: Wed, 15 Jul 2026 23:10:26 +0930 Subject: [PATCH 09/18] Fix handling of `QuoteRequest` rejections when those can't be found by `id` (#39820) --- app/lib/activitypub/activity/reject.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/lib/activitypub/activity/reject.rb b/app/lib/activitypub/activity/reject.rb index e5684367a2..d27b60a604 100644 --- a/app/lib/activitypub/activity/reject.rb +++ b/app/lib/activitypub/activity/reject.rb @@ -37,7 +37,7 @@ class ActivityPub::Activity::Reject < ActivityPub::Activity quote = quote_from_request_json(@object) return unless quote.present? && quote.status.local? - reject_quote!(quoting_status.quote) + reject_quote!(quote) end def reject_quote!(quote) From a614f6c38f4f9567f4e5a573a3c397d52cb39e36 Mon Sep 17 00:00:00 2001 From: Shlee Date: Wed, 15 Jul 2026 22:25:54 +0930 Subject: [PATCH 10/18] Fix CW being copied to body when editing quote posts with empty text (#39823) --- app/services/update_status_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/update_status_service.rb b/app/services/update_status_service.rb index 1f2dce08f8..9aa6c63fcb 100644 --- a/app/services/update_status_service.rb +++ b/app/services/update_status_service.rb @@ -111,7 +111,7 @@ class UpdateStatusService < BaseService end def update_immediate_attributes! - @status.text = @options[:text].presence || @options.delete(:spoiler_text) || '' if @options.key?(:text) + @status.text = @options[:text].presence || @options.delete(:spoiler_text) || '' if @options.key?(:text) && @status.quote.blank? @status.spoiler_text = @options[:spoiler_text] || '' if @options.key?(:spoiler_text) @status.sensitive = @options[:sensitive] || @options[:spoiler_text].present? if @options.key?(:sensitive) || @options.key?(:spoiler_text) @status.language = valid_locale_cascade(@options[:language], @status.language, @status.account.user&.preferred_posting_language, I18n.default_locale) From c870aae3a9fb6baafdb3f9801cc179f1aff893e1 Mon Sep 17 00:00:00 2001 From: Shlee Date: Wed, 15 Jul 2026 22:43:05 +0930 Subject: [PATCH 11/18] Include tag_follows in ASSOCIATIONS_ON_SUSPEND to suspended users (#39824) --- app/services/delete_account_service.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/services/delete_account_service.rb b/app/services/delete_account_service.rb index b96e816b34..ce998c79f9 100644 --- a/app/services/delete_account_service.rb +++ b/app/services/delete_account_service.rb @@ -27,6 +27,7 @@ class DeleteAccountService < BaseService report_notes scheduled_statuses status_pins + tag_follows ).freeze # The following associations have no important side-effects From 92f5d853d3038e1366855125ae2e170e869bf058 Mon Sep 17 00:00:00 2001 From: diondiondion Date: Wed, 15 Jul 2026 11:28:44 +0200 Subject: [PATCH 12/18] Fix page refresh when trying to save custom profile fields (#39828) --- .../confirmation_modal.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx index 3dfd3124d1..a581cb232f 100644 --- a/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx +++ b/app/javascript/mastodon/features/ui/components/confirmation_modals/confirmation_modal.tsx @@ -52,13 +52,18 @@ export const ConfirmationModal: React.FC< noCloseOnConfirm = false, noFocusButton = false, }) => { - const handleClick = useCallback(() => { - if (!noCloseOnConfirm) { - onClose(); - } + const handleSubmit = useCallback>( + (e) => { + e.preventDefault(); - void onConfirm(); - }, [onClose, onConfirm, noCloseOnConfirm]); + if (!noCloseOnConfirm) { + onClose(); + } + + void onConfirm(); + }, + [onClose, onConfirm, noCloseOnConfirm], + ); const handleSecondary = useCallback(() => { onClose(); @@ -66,7 +71,7 @@ export const ConfirmationModal: React.FC< }, [onClose, onSecondary]); return ( - + {noFocusButton ? ( @@ -107,7 +112,6 @@ export const ConfirmationModal: React.FC< {/* eslint-disable jsx-a11y/no-autofocus -- we are in a modal and thus autofocusing is justified */}