From f199d7995c59a87cf43bdb476c2b615ff989bcdb Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 16 Feb 2026 13:34:38 +0100 Subject: [PATCH 1/4] =?UTF-8?q?Add=20missing=20=E2=80=9CFilter=20action?= =?UTF-8?q?=E2=80=9D=20source=20string=20(#37838)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/locales/simple_form.en.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/locales/simple_form.en.yml b/config/locales/simple_form.en.yml index 7fff363ff1..87334b14e7 100644 --- a/config/locales/simple_form.en.yml +++ b/config/locales/simple_form.en.yml @@ -224,6 +224,7 @@ en: email: E-mail address expires_in: Expire after fields: Extra fields + filter_action: Filter action header: Header picture honeypot: "%{label} (do not fill in)" inbox_url: URL of the relay inbox From f7bf804a3ff5e0624ea417171f8f5303f1758e4e Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 16 Feb 2026 14:06:49 +0100 Subject: [PATCH 2/4] Profile redesign: Switch to server flag (#37876) --- app/javascript/mastodon/features/account_timeline/common.ts | 4 ++-- app/javascript/mastodon/features/ui/index.jsx | 4 ++-- app/javascript/mastodon/features/ui/util/async-components.js | 4 ++-- app/javascript/mastodon/utils/environment.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/javascript/mastodon/features/account_timeline/common.ts b/app/javascript/mastodon/features/account_timeline/common.ts index ee76c25bb7..1bb62a4220 100644 --- a/app/javascript/mastodon/features/account_timeline/common.ts +++ b/app/javascript/mastodon/features/account_timeline/common.ts @@ -1,5 +1,5 @@ -import { isClientFeatureEnabled } from '@/mastodon/utils/environment'; +import { isServerFeatureEnabled } from '@/mastodon/utils/environment'; export function isRedesignEnabled() { - return isClientFeatureEnabled('profile_redesign'); + return isServerFeatureEnabled('profile_redesign'); } diff --git a/app/javascript/mastodon/features/ui/index.jsx b/app/javascript/mastodon/features/ui/index.jsx index 23025cf16c..c2fcd2d02f 100644 --- a/app/javascript/mastodon/features/ui/index.jsx +++ b/app/javascript/mastodon/features/ui/index.jsx @@ -22,6 +22,7 @@ import { identityContextPropShape, withIdentity } from 'mastodon/identity_contex import { layoutFromWindow } from 'mastodon/is_mobile'; import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { checkAnnualReport } from '@/mastodon/reducers/slices/annual_report'; +import { isServerFeatureEnabled } from '@/mastodon/utils/environment'; import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose'; import { clearHeight } from '../../actions/height_cache'; @@ -89,7 +90,6 @@ import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; // Without this it ends up in ~8 very commonly used bundles. import '../../components/status'; import { areCollectionsEnabled } from '../collections/utils'; -import { isClientFeatureEnabled } from '@/mastodon/utils/environment'; const messages = defineMessages({ beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' }, @@ -162,7 +162,7 @@ class SwitchingColumnsArea extends PureComponent { redirect = ; } - const profileRedesignEnabled = isClientFeatureEnabled('profile_redesign'); + const profileRedesignEnabled = isServerFeatureEnabled('profile_redesign'); const profileRedesignRoutes = []; if (profileRedesignEnabled) { profileRedesignRoutes.push( diff --git a/app/javascript/mastodon/features/ui/util/async-components.js b/app/javascript/mastodon/features/ui/util/async-components.js index 27c634d07c..55fcc29fa4 100644 --- a/app/javascript/mastodon/features/ui/util/async-components.js +++ b/app/javascript/mastodon/features/ui/util/async-components.js @@ -1,4 +1,4 @@ -import { isClientFeatureEnabled } from '@/mastodon/utils/environment'; +import { isServerFeatureEnabled } from '@/mastodon/utils/environment'; export function EmojiPicker () { return import('../../emoji/emoji_picker'); @@ -73,7 +73,7 @@ export function PinnedStatuses () { } export function AccountTimeline () { - if (isClientFeatureEnabled('profile_redesign')) { + if (isServerFeatureEnabled('profile_redesign')) { return import('../../account_timeline/v2'); } return import('../../account_timeline'); diff --git a/app/javascript/mastodon/utils/environment.ts b/app/javascript/mastodon/utils/environment.ts index c7fecee022..5f736fa80c 100644 --- a/app/javascript/mastodon/utils/environment.ts +++ b/app/javascript/mastodon/utils/environment.ts @@ -12,13 +12,13 @@ export function isProduction() { else return import.meta.env.PROD; } -export type ServerFeatures = 'fasp' | 'collections'; +export type ServerFeatures = 'fasp' | 'collections' | 'profile_redesign'; export function isServerFeatureEnabled(feature: ServerFeatures) { return initialState?.features.includes(feature) ?? false; } -type ClientFeatures = 'profile_redesign' | 'collections'; +type ClientFeatures = 'collections'; export function isClientFeatureEnabled(feature: ClientFeatures) { try { From cff25c186bb8bb7cdd29de8938949c775dc0ec7f Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 16 Feb 2026 15:58:22 +0100 Subject: [PATCH 3/4] Fix race condition when processing statuses twice with the same idempotency key (#37879) --- app/services/post_status_service.rb | 31 +++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/app/services/post_status_service.rb b/app/services/post_status_service.rb index 3e4a715225..5b066523fd 100644 --- a/app/services/post_status_service.rb +++ b/app/services/post_status_service.rb @@ -2,6 +2,7 @@ class PostStatusService < BaseService include Redisable + include Lockable include LanguagesHelper class UnexpectedMentionsError < StandardError @@ -39,19 +40,17 @@ class PostStatusService < BaseService @in_reply_to = @options[:thread] @quoted_status = @options[:quoted_status] - return idempotency_duplicate if idempotency_given? && idempotency_duplicate? + with_idempotency do + validate_media! + preprocess_attributes! - validate_media! - preprocess_attributes! - - if scheduled? - schedule_status! - else - process_status! + if scheduled? + schedule_status! + else + process_status! + end end - redis.setex(idempotency_key, 3_600, @status.id) if idempotency_given? - unless scheduled? postprocess_status! bump_potential_friendship! @@ -208,6 +207,18 @@ class PostStatusService < BaseService @idempotency_duplicate = redis.get(idempotency_key) end + def with_idempotency + return yield unless idempotency_given? + + with_redis_lock("idempotency:lock:status:#{@account.id}:#{@options[:idempotency]}") do + return idempotency_duplicate if idempotency_duplicate? + + yield + + redis.setex(idempotency_key, 3_600, @status.id) + end + end + def scheduled_in_the_past? @scheduled_at.present? && @scheduled_at <= Time.now.utc end From df9d9423b9b046cfa84b4acbf73430b50a1dacf3 Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 16 Feb 2026 14:06:49 +0100 Subject: [PATCH 4/4] [Glitch] Profile redesign: Switch to server flag Port f7bf804a3ff5e0624ea417171f8f5303f1758e4e to glitch-soc Signed-off-by: Claire --- .../flavours/glitch/features/account_timeline/common.ts | 4 ++-- app/javascript/flavours/glitch/features/ui/index.jsx | 4 ++-- .../flavours/glitch/features/ui/util/async-components.js | 4 ++-- app/javascript/flavours/glitch/utils/environment.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/javascript/flavours/glitch/features/account_timeline/common.ts b/app/javascript/flavours/glitch/features/account_timeline/common.ts index 33d1ee210d..866b2549e6 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/common.ts +++ b/app/javascript/flavours/glitch/features/account_timeline/common.ts @@ -1,5 +1,5 @@ -import { isClientFeatureEnabled } from '@/flavours/glitch/utils/environment'; +import { isServerFeatureEnabled } from '@/flavours/glitch/utils/environment'; export function isRedesignEnabled() { - return isClientFeatureEnabled('profile_redesign'); + return isServerFeatureEnabled('profile_redesign'); } diff --git a/app/javascript/flavours/glitch/features/ui/index.jsx b/app/javascript/flavours/glitch/features/ui/index.jsx index 47c7b15f36..d692359300 100644 --- a/app/javascript/flavours/glitch/features/ui/index.jsx +++ b/app/javascript/flavours/glitch/features/ui/index.jsx @@ -25,6 +25,7 @@ import { layoutFromWindow } from 'flavours/glitch/is_mobile'; import { selectUnreadNotificationGroupsCount } from 'flavours/glitch/selectors/notifications'; import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router'; import { checkAnnualReport } from '@/flavours/glitch/reducers/slices/annual_report'; +import { isServerFeatureEnabled } from '@/flavours/glitch/utils/environment'; import { uploadCompose, resetCompose, changeComposeSpoilerness } from '../../actions/compose'; import { clearHeight } from '../../actions/height_cache'; @@ -92,7 +93,6 @@ import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers'; // Without this it ends up in ~8 very commonly used bundles. import '../../components/status'; import { areCollectionsEnabled } from '../collections/utils'; -import { isClientFeatureEnabled } from '@/flavours/glitch/utils/environment'; const messages = defineMessages({ beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' }, @@ -170,7 +170,7 @@ class SwitchingColumnsArea extends PureComponent { redirect = ; } - const profileRedesignEnabled = isClientFeatureEnabled('profile_redesign'); + const profileRedesignEnabled = isServerFeatureEnabled('profile_redesign'); const profileRedesignRoutes = []; if (profileRedesignEnabled) { profileRedesignRoutes.push( diff --git a/app/javascript/flavours/glitch/features/ui/util/async-components.js b/app/javascript/flavours/glitch/features/ui/util/async-components.js index bc76a9c019..db5cdf9f2a 100644 --- a/app/javascript/flavours/glitch/features/ui/util/async-components.js +++ b/app/javascript/flavours/glitch/features/ui/util/async-components.js @@ -1,4 +1,4 @@ -import { isClientFeatureEnabled } from '@/flavours/glitch/utils/environment'; +import { isServerFeatureEnabled } from '@/flavours/glitch/utils/environment'; export function EmojiPicker () { return import('../../emoji/emoji_picker'); @@ -73,7 +73,7 @@ export function PinnedStatuses () { } export function AccountTimeline () { - if (isClientFeatureEnabled('profile_redesign')) { + if (isServerFeatureEnabled('profile_redesign')) { return import('../../account_timeline/v2'); } return import('../../account_timeline'); diff --git a/app/javascript/flavours/glitch/utils/environment.ts b/app/javascript/flavours/glitch/utils/environment.ts index c7fecee022..5f736fa80c 100644 --- a/app/javascript/flavours/glitch/utils/environment.ts +++ b/app/javascript/flavours/glitch/utils/environment.ts @@ -12,13 +12,13 @@ export function isProduction() { else return import.meta.env.PROD; } -export type ServerFeatures = 'fasp' | 'collections'; +export type ServerFeatures = 'fasp' | 'collections' | 'profile_redesign'; export function isServerFeatureEnabled(feature: ServerFeatures) { return initialState?.features.includes(feature) ?? false; } -type ClientFeatures = 'profile_redesign' | 'collections'; +type ClientFeatures = 'collections'; export function isClientFeatureEnabled(feature: ClientFeatures) { try {