From 2829f72d1668ec2cda5f07137f0e69c7abd37274 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 11:13:29 +0200
Subject: [PATCH 01/70] Update dependency propshaft to v1.1.0 (#32192)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index b85d97761..a06b4407a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -601,7 +601,7 @@ GEM
       actionmailer (>= 3)
       net-smtp
       premailer (~> 1.7, >= 1.7.9)
-    propshaft (1.0.1)
+    propshaft (1.1.0)
       actionpack (>= 7.0.0)
       activesupport (>= 7.0.0)
       rack

From 3d943f03fd5264ec5f8c636f4fde18867a4e22c3 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 11:13:33 +0200
Subject: [PATCH 02/70] Update dependency webmock to v3.24.0 (#32190)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Gemfile.lock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index a06b4407a..b3c37393f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -698,7 +698,7 @@ GEM
     responders (3.1.1)
       actionpack (>= 5.2)
       railties (>= 5.2)
-    rexml (3.3.7)
+    rexml (3.3.8)
     rotp (6.3.0)
     rouge (4.3.0)
     rpam2 (4.0.2)
@@ -884,7 +884,7 @@ GEM
     webfinger (1.2.0)
       activesupport
       httpclient (>= 2.4)
-    webmock (3.23.1)
+    webmock (3.24.0)
       addressable (>= 2.8.0)
       crack (>= 0.3.2)
       hashdiff (>= 0.4.0, < 2.0.0)

From b7ddf45924c35dc8d5ecc8f4ab3c611da9d70d94 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 11:29:38 +0200
Subject: [PATCH 03/70] Update docker/dockerfile Docker tag to v1.10 (#32166)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Dockerfile           | 2 +-
 streaming/Dockerfile | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/Dockerfile b/Dockerfile
index c7c02d9b4..0452e5d06 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-# syntax=docker/dockerfile:1.9
+# syntax=docker/dockerfile:1.10
 
 # This file is designed for production server deployment, not local development work
 # For a containerized local dev environment, see: https://github.com/mastodon/mastodon/blob/main/README.md#docker
diff --git a/streaming/Dockerfile b/streaming/Dockerfile
index 938f1655d..bed6b6373 100644
--- a/streaming/Dockerfile
+++ b/streaming/Dockerfile
@@ -1,4 +1,4 @@
-# syntax=docker/dockerfile:1.9
+# syntax=docker/dockerfile:1.10
 
 # Please see https://docs.docker.com/engine/reference/builder for information about
 # the extended buildx capabilities used in this file.

From 7a653001b42d07eb82858801b5c5b00d71fefa12 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 11:30:09 +0200
Subject: [PATCH 04/70] Update dependency haml_lint to v0.59.0 (#32146)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Gemfile.lock | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index b3c37393f..cd939c9eb 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -301,7 +301,7 @@ GEM
       activesupport (>= 5.1)
       haml (>= 4.0.6)
       railties (>= 5.1)
-    haml_lint (0.58.0)
+    haml_lint (0.59.0)
       haml (>= 5.0)
       parallel (~> 1.10)
       rainbow
@@ -862,7 +862,7 @@ GEM
     unf (0.1.4)
       unf_ext
     unf_ext (0.0.9.1)
-    unicode-display_width (2.5.0)
+    unicode-display_width (2.6.0)
     uri (0.13.1)
     validate_email (0.1.6)
       activemodel (>= 3.0)

From f251edfc1daffecb30914d1f48e6919710c61b12 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 05:30:21 -0400
Subject: [PATCH 05/70] Add `date_range` view helper (#32187)

---
 app/helpers/admin/dashboard_helper.rb     | 5 +++++
 app/views/admin/dashboard/index.html.haml | 4 +---
 app/views/admin/instances/show.html.haml  | 4 +---
 app/views/admin/tags/show.html.haml       | 4 +---
 4 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/app/helpers/admin/dashboard_helper.rb b/app/helpers/admin/dashboard_helper.rb
index 6096ff138..4de779b79 100644
--- a/app/helpers/admin/dashboard_helper.rb
+++ b/app/helpers/admin/dashboard_helper.rb
@@ -18,6 +18,11 @@ module Admin::DashboardHelper
     end
   end
 
+  def date_range(range)
+    [l(range.first), l(range.last)]
+      .join(' - ')
+  end
+
   def relevant_account_timestamp(account)
     timestamp, exact = if account.user_current_sign_in_at && account.user_current_sign_in_at < 24.hours.ago
                          [account.user_current_sign_in_at, true]
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 27d8f4790..2b4d02fa6 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -2,9 +2,7 @@
   = t('admin.dashboard.title')
 
 - content_for :heading_actions do
-  = l(@time_period.first)
-  = ' - '
-  = l(@time_period.last)
+  = date_range(@time_period)
 
 - unless @system_checks.empty?
   .flash-message-stack
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index dfedbf4cb..812a9c887 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -3,9 +3,7 @@
 
 - if current_user.can?(:view_dashboard)
   - content_for :heading_actions do
-    = l(@time_period.first)
-    = ' - '
-    = l(@time_period.last)
+    = date_range(@time_period)
 
   - if @instance.persisted?
     = render 'dashboard', instance_domain: @instance.domain, period_end_at: @time_period.last, period_start_at: @time_period.first
diff --git a/app/views/admin/tags/show.html.haml b/app/views/admin/tags/show.html.haml
index 93387843b..462ca312a 100644
--- a/app/views/admin/tags/show.html.haml
+++ b/app/views/admin/tags/show.html.haml
@@ -4,9 +4,7 @@
 - content_for :heading_actions do
   - if current_user.can?(:view_dashboard)
     .time-period
-      = l(@time_period.first)
-      = ' - '
-      = l(@time_period.last)
+      = date_range(@time_period)
 
   = link_to t('admin.tags.open'), tag_url(@tag), class: 'button', target: '_blank', rel: 'noopener noreferrer'
 

From 0b89765e9a9154c5f904b602ae5aaa0966880818 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 09:30:54 +0000
Subject: [PATCH 06/70] Update RuboCop (non-major) to v1.22.1 (#31573)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Gemfile.lock | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index cd939c9eb..334a9e30d 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -748,15 +748,15 @@ GEM
       parser (>= 3.3.1.0)
     rubocop-capybara (2.21.0)
       rubocop (~> 1.41)
-    rubocop-performance (1.21.1)
+    rubocop-performance (1.22.1)
       rubocop (>= 1.48.1, < 2.0)
       rubocop-ast (>= 1.31.1, < 2.0)
-    rubocop-rails (2.25.1)
+    rubocop-rails (2.26.2)
       activesupport (>= 4.2.0)
       rack (>= 1.1)
-      rubocop (>= 1.33.0, < 2.0)
+      rubocop (>= 1.52.0, < 2.0)
       rubocop-ast (>= 1.31.1, < 2.0)
-    rubocop-rspec (3.0.4)
+    rubocop-rspec (3.0.5)
       rubocop (~> 1.61)
     rubocop-rspec_rails (2.30.0)
       rubocop (~> 1.61)

From e6cda8388c0eae4dee82437538544563b04e7f24 Mon Sep 17 00:00:00 2001
From: David Roetzel <david@roetzel.de>
Date: Tue, 1 Oct 2024 11:38:42 +0200
Subject: [PATCH 07/70] Move OTP secret length to configuration (#32125)

---
 .../otp_authentication_controller.rb                        | 2 +-
 app/models/user.rb                                          | 3 ++-
 spec/controllers/auth/sessions_controller_spec.rb           | 6 +++---
 spec/requests/auth/sessions/security_key_options_spec.rb    | 2 +-
 spec/system/oauth_spec.rb                                   | 2 +-
 5 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb b/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb
index 0bff01ec2..ca8d46afe 100644
--- a/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb
+++ b/app/controllers/settings/two_factor_authentication/otp_authentication_controller.rb
@@ -15,7 +15,7 @@ module Settings
       end
 
       def create
-        session[:new_otp_secret] = User.generate_otp_secret(32)
+        session[:new_otp_secret] = User.generate_otp_secret
 
         redirect_to new_settings_two_factor_authentication_confirmation_path
       end
diff --git a/app/models/user.rb b/app/models/user.rb
index fcb0eced7..c32a575ed 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -71,7 +71,8 @@ class User < ApplicationRecord
   ACTIVE_DURATION = ENV.fetch('USER_ACTIVE_DAYS', 7).to_i.days.freeze
 
   devise :two_factor_authenticatable,
-         otp_secret_encryption_key: Rails.configuration.x.otp_secret
+         otp_secret_encryption_key: Rails.configuration.x.otp_secret,
+         otp_secret_length: 32
 
   include LegacyOtpSecret # Must be after the above `devise` line in order to override the legacy method
 
diff --git a/spec/controllers/auth/sessions_controller_spec.rb b/spec/controllers/auth/sessions_controller_spec.rb
index 713ea3ff1..4a6956cb0 100644
--- a/spec/controllers/auth/sessions_controller_spec.rb
+++ b/spec/controllers/auth/sessions_controller_spec.rb
@@ -208,7 +208,7 @@ RSpec.describe Auth::SessionsController do
     context 'when using two-factor authentication' do
       context 'with OTP enabled as second factor' do
         let!(:user) do
-          Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
+          Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret)
         end
 
         let!(:recovery_codes) do
@@ -230,7 +230,7 @@ RSpec.describe Auth::SessionsController do
 
         context 'when using email and password after an unfinished log-in attempt to a 2FA-protected account' do
           let!(:other_user) do
-            Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
+            Fabricate(:user, email: 'z@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret)
           end
 
           before do
@@ -342,7 +342,7 @@ RSpec.describe Auth::SessionsController do
 
       context 'with WebAuthn and OTP enabled as second factor' do
         let!(:user) do
-          Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
+          Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret)
         end
 
         let!(:webauthn_credential) do
diff --git a/spec/requests/auth/sessions/security_key_options_spec.rb b/spec/requests/auth/sessions/security_key_options_spec.rb
index 6246e4beb..e53b9802b 100644
--- a/spec/requests/auth/sessions/security_key_options_spec.rb
+++ b/spec/requests/auth/sessions/security_key_options_spec.rb
@@ -6,7 +6,7 @@ require 'webauthn/fake_client'
 RSpec.describe 'Security Key Options' do
   describe 'GET /auth/sessions/security_key_options' do
     let!(:user) do
-      Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret(32))
+      Fabricate(:user, email: 'x@y.com', password: 'abcdefgh', otp_required_for_login: true, otp_secret: User.generate_otp_secret)
     end
 
     context 'with WebAuthn and OTP enabled as second factor' do
diff --git a/spec/system/oauth_spec.rb b/spec/system/oauth_spec.rb
index 0f96a5967..64ac75879 100644
--- a/spec/system/oauth_spec.rb
+++ b/spec/system/oauth_spec.rb
@@ -179,7 +179,7 @@ RSpec.describe 'Using OAuth from an external app' do
     end
 
     context 'when the user has set up TOTP' do
-      let(:user) { Fabricate(:user, email: email, password: password, otp_required_for_login: true, otp_secret: User.generate_otp_secret(32)) }
+      let(:user) { Fabricate(:user, email: email, password: password, otp_required_for_login: true, otp_secret: User.generate_otp_secret) }
 
       it 'when accepting the authorization request' do
         params = { client_id: client_app.uid, response_type: 'code', redirect_uri: client_app.redirect_uri, scope: 'read' }

From e975b55c24f420316b14d5dc02b2eab3fbba4d6e Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 05:54:42 -0400
Subject: [PATCH 08/70] Remove `WebfingerHelper` module & move usage inline
 (#31203)

---
 app/helpers/webfinger_helper.rb                        | 7 -------
 app/models/remote_follow.rb                            | 3 +--
 app/services/activitypub/fetch_remote_actor_service.rb | 5 ++---
 app/services/resolve_account_service.rb                | 5 ++---
 4 files changed, 5 insertions(+), 15 deletions(-)
 delete mode 100644 app/helpers/webfinger_helper.rb

diff --git a/app/helpers/webfinger_helper.rb b/app/helpers/webfinger_helper.rb
deleted file mode 100644
index 482f4e19e..000000000
--- a/app/helpers/webfinger_helper.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-module WebfingerHelper
-  def webfinger!(uri)
-    Webfinger.new(uri).perform
-  end
-end
diff --git a/app/models/remote_follow.rb b/app/models/remote_follow.rb
index 10715ac97..fa0586f57 100644
--- a/app/models/remote_follow.rb
+++ b/app/models/remote_follow.rb
@@ -3,7 +3,6 @@
 class RemoteFollow
   include ActiveModel::Validations
   include RoutingHelper
-  include WebfingerHelper
 
   attr_accessor :acct, :addressable_template
 
@@ -66,7 +65,7 @@ class RemoteFollow
   end
 
   def acct_resource
-    @acct_resource ||= webfinger!("acct:#{acct}")
+    @acct_resource ||= Webfinger.new("acct:#{acct}").perform
   rescue Webfinger::Error, HTTP::ConnectionError
     nil
   end
diff --git a/app/services/activitypub/fetch_remote_actor_service.rb b/app/services/activitypub/fetch_remote_actor_service.rb
index 2c372c2ec..560cf424e 100644
--- a/app/services/activitypub/fetch_remote_actor_service.rb
+++ b/app/services/activitypub/fetch_remote_actor_service.rb
@@ -3,7 +3,6 @@
 class ActivityPub::FetchRemoteActorService < BaseService
   include JsonLdHelper
   include DomainControlHelper
-  include WebfingerHelper
 
   class Error < StandardError; end
 
@@ -45,7 +44,7 @@ class ActivityPub::FetchRemoteActorService < BaseService
   private
 
   def check_webfinger!
-    webfinger                            = webfinger!("acct:#{@username}@#{@domain}")
+    webfinger = Webfinger.new("acct:#{@username}@#{@domain}").perform
     confirmed_username, confirmed_domain = split_acct(webfinger.subject)
 
     if @username.casecmp(confirmed_username).zero? && @domain.casecmp(confirmed_domain).zero?
@@ -54,7 +53,7 @@ class ActivityPub::FetchRemoteActorService < BaseService
       return
     end
 
-    webfinger                            = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}")
+    webfinger = Webfinger.new("acct:#{confirmed_username}@#{confirmed_domain}").perform
     @username, @domain                   = split_acct(webfinger.subject)
 
     raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{@uri} (stopped at #{@username}@#{@domain})" unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
diff --git a/app/services/resolve_account_service.rb b/app/services/resolve_account_service.rb
index 8a5863bab..cd96b55c7 100644
--- a/app/services/resolve_account_service.rb
+++ b/app/services/resolve_account_service.rb
@@ -2,7 +2,6 @@
 
 class ResolveAccountService < BaseService
   include DomainControlHelper
-  include WebfingerHelper
   include Redisable
   include Lockable
 
@@ -81,7 +80,7 @@ class ResolveAccountService < BaseService
   end
 
   def process_webfinger!(uri)
-    @webfinger                           = webfinger!("acct:#{uri}")
+    @webfinger = Webfinger.new("acct:#{uri}").perform
     confirmed_username, confirmed_domain = split_acct(@webfinger.subject)
 
     if confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?
@@ -91,7 +90,7 @@ class ResolveAccountService < BaseService
     end
 
     # Account doesn't match, so it may have been redirected
-    @webfinger         = webfinger!("acct:#{confirmed_username}@#{confirmed_domain}")
+    @webfinger = Webfinger.new("acct:#{confirmed_username}@#{confirmed_domain}").perform
     @username, @domain = split_acct(@webfinger.subject)
 
     raise Webfinger::RedirectError, "Too many webfinger redirects for URI #{uri} (stopped at #{@username}@#{@domain})" unless confirmed_username.casecmp(@username).zero? && confirmed_domain.casecmp(@domain).zero?

From 454d21ab5ad87def97e712bc53e8e8ef50a5c678 Mon Sep 17 00:00:00 2001
From: Emelia Smith <ThisIsMissEm@users.noreply.github.com>
Date: Tue, 1 Oct 2024 11:56:58 +0200
Subject: [PATCH 09/70] Remove OAuth Password Grant Type support (#30960)

---
 config/initializers/doorkeeper.rb | 15 ++++-----------
 1 file changed, 4 insertions(+), 11 deletions(-)

diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index b47e76c08..de1c75f57 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -9,16 +9,9 @@ Doorkeeper.configure do
     current_user || redirect_to(new_user_session_url)
   end
 
-  resource_owner_from_credentials do |_routes|
-    user   = User.authenticate_with_ldap(email: request.params[:username], password: request.params[:password]) if Devise.ldap_authentication
-    user ||= User.authenticate_with_pam(email: request.params[:username], password: request.params[:password]) if Devise.pam_authentication
-
-    if user.nil?
-      user = User.find_by(email: request.params[:username])
-      user = nil unless user&.valid_password?(request.params[:password])
-    end
-
-    user unless user&.otp_required_for_login?
+  # Disable Resource Owner Password Credentials Grant Flow
+  resource_owner_from_credentials do
+    nil
   end
 
   # Doorkeeper provides some administrative interfaces for managing OAuth
@@ -169,7 +162,7 @@ Doorkeeper.configure do
   #   http://tools.ietf.org/html/rfc6819#section-4.4.3
   #
 
-  grant_flows %w(authorization_code password client_credentials)
+  grant_flows %w(authorization_code client_credentials)
 
   # Under some circumstances you might want to have applications auto-approved,
   # so that the user skips the authorization step.

From efd3f59627a2786b5601ef6a5c484ec96a777ae5 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 06:02:58 -0400
Subject: [PATCH 10/70] Use `module: :users` in routes/admin section (#30767)

---
 config/routes/admin.rb | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index 50c4c1059..3dba6fa5b 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -144,8 +144,10 @@ namespace :admin do
   end
 
   resources :users, only: [] do
-    resource :two_factor_authentication, only: [:destroy], controller: 'users/two_factor_authentications'
-    resource :role, only: [:show, :update], controller: 'users/roles'
+    scope module: :users do
+      resource :two_factor_authentication, only: [:destroy]
+      resource :role, only: [:show, :update]
+    end
   end
 
   resources :custom_emojis, only: [:index, :new, :create] do

From 8ae381fd042aa7dbcc546c13ccb158ccd8b01ff3 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 10:04:12 +0000
Subject: [PATCH 11/70] Update dependency postcss-preset-env to v10.0.5
 (#32019)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 yarn.lock | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 38402229b..8970981d8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1763,9 +1763,9 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@csstools/postcss-light-dark-function@npm:^2.0.2":
-  version: 2.0.2
-  resolution: "@csstools/postcss-light-dark-function@npm:2.0.2"
+"@csstools/postcss-light-dark-function@npm:^2.0.4":
+  version: 2.0.4
+  resolution: "@csstools/postcss-light-dark-function@npm:2.0.4"
   dependencies:
     "@csstools/css-parser-algorithms": "npm:^3.0.1"
     "@csstools/css-tokenizer": "npm:^3.0.1"
@@ -1773,7 +1773,7 @@ __metadata:
     "@csstools/utilities": "npm:^2.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/f8973c435868998e5d6af1fc0c35b27bbf65fa9d0c35f5055c689b8ee2807a16802044e296f7def39a7253ae544fb49559e8273ee22eb4e21845aa980a1bc82b
+  checksum: 10c0/0176422ad9747953964b1ceff002df1ecb1952ebc481db6192070d68777135b582ea6fd32ae819b9c64c96cb9170bd6907c647c85b48daa4984b7ed3d7f9bccb
   languageName: node
   linkType: hard
 
@@ -13921,8 +13921,8 @@ __metadata:
   linkType: hard
 
 "postcss-preset-env@npm:^10.0.0":
-  version: 10.0.3
-  resolution: "postcss-preset-env@npm:10.0.3"
+  version: 10.0.5
+  resolution: "postcss-preset-env@npm:10.0.5"
   dependencies:
     "@csstools/postcss-cascade-layers": "npm:^5.0.0"
     "@csstools/postcss-color-function": "npm:^4.0.2"
@@ -13936,7 +13936,7 @@ __metadata:
     "@csstools/postcss-ic-unit": "npm:^4.0.0"
     "@csstools/postcss-initial": "npm:^2.0.0"
     "@csstools/postcss-is-pseudo-class": "npm:^5.0.0"
-    "@csstools/postcss-light-dark-function": "npm:^2.0.2"
+    "@csstools/postcss-light-dark-function": "npm:^2.0.4"
     "@csstools/postcss-logical-float-and-clear": "npm:^3.0.0"
     "@csstools/postcss-logical-overflow": "npm:^2.0.0"
     "@csstools/postcss-logical-overscroll-behavior": "npm:^2.0.0"
@@ -13987,7 +13987,7 @@ __metadata:
     postcss-selector-not: "npm:^8.0.0"
   peerDependencies:
     postcss: ^8.4
-  checksum: 10c0/da42caa2aab4d825fddfde00ebe2416d338c7b9a6f79a68840297888a8384f85991991c3fa10cf2d359fb230c885375f5cebd7bd63972725cd2a596d218f8b6a
+  checksum: 10c0/db5eb1175cb26bed3f1a4c47acc67935ffc784520321470520e59de366ac6f91be1e609fe36056af707ed20f7910721287cff0fae416c437dd3e944de13ffd05
   languageName: node
   linkType: hard
 

From 7e35bef97efc5b5c107434d0157326427b79660a Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 06:34:05 -0400
Subject: [PATCH 12/70] Remove explicit `put` action in settings forms (#32176)

---
 app/views/settings/applications/show.html.haml              | 2 +-
 app/views/settings/preferences/appearance/show.html.haml    | 2 +-
 app/views/settings/preferences/notifications/show.html.haml | 2 +-
 app/views/settings/preferences/other/show.html.haml         | 2 +-
 app/views/settings/privacy/show.html.haml                   | 2 +-
 app/views/settings/profiles/show.html.haml                  | 2 +-
 app/views/settings/verifications/show.html.haml             | 2 +-
 7 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/app/views/settings/applications/show.html.haml b/app/views/settings/applications/show.html.haml
index 19630cf49..099e0d96a 100644
--- a/app/views/settings/applications/show.html.haml
+++ b/app/views/settings/applications/show.html.haml
@@ -23,7 +23,7 @@
 
 %hr/
 
-= simple_form_for @application, url: settings_application_path(@application), method: :put do |form|
+= simple_form_for @application, url: settings_application_path(@application) do |form|
   = render form
 
   .actions
diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml
index f34ce4a6a..22f88c7cd 100644
--- a/app/views/settings/preferences/appearance/show.html.haml
+++ b/app/views/settings/preferences/appearance/show.html.haml
@@ -4,7 +4,7 @@
 - content_for :heading_actions do
   = button_tag t('generic.save_changes'), class: 'button', form: 'edit_user'
 
-= simple_form_for current_user, url: settings_preferences_appearance_path, html: { method: :put, id: 'edit_user' } do |f|
+= simple_form_for current_user, url: settings_preferences_appearance_path, html: { id: :edit_user } do |f|
   .fields-row
     .fields-group.fields-row__column.fields-row__column-6
       = f.input :locale,
diff --git a/app/views/settings/preferences/notifications/show.html.haml b/app/views/settings/preferences/notifications/show.html.haml
index de318dda5..a8e179d01 100644
--- a/app/views/settings/preferences/notifications/show.html.haml
+++ b/app/views/settings/preferences/notifications/show.html.haml
@@ -4,7 +4,7 @@
 - content_for :heading_actions do
   = button_tag t('generic.save_changes'), class: 'button', form: 'edit_notification'
 
-= simple_form_for current_user, url: settings_preferences_notifications_path, html: { method: :put, id: 'edit_notification' } do |f|
+= simple_form_for current_user, url: settings_preferences_notifications_path, html: { id: :edit_notification } do |f|
   = render 'shared/error_messages', object: current_user
 
   %h4= t 'notifications.email_events'
diff --git a/app/views/settings/preferences/other/show.html.haml b/app/views/settings/preferences/other/show.html.haml
index e2888f721..92df8e521 100644
--- a/app/views/settings/preferences/other/show.html.haml
+++ b/app/views/settings/preferences/other/show.html.haml
@@ -4,7 +4,7 @@
 - content_for :heading_actions do
   = button_tag t('generic.save_changes'), class: 'button', form: 'edit_preferences'
 
-= simple_form_for current_user, url: settings_preferences_other_path, html: { method: :put, id: 'edit_preferences' } do |f|
+= simple_form_for current_user, url: settings_preferences_other_path, html: { id: :edit_preferences } do |f|
   = render 'shared/error_messages', object: current_user
 
   = f.simple_fields_for :settings, current_user.settings do |ff|
diff --git a/app/views/settings/privacy/show.html.haml b/app/views/settings/privacy/show.html.haml
index 3c1438258..f7241cfb2 100644
--- a/app/views/settings/privacy/show.html.haml
+++ b/app/views/settings/privacy/show.html.haml
@@ -5,7 +5,7 @@
   %h2= t('settings.profile')
   = render partial: 'settings/shared/profile_navigation'
 
-= simple_form_for @account, url: settings_privacy_path, html: { method: :put } do |f|
+= simple_form_for @account, url: settings_privacy_path do |f|
   = render 'shared/error_messages', object: @account
 
   %p.lead= t('privacy.hint_html')
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index 8fb213251..cad5aee66 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -5,7 +5,7 @@
   %h2= t('settings.profile')
   = render partial: 'settings/shared/profile_navigation'
 
-= simple_form_for @account, url: settings_profile_path, html: { method: :put, id: 'edit_profile' } do |f|
+= simple_form_for @account, url: settings_profile_path, html: { id: :edit_profile } do |f|
   = render 'shared/error_messages', object: @account
 
   %p.lead= t('edit_profile.hint_html')
diff --git a/app/views/settings/verifications/show.html.haml b/app/views/settings/verifications/show.html.haml
index af9556d00..00491866c 100644
--- a/app/views/settings/verifications/show.html.haml
+++ b/app/views/settings/verifications/show.html.haml
@@ -31,7 +31,7 @@
             = material_symbol 'check', class: 'verified-badge__mark'
             %span= field.value
 
-= simple_form_for @account, url: settings_verification_path, html: { method: :put, class: 'form-section' } do |f|
+= simple_form_for @account, url: settings_verification_path, html: { class: 'form-section' } do |f|
   = render 'shared/error_messages', object: @account
 
   %h3= t('author_attribution.title')

From 4b4bf82ea99a5d4f223556e92d97a04dbfa45297 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 06:48:27 -0400
Subject: [PATCH 14/70] Fix nav item active highlight for some paths (#32159)

---
 config/navigation.rb | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/config/navigation.rb b/config/navigation.rb
index c79e96835..09a7b55b5 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -25,13 +25,13 @@ SimpleNavigation::Configuration.run do |navigation|
     n.item :statuses_cleanup, safe_join([material_symbol('history'), t('settings.statuses_cleanup')]), statuses_cleanup_path, if: -> { current_user.functional_or_moved? && !self_destruct }
 
     n.item :security, safe_join([material_symbol('lock'), t('settings.account')]), edit_user_registration_path do |s|
-      s.item :password, safe_join([material_symbol('lock'), t('settings.account_settings')]), edit_user_registration_path, highlights_on: %r{/auth/edit|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes}
+      s.item :password, safe_join([material_symbol('lock'), t('settings.account_settings')]), edit_user_registration_path, highlights_on: %r{^/auth|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes}
       s.item :two_factor_authentication, safe_join([material_symbol('safety_check'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_methods_path, highlights_on: %r{/settings/two_factor_authentication|/settings/otp_authentication|/settings/security_keys}
       s.item :authorized_apps, safe_join([material_symbol('list_alt'), t('settings.authorized_apps')]), oauth_authorized_applications_path, if: -> { !self_destruct }
     end
 
     n.item :data, safe_join([material_symbol('cloud_sync'), t('settings.import_and_export')]), settings_export_path do |s|
-      s.item :import, safe_join([material_symbol('cloud_upload'), t('settings.import')]), settings_imports_path, if: -> { current_user.functional? && !self_destruct }
+      s.item :import, safe_join([material_symbol('cloud_upload'), t('settings.import')]), settings_imports_path, highlights_on: %r{/settings/imports}, if: -> { current_user.functional? && !self_destruct }
       s.item :export, safe_join([material_symbol('cloud_download'), t('settings.export')]), settings_export_path
     end
 
@@ -51,7 +51,9 @@ SimpleNavigation::Configuration.run do |navigation|
       s.item :accounts, safe_join([material_symbol('groups'), t('admin.accounts.title')]), admin_accounts_path(origin: 'local'), highlights_on: %r{/admin/accounts|admin/account_moderation_notes|/admin/pending_accounts|/admin/users}, if: -> { current_user.can?(:manage_users) }
       s.item :tags, safe_join([material_symbol('tag'), t('admin.tags.title')]), admin_tags_path, highlights_on: %r{/admin/tags}, if: -> { current_user.can?(:manage_taxonomies) }
       s.item :invites, safe_join([material_symbol('person_add'), t('admin.invites.title')]), admin_invites_path, if: -> { current_user.can?(:manage_invites) }
-      s.item :instances, safe_join([material_symbol('cloud'), t('admin.instances.title')]), admin_instances_path(limited: limited_federation_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows}, if: -> { current_user.can?(:manage_federation) }
+      s.item :instances, safe_join([material_symbol('cloud'), t('admin.instances.title')]), admin_instances_path(limited: limited_federation_mode? ? nil : '1'), highlights_on: %r{/admin/instances|/admin/domain_blocks|/admin/domain_allows|/admin/export_domain_blocks}, if: lambda {
+        current_user.can?(:manage_federation)
+      }
       s.item :email_domain_blocks, safe_join([material_symbol('mail'), t('admin.email_domain_blocks.title')]), admin_email_domain_blocks_path, highlights_on: %r{/admin/email_domain_blocks}, if: -> { current_user.can?(:manage_blocks) }
       s.item :ip_blocks, safe_join([material_symbol('hide_source'), t('admin.ip_blocks.title')]), admin_ip_blocks_path, highlights_on: %r{/admin/ip_blocks}, if: -> { current_user.can?(:manage_blocks) }
       s.item :action_logs, safe_join([material_symbol('list'), t('admin.action_logs.title')]), admin_action_logs_path, if: -> { current_user.can?(:view_audit_log) }

From 651846c62227917729492ff9d6fc4161ea8d86c5 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 07:03:13 -0400
Subject: [PATCH 15/70] Only show email domain blocks MX table when some found
 (#32155)

---
 app/views/admin/email_domain_blocks/new.html.haml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/views/admin/email_domain_blocks/new.html.haml b/app/views/admin/email_domain_blocks/new.html.haml
index dd4b83ee3..2dfdca937 100644
--- a/app/views/admin/email_domain_blocks/new.html.haml
+++ b/app/views/admin/email_domain_blocks/new.html.haml
@@ -16,7 +16,7 @@
               label: I18n.t('admin.email_domain_blocks.allow_registrations_with_approval'),
               wrapper: :with_label
 
-  - if defined?(@resolved_records)
+  - if defined?(@resolved_records) && @resolved_records.any?
     %p.hint= t('admin.email_domain_blocks.resolved_dns_records_hint_html')
 
     .batch-table

From c0095079128d04974a10821e04e046ec27fa6a3f Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 07:14:12 -0400
Subject: [PATCH 16/70] Use `button_to` for `method: :post` links on account
 show page (#32154)

---
 app/javascript/styles/mastodon/forms.scss   |  4 ++++
 app/views/admin/accounts/_buttons.html.haml | 20 ++++++++++----------
 2 files changed, 14 insertions(+), 10 deletions(-)

diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 957a28352..4f974ea58 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -19,6 +19,10 @@ code {
   margin-bottom: 24px;
 }
 
+form.button_to {
+  display: inline-block;
+}
+
 .fade-out-top {
   position: relative;
   overflow: hidden;
diff --git a/app/views/admin/accounts/_buttons.html.haml b/app/views/admin/accounts/_buttons.html.haml
index 2aaca8962..eb1a7c3a4 100644
--- a/app/views/admin/accounts/_buttons.html.haml
+++ b/app/views/admin/accounts/_buttons.html.haml
@@ -4,8 +4,8 @@
     %p.muted-hint= deletion_request.present? ? t('admin.accounts.remote_suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.remote_suspension_irreversible')
   - else
     %p.muted-hint= deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible')
-  = link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsuspend, account)
-  = link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account) && account.suspension_origin_remote?
+  = button_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(account.id), class: :button if can?(:unsuspend, account)
+  = button_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), class: :button if can?(:redownload, account) && account.suspension_origin_remote?
   - if deletion_request.present? && can?(:destroy, account)
     = link_to t('admin.accounts.delete'), admin_account_path(account.id), method: :delete, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') }
 - else
@@ -14,28 +14,28 @@
       - if account.local? && account.user_approved?
         = link_to t('admin.accounts.warn'), new_admin_account_action_path(account.id, type: 'none'), class: 'button' if can?(:warn, account)
         - if account.user_disabled?
-          = link_to t('admin.accounts.enable'), enable_admin_account_path(account.id), method: :post, class: 'button' if can?(:enable, account.user)
+          = button_to t('admin.accounts.enable'), enable_admin_account_path(account.id), class: :button if can?(:enable, account.user)
         - elsif can?(:disable, account.user)
           = link_to t('admin.accounts.disable'), new_admin_account_action_path(account.id, type: 'disable'), class: 'button'
       - if account.sensitized?
-        = link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsensitive, account)
+        = button_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(account.id), class: :button if can?(:unsensitive, account)
       - elsif !account.local? || account.user_approved?
         = link_to t('admin.accounts.sensitive'), new_admin_account_action_path(account.id, type: 'sensitive'), class: 'button' if can?(:sensitive, account)
       - if account.silenced?
-        = link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsilence, account)
+        = button_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(account.id), class: :button if can?(:unsilence, account)
       - elsif !account.local? || account.user_approved?
         = link_to t('admin.accounts.silence'), new_admin_account_action_path(account.id, type: 'silence'), class: 'button' if can?(:silence, account)
       - if account.local?
         - if account.user_pending?
-          = link_to t('admin.accounts.approve'), approve_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, account.user)
-          = link_to t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, account.user)
+          = button_to t('admin.accounts.approve'), approve_admin_account_path(account.id), data: { confirm: t('admin.accounts.are_you_sure') }, class: :button if can?(:approve, account.user)
+          = button_to t('admin.accounts.reject'), reject_admin_account_path(account.id), data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, account.user)
         - if !account.user_confirmed? && can?(:confirm, account.user)
-          = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(account.id), method: :post, class: 'button'
+          = button_to t('admin.accounts.confirm'), admin_account_confirmation_path(account.id), class: :button
       - if (!account.local? || account.user_approved?) && can?(:suspend, account)
         = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(account.id, type: 'suspend'), class: 'button'
     %div
       - if account.local?
         - if !account.memorial? && account.user_approved? && can?(:memorialize, account)
-          = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
+          = button_to t('admin.accounts.memorialize'), memorialize_admin_account_path(account.id), data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
       - elsif can?(:redownload, account)
-        = link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button'
+        = button_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), class: :button

From ec16ed37c39a79ae23d5b4d759f571b0fa66506b Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 07:36:25 -0400
Subject: [PATCH 17/70] Extract constants for header and avatar geometry
 (#32151)

---
 app/models/concerns/account/avatar.rb      | 7 +++++--
 app/models/concerns/account/header.rb      | 5 ++++-
 app/views/settings/profiles/show.html.haml | 4 ++--
 3 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/app/models/concerns/account/avatar.rb b/app/models/concerns/account/avatar.rb
index 39f599db1..5ca8fa862 100644
--- a/app/models/concerns/account/avatar.rb
+++ b/app/models/concerns/account/avatar.rb
@@ -6,10 +6,13 @@ module Account::Avatar
   IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
   LIMIT = 2.megabytes
 
+  AVATAR_DIMENSIONS = [400, 400].freeze
+  AVATAR_GEOMETRY = [AVATAR_DIMENSIONS.first, AVATAR_DIMENSIONS.last].join('x')
+
   class_methods do
     def avatar_styles(file)
-      styles = { original: { geometry: '400x400#', file_geometry_parser: FastGeometryParser } }
-      styles[:static] = { geometry: '400x400#', format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif'
+      styles = { original: { geometry: "#{AVATAR_GEOMETRY}#", file_geometry_parser: FastGeometryParser } }
+      styles[:static] = { geometry: "#{AVATAR_GEOMETRY}#", format: 'png', convert_options: '-coalesce', file_geometry_parser: FastGeometryParser } if file.content_type == 'image/gif'
       styles
     end
 
diff --git a/app/models/concerns/account/header.rb b/app/models/concerns/account/header.rb
index 44ae774e9..2a47097fc 100644
--- a/app/models/concerns/account/header.rb
+++ b/app/models/concerns/account/header.rb
@@ -5,7 +5,10 @@ module Account::Header
 
   IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'].freeze
   LIMIT = 2.megabytes
-  MAX_PIXELS = 750_000 # 1500x500px
+
+  HEADER_DIMENSIONS = [1500, 500].freeze
+  HEADER_GEOMETRY = [HEADER_DIMENSIONS.first, HEADER_DIMENSIONS.last].join('x')
+  MAX_PIXELS = HEADER_DIMENSIONS.first * HEADER_DIMENSIONS.last
 
   class_methods do
     def header_styles(file)
diff --git a/app/views/settings/profiles/show.html.haml b/app/views/settings/profiles/show.html.haml
index cad5aee66..427a4fa95 100644
--- a/app/views/settings/profiles/show.html.haml
+++ b/app/views/settings/profiles/show.html.haml
@@ -34,7 +34,7 @@
     .fields-row__column.fields-row__column-6
       .fields-group
         = f.input :avatar,
-                  hint: t('simple_form.hints.defaults.avatar', dimensions: '400x400', size: number_to_human_size(Account::Avatar::LIMIT)),
+                  hint: t('simple_form.hints.defaults.avatar', dimensions: Account::Avatar::AVATAR_GEOMETRY, size: number_to_human_size(Account::Avatar::LIMIT)),
                   input_html: { accept: Account::Avatar::IMAGE_MIME_TYPES.join(',') },
                   wrapper: :with_block_label
 
@@ -50,7 +50,7 @@
     .fields-row__column.fields-row__column-6
       .fields-group
         = f.input :header,
-                  hint: t('simple_form.hints.defaults.header', dimensions: '1500x500', size: number_to_human_size(Account::Header::LIMIT)),
+                  hint: t('simple_form.hints.defaults.header', dimensions: Account::Header::HEADER_GEOMETRY, size: number_to_human_size(Account::Header::LIMIT)),
                   input_html: { accept: Account::Header::IMAGE_MIME_TYPES.join(',') },
                   wrapper: :with_block_label
 

From 44071fdbae14d12bcd2c64bdc0d4162aca9de486 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 08:45:58 -0400
Subject: [PATCH 18/70] Wrap datetime in `time` element with attrs (#32177)

---
 app/views/settings/exports/show.html.haml       | 3 ++-
 app/views/settings/imports/index.html.haml      | 5 ++++-
 app/views/severed_relationships/index.html.haml | 4 +++-
 3 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml
index 320bb0c7c..273c5a4ba 100644
--- a/app/views/settings/exports/show.html.haml
+++ b/app/views/settings/exports/show.html.haml
@@ -61,7 +61,8 @@
       %tbody
         - @backups.each do |backup|
           %tr
-            %td= l backup.created_at
+            %td
+              %time.formatted{ datetime: backup.created_at.iso8601, title: l(backup.created_at) }= l backup.created_at
             - if backup.processed?
               %td= number_to_human_size backup.dump_file_size
               %td= table_link_to 'download', t('exports.archive_takeout.download'), download_backup_url(backup)
diff --git a/app/views/settings/imports/index.html.haml b/app/views/settings/imports/index.html.haml
index 634631b5a..55421991e 100644
--- a/app/views/settings/imports/index.html.haml
+++ b/app/views/settings/imports/index.html.haml
@@ -55,7 +55,10 @@
                 = t("imports.states.#{import.state}")
             %td
               #{import.imported_items} / #{import.total_items}
-            %td= l(import.created_at)
+            %td
+              %time.formatted{ datetime: import.created_at.iso8601, title: l(import.created_at) }
+                = l(import.created_at)
+
             %td
               - num_failed = import.processed_items - import.imported_items
               - if num_failed.positive?
diff --git a/app/views/severed_relationships/index.html.haml b/app/views/severed_relationships/index.html.haml
index 7c599e9c0..cc9439b46 100644
--- a/app/views/severed_relationships/index.html.haml
+++ b/app/views/severed_relationships/index.html.haml
@@ -15,7 +15,9 @@
       %tbody
         - @events.each do |event|
           %tr
-            %td= l event.created_at
+            %td
+              %time.formatted{ datetime: event.created_at.iso8601, title: l(event.created_at) }
+                = l(event.created_at)
             %td= t("severed_relationships.event_type.#{event.type}", target_name: event.target_name)
             - if event.purged?
               %td{ rowspan: 2 }= t('severed_relationships.purged')

From c5f0da98e43134d5922ea76dc89db292cd3531e8 Mon Sep 17 00:00:00 2001
From: Emelia Smith <ThisIsMissEm@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:49:04 +0200
Subject: [PATCH 19/70] Add detection and download of material_symbol icons in
 config/navigation.rb (#31366)

---
 .../material-icons/400-24px/breaking_news-fill.svg |  1 +
 .../material-icons/400-24px/breaking_news.svg      |  2 +-
 .../400-24px/captive_portal-fill.svg               |  1 +
 .../material-icons/400-24px/captive_portal.svg     |  2 +-
 .../material-icons/400-24px/chat_bubble-fill.svg   |  1 +
 .../material-icons/400-24px/chat_bubble.svg        |  2 +-
 .../material-icons/400-24px/cloud-fill.svg         |  1 +
 app/javascript/material-icons/400-24px/cloud.svg   |  2 +-
 .../400-24px/cloud_download-fill.svg               |  1 +
 .../material-icons/400-24px/cloud_download.svg     |  2 +-
 .../material-icons/400-24px/cloud_sync-fill.svg    |  1 +
 .../material-icons/400-24px/cloud_sync.svg         |  2 +-
 .../material-icons/400-24px/cloud_upload-fill.svg  |  1 +
 .../material-icons/400-24px/cloud_upload.svg       |  2 +-
 .../material-icons/400-24px/code-fill.svg          |  1 +
 app/javascript/material-icons/400-24px/code.svg    |  2 +-
 .../material-icons/400-24px/computer-fill.svg      |  1 +
 .../material-icons/400-24px/computer.svg           |  2 +-
 .../material-icons/400-24px/contact_mail-fill.svg  |  1 +
 .../material-icons/400-24px/contact_mail.svg       |  2 +-
 .../material-icons/400-24px/database-fill.svg      |  1 +
 .../material-icons/400-24px/database.svg           |  2 +-
 .../material-icons/400-24px/diamond-fill.svg       |  1 +
 app/javascript/material-icons/400-24px/diamond.svg |  2 +-
 .../material-icons/400-24px/filter_alt-fill.svg    |  1 +
 .../material-icons/400-24px/filter_alt.svg         |  2 +-
 .../material-icons/400-24px/groups-fill.svg        |  1 +
 app/javascript/material-icons/400-24px/groups.svg  |  2 +-
 .../material-icons/400-24px/hide_source-fill.svg   |  1 +
 .../material-icons/400-24px/hide_source.svg        |  2 +-
 .../material-icons/400-24px/inbox-fill.svg         |  1 +
 app/javascript/material-icons/400-24px/inbox.svg   |  2 +-
 .../material-icons/400-24px/list-fill.svg          |  1 +
 app/javascript/material-icons/400-24px/list.svg    |  2 +-
 .../material-icons/400-24px/mail-fill.svg          |  1 +
 app/javascript/material-icons/400-24px/mail.svg    |  2 +-
 .../material-icons/400-24px/mood-fill.svg          |  1 +
 app/javascript/material-icons/400-24px/mood.svg    |  2 +-
 .../material-icons/400-24px/report-fill.svg        |  1 +
 app/javascript/material-icons/400-24px/report.svg  |  2 +-
 .../material-icons/400-24px/safety_check-fill.svg  |  1 +
 .../material-icons/400-24px/safety_check.svg       |  2 +-
 .../material-icons/400-24px/speed-fill.svg         |  1 +
 app/javascript/material-icons/400-24px/speed.svg   |  2 +-
 .../material-icons/400-24px/trending_up-fill.svg   |  1 +
 .../material-icons/400-24px/trending_up.svg        |  2 +-
 lib/tasks/icons.rake                               | 14 ++++++++++++++
 47 files changed, 60 insertions(+), 23 deletions(-)
 create mode 100644 app/javascript/material-icons/400-24px/breaking_news-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/captive_portal-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/chat_bubble-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/cloud-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/cloud_download-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/cloud_sync-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/cloud_upload-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/code-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/computer-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/contact_mail-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/database-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/diamond-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/filter_alt-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/groups-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/hide_source-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/inbox-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/list-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/mail-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/mood-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/report-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/safety_check-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/speed-fill.svg
 create mode 100644 app/javascript/material-icons/400-24px/trending_up-fill.svg

diff --git a/app/javascript/material-icons/400-24px/breaking_news-fill.svg b/app/javascript/material-icons/400-24px/breaking_news-fill.svg
new file mode 100644
index 000000000..633ca48d5
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/breaking_news-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-280q17 0 28.5-11.5T320-320q0-17-11.5-28.5T280-360q-17 0-28.5 11.5T240-320q0 17 11.5 28.5T280-280Zm-40-160h80v-240h-80v240Zm200 160h280v-80H440v80Zm0-160h280v-80H440v80Zm0-160h280v-80H440v80ZM160-120q-33 0-56.5-23.5T80-200v-560q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v560q0 33-23.5 56.5T800-120H160Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/breaking_news.svg b/app/javascript/material-icons/400-24px/breaking_news.svg
index d7dd0c12f..c043f11a8 100644
--- a/app/javascript/material-icons/400-24px/breaking_news.svg
+++ b/app/javascript/material-icons/400-24px/breaking_news.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M280-280q17 0 28.5-11.5T320-320q0-17-11.5-28.5T280-360q-17 0-28.5 11.5T240-320q0 17 11.5 28.5T280-280Zm-40-160h80v-240h-80v240Zm200 160h280v-80H440v80Zm0-160h280v-80H440v80Zm0-160h280v-80H440v80ZM160-120q-33 0-56.5-23.5T80-200v-560q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v560q0 33-23.5 56.5T800-120H160Zm0-80h640v-560H160v560Zm0 0v-560 560Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-280q17 0 28.5-11.5T320-320q0-17-11.5-28.5T280-360q-17 0-28.5 11.5T240-320q0 17 11.5 28.5T280-280Zm-40-160h80v-240h-80v240Zm200 160h280v-80H440v80Zm0-160h280v-80H440v80Zm0-160h280v-80H440v80ZM160-120q-33 0-56.5-23.5T80-200v-560q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v560q0 33-23.5 56.5T800-120H160Zm0-80h640v-560H160v560Zm0 0v-560 560Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/captive_portal-fill.svg b/app/javascript/material-icons/400-24px/captive_portal-fill.svg
new file mode 100644
index 000000000..5c0b26fb6
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/captive_portal-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M838-65 720-183v89h-80v-226h226v80h-90l118 118-56 57ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 20-2 40t-6 40h-82q5-20 7.5-40t2.5-40q0-20-2.5-40t-7.5-40H654q3 20 4.5 40t1.5 40q0 20-1.5 40t-4.5 40h-80q3-20 4.5-40t1.5-40q0-20-1.5-40t-4.5-40H386q-3 20-4.5 40t-1.5 40q0 20 1.5 40t4.5 40h134v80H404q12 43 31 82.5t45 75.5q20 0 40-2.5t40-4.5v82q-20 2-40 4.5T480-80ZM170-400h136q-3-20-4.5-40t-1.5-40q0-20 1.5-40t4.5-40H170q-5 20-7.5 40t-2.5 40q0 20 2.5 40t7.5 40Zm34-240h118q9-37 22.5-72.5T376-782q-55 18-99 54.5T204-640Zm172 462q-18-34-31.5-69.5T322-320H204q29 51 73 87.5t99 54.5Zm28-462h152q-12-43-31-82.5T480-798q-26 36-45 75.5T404-640Zm234 0h118q-29-51-73-87.5T584-782q18 34 31.5 69.5T638-640Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/captive_portal.svg b/app/javascript/material-icons/400-24px/captive_portal.svg
index 1f0f09c77..5c0b26fb6 100644
--- a/app/javascript/material-icons/400-24px/captive_portal.svg
+++ b/app/javascript/material-icons/400-24px/captive_portal.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M838-65 720-183v89h-80v-226h226v80h-90l118 118-56 57ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 20-2 40t-6 40h-82q5-20 7.5-40t2.5-40q0-20-2.5-40t-7.5-40H654q3 20 4.5 40t1.5 40q0 20-1.5 40t-4.5 40h-80q3-20 4.5-40t1.5-40q0-20-1.5-40t-4.5-40H386q-3 20-4.5 40t-1.5 40q0 20 1.5 40t4.5 40h134v80H404q12 43 31 82.5t45 75.5q20 0 40-2.5t40-4.5v82q-20 2-40 4.5T480-80ZM170-400h136q-3-20-4.5-40t-1.5-40q0-20 1.5-40t4.5-40H170q-5 20-7.5 40t-2.5 40q0 20 2.5 40t7.5 40Zm34-240h118q9-37 22.5-72.5T376-782q-55 18-99 54.5T204-640Zm172 462q-18-34-31.5-69.5T322-320H204q29 51 73 87.5t99 54.5Zm28-462h152q-12-43-31-82.5T480-798q-26 36-45 75.5T404-640Zm234 0h118q-29-51-73-87.5T584-782q18 34 31.5 69.5T638-640Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M838-65 720-183v89h-80v-226h226v80h-90l118 118-56 57ZM480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 20-2 40t-6 40h-82q5-20 7.5-40t2.5-40q0-20-2.5-40t-7.5-40H654q3 20 4.5 40t1.5 40q0 20-1.5 40t-4.5 40h-80q3-20 4.5-40t1.5-40q0-20-1.5-40t-4.5-40H386q-3 20-4.5 40t-1.5 40q0 20 1.5 40t4.5 40h134v80H404q12 43 31 82.5t45 75.5q20 0 40-2.5t40-4.5v82q-20 2-40 4.5T480-80ZM170-400h136q-3-20-4.5-40t-1.5-40q0-20 1.5-40t4.5-40H170q-5 20-7.5 40t-2.5 40q0 20 2.5 40t7.5 40Zm34-240h118q9-37 22.5-72.5T376-782q-55 18-99 54.5T204-640Zm172 462q-18-34-31.5-69.5T322-320H204q29 51 73 87.5t99 54.5Zm28-462h152q-12-43-31-82.5T480-798q-26 36-45 75.5T404-640Zm234 0h118q-29-51-73-87.5T584-782q18 34 31.5 69.5T638-640Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/chat_bubble-fill.svg b/app/javascript/material-icons/400-24px/chat_bubble-fill.svg
new file mode 100644
index 000000000..b47338a6c
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/chat_bubble-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/chat_bubble.svg b/app/javascript/material-icons/400-24px/chat_bubble.svg
index 7d210b460..05d976d24 100644
--- a/app/javascript/material-icons/400-24px/chat_bubble.svg
+++ b/app/javascript/material-icons/400-24px/chat_bubble.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M80-80v-720q0-33 23.5-56.5T160-880h640q33 0 56.5 23.5T880-800v480q0 33-23.5 56.5T800-240H240L80-80Zm126-240h594v-480H160v525l46-45Zm-46 0v-480 480Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/cloud-fill.svg b/app/javascript/material-icons/400-24px/cloud-fill.svg
new file mode 100644
index 000000000..d049a74c0
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/cloud-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q25-92 100-149t170-57q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H260Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/cloud.svg b/app/javascript/material-icons/400-24px/cloud.svg
index 75b4e957f..a36bddda9 100644
--- a/app/javascript/material-icons/400-24px/cloud.svg
+++ b/app/javascript/material-icons/400-24px/cloud.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q25-92 100-149t170-57q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H260Zm0-80h480q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-83 0-141.5 58.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41Zm220-240Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q25-92 100-149t170-57q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H260Zm0-80h480q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-83 0-141.5 58.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41Zm220-240Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/cloud_download-fill.svg b/app/javascript/material-icons/400-24px/cloud_download-fill.svg
new file mode 100644
index 000000000..c55d49f7e
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/cloud_download-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q23-81 85.5-136T440-797v323l-64-62-56 56 160 160 160-160-56-56-64 62v-323q103 14 171.5 92.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H260Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/cloud_download.svg b/app/javascript/material-icons/400-24px/cloud_download.svg
index 2fc3717ff..8e9314800 100644
--- a/app/javascript/material-icons/400-24px/cloud_download.svg
+++ b/app/javascript/material-icons/400-24px/cloud_download.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q17-72 85-137t145-65q33 0 56.5 23.5T520-716v242l64-62 56 56-160 160-160-160 56-56 64 62v-242q-76 14-118 73.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41h480q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-48-22-89.5T600-680v-93q74 35 117 103.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H260Zm220-358Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q17-72 85-137t145-65q33 0 56.5 23.5T520-716v242l64-62 56 56-160 160-160-160 56-56 64 62v-242q-76 14-118 73.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41h480q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-48-22-89.5T600-680v-93q74 35 117 103.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H260Zm220-358Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/cloud_sync-fill.svg b/app/javascript/material-icons/400-24px/cloud_sync-fill.svg
new file mode 100644
index 000000000..0c648e19e
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/cloud_sync-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M160-160v-80h109q-51-44-80-106t-29-134q0-112 68-197.5T400-790v84q-70 25-115 86.5T240-480q0 54 21.5 99.5T320-302v-98h80v240H160Zm440 0q-50 0-85-35t-35-85q0-48 33-82.5t81-36.5q17-36 50.5-58.5T720-480q53 0 91.5 34.5T858-360q42 0 72 29t30 70q0 42-29 71.5T860-160H600Zm116-360q-7-41-27-76t-49-62v98h-80v-240h240v80H691q43 38 70.5 89T797-520h-81Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/cloud_sync.svg b/app/javascript/material-icons/400-24px/cloud_sync.svg
index dbf6adc00..461796e32 100644
--- a/app/javascript/material-icons/400-24px/cloud_sync.svg
+++ b/app/javascript/material-icons/400-24px/cloud_sync.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M160-160v-80h109q-51-44-80-106t-29-134q0-112 68-197.5T400-790v84q-70 25-115 86.5T240-480q0 54 21.5 99.5T320-302v-98h80v240H160Zm440 0q-50 0-85-35t-35-85q0-48 33-82.5t81-36.5q17-36 50.5-58.5T720-480q53 0 91.5 34.5T858-360q42 0 72 29t30 70q0 42-29 71.5T860-160H600Zm116-360q-7-41-27-76t-49-62v98h-80v-240h240v80H691q43 38 70.5 89T797-520h-81ZM600-240h260q8 0 14-6t6-14q0-8-6-14t-14-6h-70v-50q0-29-20.5-49.5T720-400q-29 0-49.5 20.5T650-330v10h-50q-17 0-28.5 11.5T560-280q0 17 11.5 28.5T600-240Zm120-80Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M160-160v-80h109q-51-44-80-106t-29-134q0-112 68-197.5T400-790v84q-70 25-115 86.5T240-480q0 54 21.5 99.5T320-302v-98h80v240H160Zm440 0q-50 0-85-35t-35-85q0-48 33-82.5t81-36.5q17-36 50.5-58.5T720-480q53 0 91.5 34.5T858-360q42 0 72 29t30 70q0 42-29 71.5T860-160H600Zm116-360q-7-41-27-76t-49-62v98h-80v-240h240v80H691q43 38 70.5 89T797-520h-81ZM600-240h260q8 0 14-6t6-14q0-8-6-14t-14-6h-70v-50q0-29-20.5-49.5T720-400q-29 0-49.5 20.5T650-330v10h-50q-17 0-28.5 11.5T560-280q0 17 11.5 28.5T600-240Zm120-80Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/cloud_upload-fill.svg b/app/javascript/material-icons/400-24px/cloud_upload-fill.svg
new file mode 100644
index 000000000..66a7bb22d
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/cloud_upload-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M440-160H260q-91 0-155.5-63T40-377q0-78 47-139t123-78q25-92 100-149t170-57q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H520v-286l64 62 56-56-160-160-160 160 56 56 64-62v286Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/cloud_upload.svg b/app/javascript/material-icons/400-24px/cloud_upload.svg
index 5e1a4b9ae..94968cb94 100644
--- a/app/javascript/material-icons/400-24px/cloud_upload.svg
+++ b/app/javascript/material-icons/400-24px/cloud_upload.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q25-92 100-149t170-57q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H520q-33 0-56.5-23.5T440-240v-206l-64 62-56-56 160-160 160 160-56 56-64-62v206h220q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-83 0-141.5 58.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41h100v80H260Zm220-280Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M260-160q-91 0-155.5-63T40-377q0-78 47-139t123-78q25-92 100-149t170-57q117 0 198.5 81.5T760-520q69 8 114.5 59.5T920-340q0 75-52.5 127.5T740-160H520q-33 0-56.5-23.5T440-240v-206l-64 62-56-56 160-160 160 160-56 56-64-62v206h220q42 0 71-29t29-71q0-42-29-71t-71-29h-60v-80q0-83-58.5-141.5T480-720q-83 0-141.5 58.5T280-520h-20q-58 0-99 41t-41 99q0 58 41 99t99 41h100v80H260Zm220-280Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/code-fill.svg b/app/javascript/material-icons/400-24px/code-fill.svg
new file mode 100644
index 000000000..8ef5c55cd
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/code-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M320-240 80-480l240-240 57 57-184 184 183 183-56 56Zm320 0-57-57 184-184-183-183 56-56 240 240-240 240Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/code.svg b/app/javascript/material-icons/400-24px/code.svg
index 5bdc338f7..8ef5c55cd 100644
--- a/app/javascript/material-icons/400-24px/code.svg
+++ b/app/javascript/material-icons/400-24px/code.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M320-240 80-480l240-240 57 57-184 184 183 183-56 56Zm320 0-57-57 184-184-183-183 56-56 240 240-240 240Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M320-240 80-480l240-240 57 57-184 184 183 183-56 56Zm320 0-57-57 184-184-183-183 56-56 240 240-240 240Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/computer-fill.svg b/app/javascript/material-icons/400-24px/computer-fill.svg
new file mode 100644
index 000000000..91295d684
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/computer-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M160-240q-33 0-56.5-23.5T80-320v-440q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v440q0 33-23.5 56.5T800-240H160ZM40-120v-80h880v80H40Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/computer.svg b/app/javascript/material-icons/400-24px/computer.svg
index 8c5bd9110..b8af5d464 100644
--- a/app/javascript/material-icons/400-24px/computer.svg
+++ b/app/javascript/material-icons/400-24px/computer.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M40-120v-80h880v80H40Zm120-120q-33 0-56.5-23.5T80-320v-440q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v440q0 33-23.5 56.5T800-240H160Zm0-80h640v-440H160v440Zm0 0v-440 440Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M40-120v-80h880v80H40Zm120-120q-33 0-56.5-23.5T80-320v-440q0-33 23.5-56.5T160-840h640q33 0 56.5 23.5T880-760v440q0 33-23.5 56.5T800-240H160Zm0-80h640v-440H160v440Zm0 0v-440 440Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/contact_mail-fill.svg b/app/javascript/material-icons/400-24px/contact_mail-fill.svg
new file mode 100644
index 000000000..c42c79995
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/contact_mail-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M560-520h280v-200H560v200Zm140-50-100-70v-40l100 70 100-70v40l-100 70ZM80-120q-33 0-56.5-23.5T0-200v-560q0-33 23.5-56.5T80-840h800q33 0 56.5 23.5T960-760v560q0 33-23.5 56.5T880-120H80Zm280-280q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM84-200h552q-42-75-116-117.5T360-360q-86 0-160 42.5T84-200Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/contact_mail.svg b/app/javascript/material-icons/400-24px/contact_mail.svg
index 1ae26cc4d..4547c48ec 100644
--- a/app/javascript/material-icons/400-24px/contact_mail.svg
+++ b/app/javascript/material-icons/400-24px/contact_mail.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M560-520h280v-200H560v200Zm140-50-100-70v-40l100 70 100-70v40l-100 70ZM80-120q-33 0-56.5-23.5T0-200v-560q0-33 23.5-56.5T80-840h800q33 0 56.5 23.5T960-760v560q0 33-23.5 56.5T880-120H80Zm556-80h244v-560H80v560h4q42-75 116-117.5T360-360q86 0 160 42.5T636-200ZM360-400q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM182-200h356q-34-38-80.5-59T360-280q-51 0-97 21t-81 59Zm178-280q-17 0-28.5-11.5T320-520q0-17 11.5-28.5T360-560q17 0 28.5 11.5T400-520q0 17-11.5 28.5T360-480Zm120 0Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M560-520h280v-200H560v200Zm140-50-100-70v-40l100 70 100-70v40l-100 70ZM80-120q-33 0-56.5-23.5T0-200v-560q0-33 23.5-56.5T80-840h800q33 0 56.5 23.5T960-760v560q0 33-23.5 56.5T880-120H80Zm556-80h244v-560H80v560h4q42-75 116-117.5T360-360q86 0 160 42.5T636-200ZM360-400q50 0 85-35t35-85q0-50-35-85t-85-35q-50 0-85 35t-35 85q0 50 35 85t85 35ZM182-200h356q-34-38-80.5-59T360-280q-51 0-97 21t-81 59Zm178-280q-17 0-28.5-11.5T320-520q0-17 11.5-28.5T360-560q17 0 28.5 11.5T400-520q0 17-11.5 28.5T360-480Zm120 0Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/database-fill.svg b/app/javascript/material-icons/400-24px/database-fill.svg
new file mode 100644
index 000000000..3520f6961
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/database-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-520q150 0 255-47t105-113q0-66-105-113t-255-47q-150 0-255 47T120-680q0 66 105 113t255 47Zm0 100q41 0 102.5-8.5T701-456q57-19 98-49.5t41-74.5v100q0 44-41 74.5T701-356q-57 19-118.5 27.5T480-320q-41 0-102.5-8.5T259-356q-57-19-98-49.5T120-480v-100q0 44 41 74.5t98 49.5q57 19 118.5 27.5T480-420Zm0 200q41 0 102.5-8.5T701-256q57-19 98-49.5t41-74.5v100q0 44-41 74.5T701-156q-57 19-118.5 27.5T480-120q-41 0-102.5-8.5T259-156q-57-19-98-49.5T120-280v-100q0 44 41 74.5t98 49.5q57 19 118.5 27.5T480-220Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/database.svg b/app/javascript/material-icons/400-24px/database.svg
index 54ca2f4e5..a3bc2bfbc 100644
--- a/app/javascript/material-icons/400-24px/database.svg
+++ b/app/javascript/material-icons/400-24px/database.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M480-120q-151 0-255.5-46.5T120-280v-400q0-66 105.5-113T480-840q149 0 254.5 47T840-680v400q0 67-104.5 113.5T480-120Zm0-479q89 0 179-25.5T760-679q-11-29-100.5-55T480-760q-91 0-178.5 25.5T200-679q14 30 101.5 55T480-599Zm0 199q42 0 81-4t74.5-11.5q35.5-7.5 67-18.5t57.5-25v-120q-26 14-57.5 25t-67 18.5Q600-528 561-524t-81 4q-42 0-82-4t-75.5-11.5Q287-543 256-554t-56-25v120q25 14 56 25t66.5 18.5Q358-408 398-404t82 4Zm0 200q46 0 93.5-7t87.5-18.5q40-11.5 67-26t32-29.5v-98q-26 14-57.5 25t-67 18.5Q600-328 561-324t-81 4q-42 0-82-4t-75.5-11.5Q287-343 256-354t-56-25v99q5 15 31.5 29t66.5 25.5q40 11.5 88 18.5t94 7Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-120q-151 0-255.5-46.5T120-280v-400q0-66 105.5-113T480-840q149 0 254.5 47T840-680v400q0 67-104.5 113.5T480-120Zm0-479q89 0 179-25.5T760-679q-11-29-100.5-55T480-760q-91 0-178.5 25.5T200-679q14 30 101.5 55T480-599Zm0 199q42 0 81-4t74.5-11.5q35.5-7.5 67-18.5t57.5-25v-120q-26 14-57.5 25t-67 18.5Q600-528 561-524t-81 4q-42 0-82-4t-75.5-11.5Q287-543 256-554t-56-25v120q25 14 56 25t66.5 18.5Q358-408 398-404t82 4Zm0 200q46 0 93.5-7t87.5-18.5q40-11.5 67-26t32-29.5v-98q-26 14-57.5 25t-67 18.5Q600-328 561-324t-81 4q-42 0-82-4t-75.5-11.5Q287-343 256-354t-56-25v99q5 15 31.5 29t66.5 25.5q40 11.5 88 18.5t94 7Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/diamond-fill.svg b/app/javascript/material-icons/400-24px/diamond-fill.svg
new file mode 100644
index 000000000..474968ad6
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/diamond-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m368-630 106-210h12l106 210H368Zm82 474L105-570h345v414Zm60 0v-414h345L510-156Zm148-474L554-840h206l105 210H658Zm-563 0 105-210h206L302-630H95Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/diamond.svg b/app/javascript/material-icons/400-24px/diamond.svg
index 26f4814b4..b604492fa 100644
--- a/app/javascript/material-icons/400-24px/diamond.svg
+++ b/app/javascript/material-icons/400-24px/diamond.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M480-120 80-600l120-240h560l120 240-400 480Zm-95-520h190l-60-120h-70l-60 120Zm55 347v-267H218l222 267Zm80 0 222-267H520v267Zm144-347h106l-60-120H604l60 120Zm-474 0h106l60-120H250l-60 120Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-120 80-600l120-240h560l120 240-400 480Zm-95-520h190l-60-120h-70l-60 120Zm55 347v-267H218l222 267Zm80 0 222-267H520v267Zm144-347h106l-60-120H604l60 120Zm-474 0h106l60-120H250l-60 120Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/filter_alt-fill.svg b/app/javascript/material-icons/400-24px/filter_alt-fill.svg
new file mode 100644
index 000000000..ec1d90bba
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/filter_alt-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M440-160q-17 0-28.5-11.5T400-200v-240L168-736q-15-20-4.5-42t36.5-22h560q26 0 36.5 22t-4.5 42L560-440v240q0 17-11.5 28.5T520-160h-80Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/filter_alt.svg b/app/javascript/material-icons/400-24px/filter_alt.svg
index 0294cf1da..e4af9efd5 100644
--- a/app/javascript/material-icons/400-24px/filter_alt.svg
+++ b/app/javascript/material-icons/400-24px/filter_alt.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M440-160q-17 0-28.5-11.5T400-200v-240L168-736q-15-20-4.5-42t36.5-22h560q26 0 36.5 22t-4.5 42L560-440v240q0 17-11.5 28.5T520-160h-80Zm40-308 198-252H282l198 252Zm0 0Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M440-160q-17 0-28.5-11.5T400-200v-240L168-736q-15-20-4.5-42t36.5-22h560q26 0 36.5 22t-4.5 42L560-440v240q0 17-11.5 28.5T520-160h-80Zm40-308 198-252H282l198 252Zm0 0Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/groups-fill.svg b/app/javascript/material-icons/400-24px/groups-fill.svg
new file mode 100644
index 000000000..754eb0946
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/groups-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M0-240v-63q0-43 44-70t116-27q13 0 25 .5t23 2.5q-14 21-21 44t-7 48v65H0Zm240 0v-65q0-32 17.5-58.5T307-410q32-20 76.5-30t96.5-10q53 0 97.5 10t76.5 30q32 20 49 46.5t17 58.5v65H240Zm540 0v-65q0-26-6.5-49T754-397q11-2 22.5-2.5t23.5-.5q72 0 116 26.5t44 70.5v63H780ZM160-440q-33 0-56.5-23.5T80-520q0-34 23.5-57t56.5-23q34 0 57 23t23 57q0 33-23 56.5T160-440Zm640 0q-33 0-56.5-23.5T720-520q0-34 23.5-57t56.5-23q34 0 57 23t23 57q0 33-23 56.5T800-440Zm-320-40q-50 0-85-35t-35-85q0-51 35-85.5t85-34.5q51 0 85.5 34.5T600-600q0 50-34.5 85T480-480Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/groups.svg b/app/javascript/material-icons/400-24px/groups.svg
index 0e795eb30..998ff0372 100644
--- a/app/javascript/material-icons/400-24px/groups.svg
+++ b/app/javascript/material-icons/400-24px/groups.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M0-240v-63q0-43 44-70t116-27q13 0 25 .5t23 2.5q-14 21-21 44t-7 48v65H0Zm240 0v-65q0-32 17.5-58.5T307-410q32-20 76.5-30t96.5-10q53 0 97.5 10t76.5 30q32 20 49 46.5t17 58.5v65H240Zm540 0v-65q0-26-6.5-49T754-397q11-2 22.5-2.5t23.5-.5q72 0 116 26.5t44 70.5v63H780Zm-455-80h311q-10-20-55.5-35T480-370q-55 0-100.5 15T325-320ZM160-440q-33 0-56.5-23.5T80-520q0-34 23.5-57t56.5-23q34 0 57 23t23 57q0 33-23 56.5T160-440Zm640 0q-33 0-56.5-23.5T720-520q0-34 23.5-57t56.5-23q34 0 57 23t23 57q0 33-23 56.5T800-440Zm-320-40q-50 0-85-35t-35-85q0-51 35-85.5t85-34.5q51 0 85.5 34.5T600-600q0 50-34.5 85T480-480Zm0-80q17 0 28.5-11.5T520-600q0-17-11.5-28.5T480-640q-17 0-28.5 11.5T440-600q0 17 11.5 28.5T480-560Zm1 240Zm-1-280Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M0-240v-63q0-43 44-70t116-27q13 0 25 .5t23 2.5q-14 21-21 44t-7 48v65H0Zm240 0v-65q0-32 17.5-58.5T307-410q32-20 76.5-30t96.5-10q53 0 97.5 10t76.5 30q32 20 49 46.5t17 58.5v65H240Zm540 0v-65q0-26-6.5-49T754-397q11-2 22.5-2.5t23.5-.5q72 0 116 26.5t44 70.5v63H780Zm-455-80h311q-10-20-55.5-35T480-370q-55 0-100.5 15T325-320ZM160-440q-33 0-56.5-23.5T80-520q0-34 23.5-57t56.5-23q34 0 57 23t23 57q0 33-23 56.5T160-440Zm640 0q-33 0-56.5-23.5T720-520q0-34 23.5-57t56.5-23q34 0 57 23t23 57q0 33-23 56.5T800-440Zm-320-40q-50 0-85-35t-35-85q0-51 35-85.5t85-34.5q51 0 85.5 34.5T600-600q0 50-34.5 85T480-480Zm0-80q17 0 28.5-11.5T520-600q0-17-11.5-28.5T480-640q-17 0-28.5 11.5T440-600q0 17 11.5 28.5T480-560Zm1 240Zm-1-280Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/hide_source-fill.svg b/app/javascript/material-icons/400-24px/hide_source-fill.svg
new file mode 100644
index 000000000..959631bc1
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/hide_source-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m791-55-91-91q-49 32-104.5 49T480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-60 17-115.5T146-700l-91-91 57-57 736 736-57 57Zm23-205L260-814q49-32 104.5-49T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 60-17 115.5T814-260Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/hide_source.svg b/app/javascript/material-icons/400-24px/hide_source.svg
index d103ed770..09633cef8 100644
--- a/app/javascript/material-icons/400-24px/hide_source.svg
+++ b/app/javascript/material-icons/400-24px/hide_source.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="m791-55-91-91q-49 32-104.5 49T480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-60 17-115.5T146-700l-91-91 57-57 736 736-57 57ZM480-160q43 0 83.5-11t78.5-33L204-642q-22 38-33 78.5T160-480q0 133 93.5 226.5T480-160Zm334-100-58-58q22-38 33-78.5t11-83.5q0-133-93.5-226.5T480-800q-43 0-83.5 11T318-756l-58-58q49-32 104.5-49T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 60-17 115.5T814-260ZM537-537ZM423-423Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m791-55-91-91q-49 32-104.5 49T480-80q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-60 17-115.5T146-700l-91-91 57-57 736 736-57 57ZM480-160q43 0 83.5-11t78.5-33L204-642q-22 38-33 78.5T160-480q0 133 93.5 226.5T480-160Zm334-100-58-58q22-38 33-78.5t11-83.5q0-133-93.5-226.5T480-800q-43 0-83.5 11T318-756l-58-58q49-32 104.5-49T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 60-17 115.5T814-260ZM537-537ZM423-423Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/inbox-fill.svg b/app/javascript/material-icons/400-24px/inbox-fill.svg
new file mode 100644
index 000000000..15ae2d8f3
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/inbox-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm280-200q38 0 69-22t43-58h168v-360H200v360h168q12 36 43 58t69 22Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/inbox.svg b/app/javascript/material-icons/400-24px/inbox.svg
index 427817958..32c727e81 100644
--- a/app/javascript/material-icons/400-24px/inbox.svg
+++ b/app/javascript/material-icons/400-24px/inbox.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-120H640q-30 38-71.5 59T480-240q-47 0-88.5-21T320-320H200v120Zm280-120q38 0 69-22t43-58h168v-360H200v360h168q12 36 43 58t69 22ZM200-200h560-560Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-80h560v-120H640q-30 38-71.5 59T480-240q-47 0-88.5-21T320-320H200v120Zm280-120q38 0 69-22t43-58h168v-360H200v360h168q12 36 43 58t69 22ZM200-200h560-560Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/list-fill.svg b/app/javascript/material-icons/400-24px/list-fill.svg
new file mode 100644
index 000000000..c9cbe35eb
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/list-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-600v-80h560v80H280Zm0 160v-80h560v80H280Zm0 160v-80h560v80H280ZM160-600q-17 0-28.5-11.5T120-640q0-17 11.5-28.5T160-680q17 0 28.5 11.5T200-640q0 17-11.5 28.5T160-600Zm0 160q-17 0-28.5-11.5T120-480q0-17 11.5-28.5T160-520q17 0 28.5 11.5T200-480q0 17-11.5 28.5T160-440Zm0 160q-17 0-28.5-11.5T120-320q0-17 11.5-28.5T160-360q17 0 28.5 11.5T200-320q0 17-11.5 28.5T160-280Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/list.svg b/app/javascript/material-icons/400-24px/list.svg
index 457a820ab..c9cbe35eb 100644
--- a/app/javascript/material-icons/400-24px/list.svg
+++ b/app/javascript/material-icons/400-24px/list.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M280-600v-80h560v80H280Zm0 160v-80h560v80H280Zm0 160v-80h560v80H280ZM160-600q-17 0-28.5-11.5T120-640q0-17 11.5-28.5T160-680q17 0 28.5 11.5T200-640q0 17-11.5 28.5T160-600Zm0 160q-17 0-28.5-11.5T120-480q0-17 11.5-28.5T160-520q17 0 28.5 11.5T200-480q0 17-11.5 28.5T160-440Zm0 160q-17 0-28.5-11.5T120-320q0-17 11.5-28.5T160-360q17 0 28.5 11.5T200-320q0 17-11.5 28.5T160-280Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M280-600v-80h560v80H280Zm0 160v-80h560v80H280Zm0 160v-80h560v80H280ZM160-600q-17 0-28.5-11.5T120-640q0-17 11.5-28.5T160-680q17 0 28.5 11.5T200-640q0 17-11.5 28.5T160-600Zm0 160q-17 0-28.5-11.5T120-480q0-17 11.5-28.5T160-520q17 0 28.5 11.5T200-480q0 17-11.5 28.5T160-440Zm0 160q-17 0-28.5-11.5T120-320q0-17 11.5-28.5T160-360q17 0 28.5 11.5T200-320q0 17-11.5 28.5T160-280Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/mail-fill.svg b/app/javascript/material-icons/400-24px/mail-fill.svg
new file mode 100644
index 000000000..5e7e4a2fb
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/mail-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280 320-200v-80L480-520 160-720v80l320 200Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/mail.svg b/app/javascript/material-icons/400-24px/mail.svg
index a92ea7b19..15e1d12d4 100644
--- a/app/javascript/material-icons/400-24px/mail.svg
+++ b/app/javascript/material-icons/400-24px/mail.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h640q33 0 56.5 23.5T880-720v480q0 33-23.5 56.5T800-160H160Zm320-280L160-640v400h640v-400L480-440Zm0-80 320-200H160l320 200ZM160-640v-80 480-400Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/mood-fill.svg b/app/javascript/material-icons/400-24px/mood-fill.svg
new file mode 100644
index 000000000..9480d0fb9
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/mood-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M620-520q25 0 42.5-17.5T680-580q0-25-17.5-42.5T620-640q-25 0-42.5 17.5T560-580q0 25 17.5 42.5T620-520Zm-280 0q25 0 42.5-17.5T400-580q0-25-17.5-42.5T340-640q-25 0-42.5 17.5T280-580q0 25 17.5 42.5T340-520Zm140 260q68 0 123.5-38.5T684-400H276q25 63 80.5 101.5T480-260Zm0 180q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/mood.svg b/app/javascript/material-icons/400-24px/mood.svg
index 27b353424..46cafa768 100644
--- a/app/javascript/material-icons/400-24px/mood.svg
+++ b/app/javascript/material-icons/400-24px/mood.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M620-520q25 0 42.5-17.5T680-580q0-25-17.5-42.5T620-640q-25 0-42.5 17.5T560-580q0 25 17.5 42.5T620-520Zm-280 0q25 0 42.5-17.5T400-580q0-25-17.5-42.5T340-640q-25 0-42.5 17.5T280-580q0 25 17.5 42.5T340-520Zm140 260q68 0 123.5-38.5T684-400H276q25 63 80.5 101.5T480-260Zm0 180q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M620-520q25 0 42.5-17.5T680-580q0-25-17.5-42.5T620-640q-25 0-42.5 17.5T560-580q0 25 17.5 42.5T620-520Zm-280 0q25 0 42.5-17.5T400-580q0-25-17.5-42.5T340-640q-25 0-42.5 17.5T280-580q0 25 17.5 42.5T340-520Zm140 260q68 0 123.5-38.5T684-400H276q25 63 80.5 101.5T480-260Zm0 180q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-400Zm0 320q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/report-fill.svg b/app/javascript/material-icons/400-24px/report-fill.svg
new file mode 100644
index 000000000..50c638869
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/report-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm-40-160h80v-240h-80v240ZM330-120 120-330v-300l210-210h300l210 210v300L630-120H330Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/report.svg b/app/javascript/material-icons/400-24px/report.svg
index f281f0e1f..b08b5a1c9 100644
--- a/app/javascript/material-icons/400-24px/report.svg
+++ b/app/javascript/material-icons/400-24px/report.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm-40-160h80v-240h-80v240ZM330-120 120-330v-300l210-210h300l210 210v300L630-120H330Zm34-80h232l164-164v-232L596-760H364L200-596v232l164 164Zm116-280Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-280q17 0 28.5-11.5T520-320q0-17-11.5-28.5T480-360q-17 0-28.5 11.5T440-320q0 17 11.5 28.5T480-280Zm-40-160h80v-240h-80v240ZM330-120 120-330v-300l210-210h300l210 210v300L630-120H330Zm34-80h232l164-164v-232L596-760H364L200-596v232l164 164Zm116-280Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/safety_check-fill.svg b/app/javascript/material-icons/400-24px/safety_check-fill.svg
new file mode 100644
index 000000000..b38091a8e
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/safety_check-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-80q-139-35-229.5-159.5T160-516v-244l320-120 320 120v244q0 152-90.5 276.5T480-80Zm0-200q83 0 141.5-58.5T680-480q0-83-58.5-141.5T480-680q-83 0-141.5 58.5T280-480q0 83 58.5 141.5T480-280Zm66-106-86-86v-128h40v112l74 74-28 28Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/safety_check.svg b/app/javascript/material-icons/400-24px/safety_check.svg
index f4eab46fb..87bdba21f 100644
--- a/app/javascript/material-icons/400-24px/safety_check.svg
+++ b/app/javascript/material-icons/400-24px/safety_check.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M480-280q83 0 141.5-58.5T680-480q0-83-58.5-141.5T480-680q-83 0-141.5 58.5T280-480q0 83 58.5 141.5T480-280Zm66-106-86-86v-128h40v112l74 74-28 28ZM480-80q-139-35-229.5-159.5T160-516v-244l320-120 320 120v244q0 152-90.5 276.5T480-80Zm0-84q104-33 172-132t68-220v-189l-240-90-240 90v189q0 121 68 220t172 132Zm0-316Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M480-280q83 0 141.5-58.5T680-480q0-83-58.5-141.5T480-680q-83 0-141.5 58.5T280-480q0 83 58.5 141.5T480-280Zm66-106-86-86v-128h40v112l74 74-28 28ZM480-80q-139-35-229.5-159.5T160-516v-244l320-120 320 120v244q0 152-90.5 276.5T480-80Zm0-84q104-33 172-132t68-220v-189l-240-90-240 90v189q0 121 68 220t172 132Zm0-316Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/speed-fill.svg b/app/javascript/material-icons/400-24px/speed-fill.svg
new file mode 100644
index 000000000..dca22ac52
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/speed-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M418-340q25 25 63 23.5t55-27.5l224-336-336 224q-26 18-28.5 54.5T418-340ZM204-160q-22 0-40.5-9.5T134-198q-26-47-40-97.5T80-400q0-83 31.5-156T197-683q54-54 127-85.5T480-800q82 0 154 31t126 84.5q54 53.5 86 125T879-406q1 55-12.5 107.5T825-198q-11 19-29.5 28.5T755-160H204Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/speed.svg b/app/javascript/material-icons/400-24px/speed.svg
index ceb855c68..0837877f4 100644
--- a/app/javascript/material-icons/400-24px/speed.svg
+++ b/app/javascript/material-icons/400-24px/speed.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="M418-340q24 24 62 23.5t56-27.5l224-336-336 224q-27 18-28.5 55t22.5 61Zm62-460q59 0 113.5 16.5T696-734l-76 48q-33-17-68.5-25.5T480-720q-133 0-226.5 93.5T160-400q0 42 11.5 83t32.5 77h552q23-38 33.5-79t10.5-85q0-36-8.5-70T766-540l48-76q30 47 47.5 100T880-406q1 57-13 109t-41 99q-11 18-30 28t-40 10H204q-21 0-40-10t-30-28q-26-45-40-95.5T80-400q0-83 31.5-155.5t86-127Q252-737 325-768.5T480-800Zm7 313Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M418-340q24 24 62 23.5t56-27.5l224-336-336 224q-27 18-28.5 55t22.5 61Zm62-460q59 0 113.5 16.5T696-734l-76 48q-33-17-68.5-25.5T480-720q-133 0-226.5 93.5T160-400q0 42 11.5 83t32.5 77h552q23-38 33.5-79t10.5-85q0-36-8.5-70T766-540l48-76q30 47 47.5 100T880-406q1 57-13 109t-41 99q-11 18-30 28t-40 10H204q-21 0-40-10t-30-28q-26-45-40-95.5T80-400q0-83 31.5-155.5t86-127Q252-737 325-768.5T480-800Zm7 313Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/trending_up-fill.svg b/app/javascript/material-icons/400-24px/trending_up-fill.svg
new file mode 100644
index 000000000..cd0e36896
--- /dev/null
+++ b/app/javascript/material-icons/400-24px/trending_up-fill.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m136-240-56-56 296-298 160 160 208-206H640v-80h240v240h-80v-104L536-320 376-480 136-240Z"/></svg>
\ No newline at end of file
diff --git a/app/javascript/material-icons/400-24px/trending_up.svg b/app/javascript/material-icons/400-24px/trending_up.svg
index 06f9ba206..cd0e36896 100644
--- a/app/javascript/material-icons/400-24px/trending_up.svg
+++ b/app/javascript/material-icons/400-24px/trending_up.svg
@@ -1 +1 @@
-<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#5f6368"><path d="m136-240-56-56 296-298 160 160 208-206H640v-80h240v240h-80v-104L536-320 376-480 136-240Z"/></svg>
\ No newline at end of file
+<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="m136-240-56-56 296-298 160 160 208-206H640v-80h240v240h-80v-104L536-320 376-480 136-240Z"/></svg>
\ No newline at end of file
diff --git a/lib/tasks/icons.rake b/lib/tasks/icons.rake
index 96e0a1431..9a05cf349 100644
--- a/lib/tasks/icons.rake
+++ b/lib/tasks/icons.rake
@@ -38,6 +38,20 @@ def find_used_icons
     end
   end
 
+  Rails.root.join('config', 'navigation.rb').open('r') do |file|
+    pattern = /material_symbol\('(?<icon>[^']*)'\)/
+    file.each_line do |line|
+      match = pattern.match(line)
+      next if match.blank?
+
+      # navigation.rb only uses 400x24 icons, per material_symbol() in
+      # app/helpers/application_helper.rb
+      icons_by_weight_and_size[400] ||= {}
+      icons_by_weight_and_size[400][24] ||= Set.new
+      icons_by_weight_and_size[400][24] << match['icon']
+    end
+  end
+
   icons_by_weight_and_size
 end
 

From 7b92cf3b4771b42ff3895f247c006679e9a11ff6 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Tue, 1 Oct 2024 14:52:13 +0200
Subject: [PATCH 20/70] Fix unneeded requests to blocked domains when receiving
 relayed signed activities from them (#31161)

---
 app/services/activitypub/process_collection_service.rb | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/app/services/activitypub/process_collection_service.rb b/app/services/activitypub/process_collection_service.rb
index 4f049a5ae..cadc7d2d1 100644
--- a/app/services/activitypub/process_collection_service.rb
+++ b/app/services/activitypub/process_collection_service.rb
@@ -2,6 +2,7 @@
 
 class ActivityPub::ProcessCollectionService < BaseService
   include JsonLdHelper
+  include DomainControlHelper
 
   def call(body, actor, **options)
     @account = actor
@@ -69,6 +70,9 @@ class ActivityPub::ProcessCollectionService < BaseService
   end
 
   def verify_account!
+    return unless @json['signature'].is_a?(Hash)
+    return if domain_not_allowed?(@json['signature']['creator'])
+
     @options[:relayed_through_actor] = @account
     @account = ActivityPub::LinkedDataSignature.new(@json).verify_actor!
     @account = nil unless @account.is_a?(Account)

From f73787a4b84eb23ccad9d425b52aa09e1c26e18c Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 09:38:29 -0400
Subject: [PATCH 21/70] Fix broken border on applications list (#32147)

---
 app/views/settings/applications/index.html.haml | 4 +++-
 app/views/settings/applications/show.html.haml  | 3 ++-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/app/views/settings/applications/index.html.haml b/app/views/settings/applications/index.html.haml
index 80eaed5c3..e3011947a 100644
--- a/app/views/settings/applications/index.html.haml
+++ b/app/views/settings/applications/index.html.haml
@@ -18,7 +18,9 @@
         - @applications.each do |application|
           %tr
             %td= link_to application.name, settings_application_path(application)
-            %th= application.scopes
+            %th
+              - application.scopes.to_s.split.each do |scope|
+                = tag.samp(scope, class: 'information-badge', title: t("doorkeeper.scopes.#{scope}"))
             %td
               = table_link_to 'close', t('doorkeeper.applications.index.delete'), settings_application_path(application), method: :delete, data: { confirm: t('doorkeeper.applications.confirmations.destroy') }
 
diff --git a/app/views/settings/applications/show.html.haml b/app/views/settings/applications/show.html.haml
index 099e0d96a..bfde27daa 100644
--- a/app/views/settings/applications/show.html.haml
+++ b/app/views/settings/applications/show.html.haml
@@ -15,10 +15,11 @@
         %td
           %code= @application.secret
       %tr
-        %th{ rowspan: 2 }= t('applications.your_token')
+        %th= t('applications.your_token')
         %td
           %code= current_user.token_for_app(@application).token
       %tr
+        %th
         %td= table_link_to 'refresh', t('applications.regenerate_token'), regenerate_settings_application_path(@application), method: :post
 
 %hr/

From f10d61bf86559bc1464b2e7972b8980ae789908d Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 09:41:25 -0400
Subject: [PATCH 22/70] Adjust spacing on setting sub-nav items when below
 mobile size (#32137)

---
 app/javascript/styles/mastodon/admin.scss | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index da76fa110..0712a0d3f 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -226,6 +226,10 @@ $content-width: 840px;
           gap: 5px;
           white-space: nowrap;
 
+          @media screen and (max-width: $mobile-breakpoint) {
+            flex: 1 0 50%;
+          }
+
           &:hover,
           &:focus,
           &:active {

From df98cf71e30063ece4022ae4a8a73c21dfea5f74 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 09:50:20 -0400
Subject: [PATCH 23/70] Avoid repeated icon stack in settings sidebar (#32201)

---
 config/navigation.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/navigation.rb b/config/navigation.rb
index 09a7b55b5..7ec7ecb7e 100644
--- a/config/navigation.rb
+++ b/config/navigation.rb
@@ -24,7 +24,7 @@ SimpleNavigation::Configuration.run do |navigation|
     n.item :filters, safe_join([material_symbol('filter_alt'), t('filters.index.title')]), filters_path, highlights_on: %r{/filters}, if: -> { current_user.functional? && !self_destruct }
     n.item :statuses_cleanup, safe_join([material_symbol('history'), t('settings.statuses_cleanup')]), statuses_cleanup_path, if: -> { current_user.functional_or_moved? && !self_destruct }
 
-    n.item :security, safe_join([material_symbol('lock'), t('settings.account')]), edit_user_registration_path do |s|
+    n.item :security, safe_join([material_symbol('account_circle'), t('settings.account')]), edit_user_registration_path do |s|
       s.item :password, safe_join([material_symbol('lock'), t('settings.account_settings')]), edit_user_registration_path, highlights_on: %r{^/auth|/settings/delete|/settings/migration|/settings/aliases|/settings/login_activities|^/disputes}
       s.item :two_factor_authentication, safe_join([material_symbol('safety_check'), t('settings.two_factor_authentication')]), settings_two_factor_authentication_methods_path, highlights_on: %r{/settings/two_factor_authentication|/settings/otp_authentication|/settings/security_keys}
       s.item :authorized_apps, safe_join([material_symbol('list_alt'), t('settings.authorized_apps')]), oauth_authorized_applications_path, if: -> { !self_destruct }

From 65508bb0c07bab4760576a9c59cacb43cf7d1301 Mon Sep 17 00:00:00 2001
From: Jeong Arm <kjwonmail@gmail.com>
Date: Tue, 1 Oct 2024 22:54:28 +0900
Subject: [PATCH 24/70] Show timestamp when the user deletes their account on
 admin dashboard (#25640)

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
---
 app/helpers/admin/dashboard_helper.rb | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/app/helpers/admin/dashboard_helper.rb b/app/helpers/admin/dashboard_helper.rb
index 4de779b79..f87fdad70 100644
--- a/app/helpers/admin/dashboard_helper.rb
+++ b/app/helpers/admin/dashboard_helper.rb
@@ -30,6 +30,8 @@ module Admin::DashboardHelper
                          [account.user_current_sign_in_at, false]
                        elsif account.user_pending?
                          [account.user_created_at, true]
+                       elsif account.suspended_at.present? && account.local? && account.user.nil?
+                         [account.suspended_at, true]
                        elsif account.last_status_at.present?
                          [account.last_status_at, true]
                        else

From 4cae309f6a1835dcff6b85ac31b046092e2985a1 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Tue, 1 Oct 2024 09:56:43 -0400
Subject: [PATCH 25/70] Fix `Rails/CreateTableWithTimestamps` cop (#30836)

---
 db/migrate/.rubocop.yml                                   | 5 +++++
 db/migrate/20170508230434_create_conversation_mutes.rb    | 2 +-
 db/migrate/20170823162448_create_status_pins.rb           | 2 +-
 db/migrate/20171116161857_create_list_accounts.rb         | 2 +-
 db/migrate/20180929222014_create_account_conversations.rb | 2 +-
 db/migrate/20181007025445_create_pghero_space_stats.rb    | 2 +-
 db/migrate/20190103124649_create_scheduled_statuses.rb    | 2 +-
 db/migrate/20220824233535_create_status_trends.rb         | 2 +-
 db/migrate/20221006061337_create_preview_card_trends.rb   | 2 +-
 9 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/db/migrate/.rubocop.yml b/db/migrate/.rubocop.yml
index 4e23800dd..6f8b6cc60 100644
--- a/db/migrate/.rubocop.yml
+++ b/db/migrate/.rubocop.yml
@@ -2,3 +2,8 @@ inherit_from: ../../.rubocop.yml
 
 Naming/VariableNumber:
   CheckSymbols: false
+
+# Enabled here as workaround for https://docs.rubocop.org/rubocop/configuration.html#path-relativity
+Rails/CreateTableWithTimestamps:
+  Include:
+    - '*.rb'
diff --git a/db/migrate/20170508230434_create_conversation_mutes.rb b/db/migrate/20170508230434_create_conversation_mutes.rb
index 01122c451..4beba9dfa 100644
--- a/db/migrate/20170508230434_create_conversation_mutes.rb
+++ b/db/migrate/20170508230434_create_conversation_mutes.rb
@@ -2,7 +2,7 @@
 
 class CreateConversationMutes < ActiveRecord::Migration[5.0]
   def change
-    create_table :conversation_mutes do |t|
+    create_table :conversation_mutes do |t| # rubocop:disable Rails/CreateTableWithTimestamps
       t.integer :account_id, null: false
       t.bigint :conversation_id, null: false
     end
diff --git a/db/migrate/20170823162448_create_status_pins.rb b/db/migrate/20170823162448_create_status_pins.rb
index c8d3fab3a..2cf3a85ca 100644
--- a/db/migrate/20170823162448_create_status_pins.rb
+++ b/db/migrate/20170823162448_create_status_pins.rb
@@ -2,7 +2,7 @@
 
 class CreateStatusPins < ActiveRecord::Migration[5.1]
   def change
-    create_table :status_pins do |t|
+    create_table :status_pins do |t| # rubocop:disable Rails/CreateTableWithTimestamps
       t.belongs_to :account, foreign_key: { on_delete: :cascade }, null: false
       t.belongs_to :status, foreign_key: { on_delete: :cascade }, null: false
     end
diff --git a/db/migrate/20171116161857_create_list_accounts.rb b/db/migrate/20171116161857_create_list_accounts.rb
index ff9ab3faa..b0371e4c8 100644
--- a/db/migrate/20171116161857_create_list_accounts.rb
+++ b/db/migrate/20171116161857_create_list_accounts.rb
@@ -2,7 +2,7 @@
 
 class CreateListAccounts < ActiveRecord::Migration[5.2]
   def change
-    create_table :list_accounts do |t|
+    create_table :list_accounts do |t| # rubocop:disable Rails/CreateTableWithTimestamps
       t.belongs_to :list, foreign_key: { on_delete: :cascade }, null: false
       t.belongs_to :account, foreign_key: { on_delete: :cascade }, null: false
       t.belongs_to :follow, foreign_key: { on_delete: :cascade }, null: false
diff --git a/db/migrate/20180929222014_create_account_conversations.rb b/db/migrate/20180929222014_create_account_conversations.rb
index 9386b86e7..4e85e68d4 100644
--- a/db/migrate/20180929222014_create_account_conversations.rb
+++ b/db/migrate/20180929222014_create_account_conversations.rb
@@ -2,7 +2,7 @@
 
 class CreateAccountConversations < ActiveRecord::Migration[5.2]
   def change
-    create_table :account_conversations do |t|
+    create_table :account_conversations do |t| # rubocop:disable Rails/CreateTableWithTimestamps
       t.belongs_to :account, foreign_key: { on_delete: :cascade }
       t.belongs_to :conversation, foreign_key: { on_delete: :cascade }
       t.bigint :participant_account_ids, array: true, null: false, default: []
diff --git a/db/migrate/20181007025445_create_pghero_space_stats.rb b/db/migrate/20181007025445_create_pghero_space_stats.rb
index ddaf4aef3..696b53d8d 100644
--- a/db/migrate/20181007025445_create_pghero_space_stats.rb
+++ b/db/migrate/20181007025445_create_pghero_space_stats.rb
@@ -2,7 +2,7 @@
 
 class CreatePgheroSpaceStats < ActiveRecord::Migration[5.2]
   def change
-    create_table :pghero_space_stats do |t|
+    create_table :pghero_space_stats do |t| # rubocop:disable Rails/CreateTableWithTimestamps
       t.text :database
       t.text :schema
       t.text :relation
diff --git a/db/migrate/20190103124649_create_scheduled_statuses.rb b/db/migrate/20190103124649_create_scheduled_statuses.rb
index a66546187..02b4916be 100644
--- a/db/migrate/20190103124649_create_scheduled_statuses.rb
+++ b/db/migrate/20190103124649_create_scheduled_statuses.rb
@@ -2,7 +2,7 @@
 
 class CreateScheduledStatuses < ActiveRecord::Migration[5.2]
   def change
-    create_table :scheduled_statuses do |t|
+    create_table :scheduled_statuses do |t| # rubocop:disable Rails/CreateTableWithTimestamps
       t.belongs_to :account, foreign_key: { on_delete: :cascade }
       t.datetime :scheduled_at, index: true
       t.jsonb :params
diff --git a/db/migrate/20220824233535_create_status_trends.rb b/db/migrate/20220824233535_create_status_trends.rb
index 52dcbf8f3..e68e5b7c1 100644
--- a/db/migrate/20220824233535_create_status_trends.rb
+++ b/db/migrate/20220824233535_create_status_trends.rb
@@ -2,7 +2,7 @@
 
 class CreateStatusTrends < ActiveRecord::Migration[6.1]
   def change
-    create_table :status_trends do |t|
+    create_table :status_trends do |t| # rubocop:disable Rails/CreateTableWithTimestamps
       t.references :status, null: false, foreign_key: { on_delete: :cascade }, index: { unique: true }
       t.references :account, null: false, foreign_key: { on_delete: :cascade }
       t.float :score, null: false, default: 0
diff --git a/db/migrate/20221006061337_create_preview_card_trends.rb b/db/migrate/20221006061337_create_preview_card_trends.rb
index 934a06e24..266f64402 100644
--- a/db/migrate/20221006061337_create_preview_card_trends.rb
+++ b/db/migrate/20221006061337_create_preview_card_trends.rb
@@ -2,7 +2,7 @@
 
 class CreatePreviewCardTrends < ActiveRecord::Migration[6.1]
   def change
-    create_table :preview_card_trends do |t|
+    create_table :preview_card_trends do |t| # rubocop:disable Rails/CreateTableWithTimestamps
       t.references :preview_card, null: false, foreign_key: { on_delete: :cascade }, index: { unique: true }
       t.float :score, null: false, default: 0
       t.integer :rank, null: false, default: 0

From a2c4ba20010a1fabca65ff6353e1b0fbea3b5f6d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Tue, 1 Oct 2024 17:10:56 +0200
Subject: [PATCH 26/70] Update dependency rubocop-rspec to v3.1.0 (#32202)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 334a9e30d..c623f92e7 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -756,7 +756,7 @@ GEM
       rack (>= 1.1)
       rubocop (>= 1.52.0, < 2.0)
       rubocop-ast (>= 1.31.1, < 2.0)
-    rubocop-rspec (3.0.5)
+    rubocop-rspec (3.1.0)
       rubocop (~> 1.61)
     rubocop-rspec_rails (2.30.0)
       rubocop (~> 1.61)

From f91f077985eccf9933346052ce4ee532215099c6 Mon Sep 17 00:00:00 2001
From: Nick Schonning <nschonni@gmail.com>
Date: Tue, 1 Oct 2024 12:26:30 -0400
Subject: [PATCH 27/70] Enable ESLlint no-case-declarations (#30768)

---
 .eslintrc.js                                      | 1 -
 app/javascript/mastodon/actions/streaming.js      | 3 ++-
 app/javascript/mastodon/reducers/compose.js       | 3 ++-
 app/javascript/mastodon/reducers/notifications.js | 3 ++-
 app/javascript/mastodon/reducers/search.js        | 3 ++-
 5 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/.eslintrc.js b/.eslintrc.js
index b6e4253e6..93ff1d7b5 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -64,7 +64,6 @@ module.exports = defineConfig({
     'indent': ['error', 2],
     'jsx-quotes': ['error', 'prefer-single'],
     'semi': ['error', 'always'],
-    'no-case-declarations': 'off',
     'no-catch-shadow': 'error',
     'no-console': [
       'warn',
diff --git a/app/javascript/mastodon/actions/streaming.js b/app/javascript/mastodon/actions/streaming.js
index b6fc31fae..30e643363 100644
--- a/app/javascript/mastodon/actions/streaming.js
+++ b/app/javascript/mastodon/actions/streaming.js
@@ -106,12 +106,13 @@ export const connectTimelineStream = (timelineId, channelName, params = {}, opti
           dispatch(processNewNotificationForGroups(notificationJSON));
           break;
         }
-        case 'notifications_merged':
+        case 'notifications_merged': {
           const state = getState();
           if (state.notifications.top || !state.notifications.mounted)
             dispatch(expandNotifications({ forceLoad: true, maxId: undefined }));
           dispatch(refreshStaleNotificationGroups());
           break;
+        }
         case 'conversation':
           // @ts-expect-error
           dispatch(updateConversations(JSON.parse(data.payload)));
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 9f66c0963..e77f2ac6e 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -407,7 +407,7 @@ export default function compose(state = initialState, action) {
 
         return item;
       }));
-  case INIT_MEDIA_EDIT_MODAL:
+  case INIT_MEDIA_EDIT_MODAL: {
     const media =  state.get('media_attachments').find(item => item.get('id') === action.id);
     return state.set('media_modal', ImmutableMap({
       id: action.id,
@@ -416,6 +416,7 @@ export default function compose(state = initialState, action) {
       focusY: media.getIn(['meta', 'focus', 'y'], 0),
       dirty: false,
     }));
+  }
   case COMPOSE_CHANGE_MEDIA_DESCRIPTION:
     return state.setIn(['media_modal', 'description'], action.description).setIn(['media_modal', 'dirty'], true);
   case COMPOSE_CHANGE_MEDIA_FOCUS:
diff --git a/app/javascript/mastodon/reducers/notifications.js b/app/javascript/mastodon/reducers/notifications.js
index 622f5e8e8..c99a619b5 100644
--- a/app/javascript/mastodon/reducers/notifications.js
+++ b/app/javascript/mastodon/reducers/notifications.js
@@ -298,9 +298,10 @@ export default function notifications(state = initialState, action) {
     return action.payload.timeline === 'home' ?
       state.update(action.payload.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items) :
       state;
-  case NOTIFICATIONS_MARK_AS_READ:
+  case NOTIFICATIONS_MARK_AS_READ: {
     const lastNotification = state.get('items').find(item => item !== null);
     return lastNotification ? recountUnread(state, lastNotification.get('id')) : state;
+  }
   case NOTIFICATIONS_SET_BROWSER_SUPPORT:
     return state.set('browserSupport', action.value);
   case NOTIFICATIONS_SET_BROWSER_PERMISSION:
diff --git a/app/javascript/mastodon/reducers/search.js b/app/javascript/mastodon/reducers/search.js
index 7828d49ee..7de1c65c0 100644
--- a/app/javascript/mastodon/reducers/search.js
+++ b/app/javascript/mastodon/reducers/search.js
@@ -72,9 +72,10 @@ export default function search(state = initialState, action) {
     });
   case SEARCH_EXPAND_REQUEST:
     return state.set('type', action.searchType).set('isLoading', true);
-  case SEARCH_EXPAND_SUCCESS:
+  case SEARCH_EXPAND_SUCCESS: {
     const results = action.searchType === 'hashtags' ? ImmutableOrderedSet(fromJS(action.results.hashtags)) : action.results[action.searchType].map(item => item.id);
     return state.updateIn(['results', action.searchType], list => list.union(results)).set('isLoading', false);
+  }
   case SEARCH_HISTORY_UPDATE:
     return state.set('recent', ImmutableOrderedSet(fromJS(action.recent)));
   default:

From 66b2bc1c841d6ad59fe4961ff9f0075fa3e52d50 Mon Sep 17 00:00:00 2001
From: Jeong Arm <kjwonmail@gmail.com>
Date: Wed, 2 Oct 2024 17:08:02 +0900
Subject: [PATCH 28/70] Ignore error if mentioned account was not processable
 (#29215)

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
---
 app/lib/activitypub/activity/create.rb       | 10 ++++++
 app/workers/mention_resolve_worker.rb        | 37 +++++++++++++++++++
 spec/lib/activitypub/activity/create_spec.rb | 37 +++++++++++++++++++
 spec/workers/mention_resolve_worker_spec.rb  | 38 ++++++++++++++++++++
 4 files changed, 122 insertions(+)
 create mode 100644 app/workers/mention_resolve_worker.rb
 create mode 100644 spec/workers/mention_resolve_worker_spec.rb

diff --git a/app/lib/activitypub/activity/create.rb b/app/lib/activitypub/activity/create.rb
index 09a8caf1f..928803cd6 100644
--- a/app/lib/activitypub/activity/create.rb
+++ b/app/lib/activitypub/activity/create.rb
@@ -42,6 +42,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
   def process_status
     @tags                 = []
     @mentions             = []
+    @unresolved_mentions  = []
     @silenced_account_ids = []
     @params               = {}
 
@@ -55,6 +56,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     end
 
     resolve_thread(@status)
+    resolve_unresolved_mentions(@status)
     fetch_replies(@status)
     distribute
     forward_for_reply
@@ -197,6 +199,8 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     return if account.nil?
 
     @mentions << Mention.new(account: account, silent: false)
+  rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError
+    @unresolved_mentions << tag['href']
   end
 
   def process_emoji(tag)
@@ -301,6 +305,12 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
     ThreadResolveWorker.perform_async(status.id, in_reply_to_uri, { 'request_id' => @options[:request_id] })
   end
 
+  def resolve_unresolved_mentions(status)
+    @unresolved_mentions.uniq.each do |uri|
+      MentionResolveWorker.perform_in(rand(30...600).seconds, status.id, uri, { 'request_id' => @options[:request_id] })
+    end
+  end
+
   def fetch_replies(status)
     collection = @object['replies']
     return if collection.blank?
diff --git a/app/workers/mention_resolve_worker.rb b/app/workers/mention_resolve_worker.rb
new file mode 100644
index 000000000..72dcd9633
--- /dev/null
+++ b/app/workers/mention_resolve_worker.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+class MentionResolveWorker
+  include Sidekiq::Worker
+  include ExponentialBackoff
+  include JsonLdHelper
+
+  sidekiq_options queue: 'pull', retry: 7
+
+  def perform(status_id, uri, options = {})
+    status = Status.find_by(id: status_id)
+    return if status.nil?
+
+    account = account_from_uri(uri)
+    account = ActivityPub::FetchRemoteAccountService.new.call(uri, request_id: options[:request_id]) if account.nil?
+
+    return if account.nil?
+
+    status.mentions.create!(account: account, silent: false)
+  rescue ActiveRecord::RecordNotFound
+    # Do nothing
+  rescue Mastodon::UnexpectedResponseError => e
+    response = e.response
+
+    if response_error_unsalvageable?(response)
+      # Give up
+    else
+      raise e
+    end
+  end
+
+  private
+
+  def account_from_uri(uri)
+    ActivityPub::TagManager.instance.uri_to_resource(uri, Account)
+  end
+end
diff --git a/spec/lib/activitypub/activity/create_spec.rb b/spec/lib/activitypub/activity/create_spec.rb
index 0986ae171..bdc8fd9d5 100644
--- a/spec/lib/activitypub/activity/create_spec.rb
+++ b/spec/lib/activitypub/activity/create_spec.rb
@@ -63,6 +63,24 @@ RSpec.describe ActivityPub::Activity::Create do
       }
     end
 
+    let(:invalid_mention_json) do
+      {
+        id: [ActivityPub::TagManager.instance.uri_for(sender), 'post2'].join('/'),
+        type: 'Note',
+        to: [
+          'https://www.w3.org/ns/activitystreams#Public',
+          ActivityPub::TagManager.instance.uri_for(follower),
+        ],
+        content: '@bob lorem ipsum',
+        published: 1.hour.ago.utc.iso8601,
+        updated: 1.hour.ago.utc.iso8601,
+        tag: {
+          type: 'Mention',
+          href: 'http://notexisting.dontexistingtld/actor',
+        },
+      }
+    end
+
     def activity_for_object(json)
       {
         '@context': 'https://www.w3.org/ns/activitystreams',
@@ -117,6 +135,25 @@ RSpec.describe ActivityPub::Activity::Create do
       # Creates two notifications
       expect(Notification.count).to eq 2
     end
+
+    it 'ignores unprocessable mention', :aggregate_failures do
+      stub_request(:get, invalid_mention_json[:tag][:href]).to_raise(HTTP::ConnectionError)
+      # When receiving the post that contains an invalid mention…
+      described_class.new(activity_for_object(invalid_mention_json), sender, delivery: true).perform
+
+      # NOTE: Refering explicitly to the workers is a bit awkward
+      DistributionWorker.drain
+      FeedInsertWorker.drain
+
+      # …it creates a status
+      status = Status.find_by(uri: invalid_mention_json[:id])
+
+      # Check the process did not crash
+      expect(status.nil?).to be false
+
+      # It has queued a mention resolve job
+      expect(MentionResolveWorker).to have_enqueued_sidekiq_job(status.id, invalid_mention_json[:tag][:href], anything)
+    end
   end
 
   describe '#perform' do
diff --git a/spec/workers/mention_resolve_worker_spec.rb b/spec/workers/mention_resolve_worker_spec.rb
new file mode 100644
index 000000000..5e23876b4
--- /dev/null
+++ b/spec/workers/mention_resolve_worker_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe MentionResolveWorker do
+  let(:status_id) { -42 }
+  let(:uri) { 'https://example.com/users/unknown' }
+
+  describe '#perform' do
+    subject { described_class.new.perform(status_id, uri, {}) }
+
+    context 'with a non-existent status' do
+      it 'returns nil' do
+        expect(subject).to be_nil
+      end
+    end
+
+    context 'with a valid user' do
+      let(:status) { Fabricate(:status) }
+      let(:status_id) { status.id }
+
+      let(:service_double) { instance_double(ActivityPub::FetchRemoteAccountService) }
+
+      before do
+        allow(ActivityPub::FetchRemoteAccountService).to receive(:new).and_return(service_double)
+
+        allow(service_double).to receive(:call).with(uri, anything) { Fabricate(:account, domain: 'example.com', uri: uri) }
+      end
+
+      it 'resolves the account and adds a new mention', :aggregate_failures do
+        expect { subject }
+          .to change { status.reload.mentions }.from([]).to(a_collection_including(having_attributes(account: having_attributes(uri: uri), silent: false)))
+
+        expect(service_double).to have_received(:call).once
+      end
+    end
+  end
+end

From ea72d5ec3a2eda181baa6846a67aec9b8b4b0142 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 2 Oct 2024 09:05:57 +0000
Subject: [PATCH 29/70] New Crowdin Translations (automated) (#32214)

Co-authored-by: GitHub Actions <noreply@github.com>
---
 app/javascript/mastodon/locales/cy.json  |  7 +++
 app/javascript/mastodon/locales/eo.json  | 64 ++++++++++++------------
 app/javascript/mastodon/locales/gl.json  |  2 +-
 app/javascript/mastodon/locales/he.json  |  8 +++
 app/javascript/mastodon/locales/hu.json  |  4 +-
 app/javascript/mastodon/locales/ja.json  |  1 +
 app/javascript/mastodon/locales/kab.json |  2 +
 app/javascript/mastodon/locales/no.json  |  2 +
 app/javascript/mastodon/locales/tr.json  |  5 ++
 config/locales/cy.yml                    |  4 ++
 config/locales/doorkeeper.gl.yml         |  2 +-
 config/locales/he.yml                    |  4 ++
 config/locales/nn.yml                    |  6 +--
 13 files changed, 72 insertions(+), 39 deletions(-)

diff --git a/app/javascript/mastodon/locales/cy.json b/app/javascript/mastodon/locales/cy.json
index f2d070888..52dc6a49e 100644
--- a/app/javascript/mastodon/locales/cy.json
+++ b/app/javascript/mastodon/locales/cy.json
@@ -85,6 +85,7 @@
   "alert.rate_limited.title": "Cyfradd gyfyngedig",
   "alert.unexpected.message": "Digwyddodd gwall annisgwyl.",
   "alert.unexpected.title": "Wps!",
+  "alt_text_badge.title": "Testun Amgen",
   "announcement.announcement": "Cyhoeddiad",
   "attachments_list.unprocessed": "(heb eu prosesu)",
   "audio.hide": "Cuddio sain",
@@ -221,6 +222,7 @@
   "domain_block_modal.they_cant_follow": "Ni all neb o'r gweinydd hwn eich dilyn.",
   "domain_block_modal.they_wont_know": "Fyddan nhw ddim yn gwybod eu bod wedi cael eu blocio.",
   "domain_block_modal.title": "Blocio parth?",
+  "domain_block_modal.you_will_lose_relationships": "Byddwch yn colli'r holl ddilynwyr a phobl rydych chi'n eu dilyn o'r gweinydd hwn.",
   "domain_block_modal.you_wont_see_posts": "Fyddwch chi ddim yn gweld postiadau na hysbysiadau gan ddefnyddwyr ar y gweinydd hwn.",
   "domain_pill.activitypub_lets_connect": "Mae'n caniatáu ichi gysylltu a rhyngweithio â phobl nid yn unig ar Mastodon, ond ar draws gwahanol apiau cymdeithasol hefyd.",
   "domain_pill.activitypub_like_language": "Mae ActivityPub fel yr iaith y mae Mastodon yn ei siarad â rhwydweithiau cymdeithasol eraill.",
@@ -849,6 +851,11 @@
   "upload_error.poll": "Nid oes modd llwytho ffeiliau â phleidleisiau.",
   "upload_form.audio_description": "Disgrifio ar gyfer pobl sydd â cholled clyw",
   "upload_form.description": "Disgrifio i'r rheini a nam ar ei golwg",
+  "upload_form.drag_and_drop.instructions": "I godi atodiad cyfryngau, pwyswch y space neu enter. Wrth lusgo, defnyddiwch y bysellau saeth i symud yr atodiad cyfryngau i unrhyw gyfeiriad penodol. Pwyswch space neu enter eto i ollwng yr atodiad cyfryngau yn ei safle newydd, neu pwyswch escape i ddiddymu.",
+  "upload_form.drag_and_drop.on_drag_cancel": "Cafodd llusgo ei ddiddymu. Cafodd atodiad cyfryngau {item} ei ollwng.",
+  "upload_form.drag_and_drop.on_drag_end": "Cafodd atodiad cyfryngau {item} ei ollwng.",
+  "upload_form.drag_and_drop.on_drag_over": "Symudwyd atodiad cyfryngau {item}.",
+  "upload_form.drag_and_drop.on_drag_start": "Atodiad cyfryngau godwyd {item}.",
   "upload_form.edit": "Golygu",
   "upload_form.thumbnail": "Newid llun bach",
   "upload_form.video_description": "Disgrifio ar gyfer pobl sydd â cholled clyw neu amhariad golwg",
diff --git a/app/javascript/mastodon/locales/eo.json b/app/javascript/mastodon/locales/eo.json
index 4268c3853..35c2a5c74 100644
--- a/app/javascript/mastodon/locales/eo.json
+++ b/app/javascript/mastodon/locales/eo.json
@@ -22,10 +22,10 @@
   "account.cancel_follow_request": "Nuligi peton por sekvado",
   "account.copy": "Kopii ligilon al profilo",
   "account.direct": "Private mencii @{name}",
-  "account.disable_notifications": "Ne plu sciigi min, kiam @{name} mesaĝas",
+  "account.disable_notifications": "Ĉesu sciigi min kiam @{name} afiŝas",
   "account.domain_blocked": "Domajno blokita",
   "account.edit_profile": "Redakti la profilon",
-  "account.enable_notifications": "Sciigi min, kiam @{name} mesaĝas",
+  "account.enable_notifications": "Sciigu min kiam @{name} afiŝos",
   "account.endorse": "Rekomendi ĉe via profilo",
   "account.featured_tags.last_status_at": "Lasta afîŝo je {date}",
   "account.featured_tags.last_status_never": "Neniu afiŝo",
@@ -49,14 +49,14 @@
   "account.mention": "Mencii @{name}",
   "account.moved_to": "{name} indikis, ke ria nova konto estas nun:",
   "account.mute": "Silentigi @{name}",
-  "account.mute_notifications_short": "Silentigu Sciigojn",
+  "account.mute_notifications_short": "Silentigu sciigojn",
   "account.mute_short": "Silentigu",
   "account.muted": "Silentigita",
   "account.mutual": "Reciproka",
   "account.no_bio": "Neniu priskribo estas provizita.",
   "account.open_original_page": "Malfermi la originalan paĝon",
   "account.posts": "Afiŝoj",
-  "account.posts_with_replies": "Mesaĝoj kaj respondoj",
+  "account.posts_with_replies": "Afiŝoj kaj respondoj",
   "account.report": "Raporti @{name}",
   "account.requested": "Atendo de aprobo. Klaku por nuligi la peton por sekvado",
   "account.requested_follow": "{name} petis sekvi vin",
@@ -69,7 +69,7 @@
   "account.unendorse": "Ne plu rekomendi ĉe la profilo",
   "account.unfollow": "Ĉesi sekvi",
   "account.unmute": "Ne plu silentigi @{name}",
-  "account.unmute_notifications_short": "Malsilentigu Sciigojn",
+  "account.unmute_notifications_short": "Malsilentigu sciigojn",
   "account.unmute_short": "Ne plu silentigi",
   "account_note.placeholder": "Alklaku por aldoni noton",
   "admin.dashboard.daily_retention": "Uzantoretenprocento lau tag post registro",
@@ -81,7 +81,7 @@
   "admin.impact_report.instance_followers": "Sekvantojn niaj uzantoj perdus",
   "admin.impact_report.instance_follows": "Sekvantojn ties uzantoj perdus",
   "admin.impact_report.title": "Influa reporto",
-  "alert.rate_limited.message": "Bonvolu reprovi post {retry_time, time, medium}.",
+  "alert.rate_limited.message": "Bonvolu reprovi poste {retry_time, time, medium}.",
   "alert.rate_limited.title": "Mesaĝkvante limigita",
   "alert.unexpected.message": "Neatendita eraro okazis.",
   "alert.unexpected.title": "Aj!",
@@ -163,7 +163,7 @@
   "compose_form.poll.switch_to_single": "Ŝanĝi la balotenketon por permesi unu solan elekton",
   "compose_form.poll.type": "Stilo",
   "compose_form.publish": "Afiŝo",
-  "compose_form.publish_form": "Afiŝi",
+  "compose_form.publish_form": "Nova afiŝo",
   "compose_form.reply": "Respondi",
   "compose_form.save_changes": "Ĝisdatigi",
   "compose_form.spoiler.marked": "Forigi la averton de enhavo",
@@ -173,7 +173,7 @@
   "confirmations.block.confirm": "Bloki",
   "confirmations.delete.confirm": "Forigi",
   "confirmations.delete.message": "Ĉu vi certas, ke vi volas forigi ĉi tiun afiŝon?",
-  "confirmations.delete.title": "Ĉu forigi Afiŝon?",
+  "confirmations.delete.title": "Ĉu forigi afiŝon?",
   "confirmations.delete_list.confirm": "Forigi",
   "confirmations.delete_list.message": "Ĉu vi certas, ke vi volas porĉiame forigi ĉi tiun liston?",
   "confirmations.delete_list.title": "Ĉu forigi liston?",
@@ -213,9 +213,9 @@
   "dismissable_banner.community_timeline": "Jen la plej novaj publikaj afiŝoj de uzantoj, kies kontojn gastigas {domain}.",
   "dismissable_banner.dismiss": "Eksigi",
   "dismissable_banner.explore_links": "Tiuj novaĵoj estas aktuale priparolataj de uzantoj en tiu ĉi kaj aliaj serviloj, sur la malcentrigita reto.",
-  "dismissable_banner.explore_statuses": "Ĉi tioj estas afiŝoj de socia reto kiu populariĝas hodiau.",
+  "dismissable_banner.explore_statuses": "Ĉi tiuj estas afiŝoj de la tuta socia reto, kiuj populariĝas hodiaŭ. Pli novaj afiŝoj kun pli da diskonigoj kaj plej ŝatataj estas rangigitaj pli alte.",
   "dismissable_banner.explore_tags": "Ĉi tiuj kradvostoj populariĝas en ĉi tiu kaj aliaj serviloj en la malcentraliza reto nun.",
-  "dismissable_banner.public_timeline": "Ĉi tioj estas plej lastaj publikaj afiŝoj de personoj ĉe socia reto kiu personoj ĉe {domain} sekvas.",
+  "dismissable_banner.public_timeline": "Ĉi tiuj estas la plej lastatempaj publikaj afiŝoj de homoj en la socia reto, kiujn homoj sur {domain} sekvas.",
   "domain_block_modal.block": "Bloki servilon",
   "domain_block_modal.block_account_instead": "Bloki @{name} anstataŭe",
   "domain_block_modal.they_can_interact_with_old_posts": "Homoj de ĉi tiu servilo povas interagi kun viaj malnovaj afiŝoj.",
@@ -265,8 +265,8 @@
   "empty_column.direct": "Vi ankoraŭ ne havas privatan mencion. Kiam vi sendos aŭ ricevos iun, tiu aperos ĉi tie.",
   "empty_column.domain_blocks": "Ankoraŭ neniu domajno estas blokita.",
   "empty_column.explore_statuses": "Nenio tendencas nun. Rekontrolu poste!",
-  "empty_column.favourited_statuses": "Vi ankoraŭ ne havas stelumitan afiŝon.",
-  "empty_column.favourites": "Ankoraŭ neniu stelumis tiun afiŝon.",
+  "empty_column.favourited_statuses": "Vi ankoraŭ ne havas plej ŝatatajn afiŝojn. Kiam vi ŝatatas unu, ĝi aperos ĉi tie.",
+  "empty_column.favourites": "Neniu ankoraŭ ŝatis ĉi tiun afiŝon. Kiam iu ŝatos ĝin, ili aperos ĉi tie.",
   "empty_column.follow_requests": "Vi ne ankoraŭ havas iun peton de sekvado. Kiam vi ricevos unu, ĝi aperos ĉi tie.",
   "empty_column.followed_tags": "Vi ankoraŭ ne sekvas iujn kradvortojn. Kiam vi faras, ili aperos ĉi tie.",
   "empty_column.hashtag": "Ankoraŭ estas nenio per ĉi tiu kradvorto.",
@@ -296,7 +296,7 @@
   "filter_modal.added.review_and_configure": "Por kontroli kaj pli modifi ĉi tiu filtrilkategorio, iru al la {settings_link}.",
   "filter_modal.added.review_and_configure_title": "Filtrilopcioj",
   "filter_modal.added.settings_link": "opciopaĝo",
-  "filter_modal.added.short_explanation": "Ĉi tiu mesaĝo aldonitas al la filtrilkategorio: {title}.",
+  "filter_modal.added.short_explanation": "Ĉi tiu afiŝo aldonitas al la filtrilkategorio: {title}.",
   "filter_modal.added.title": "Filtrilo aldonita!",
   "filter_modal.select_filter.context_mismatch": "ne kongruas la kuntekston",
   "filter_modal.select_filter.expired": "eksvalidiĝinta",
@@ -304,7 +304,7 @@
   "filter_modal.select_filter.search": "Serĉi aŭ krei",
   "filter_modal.select_filter.subtitle": "Uzu ekzistantan kategorion aŭ kreu novan",
   "filter_modal.select_filter.title": "Filtri ĉi tiun afiŝon",
-  "filter_modal.title.status": "Filtri mesaĝon",
+  "filter_modal.title.status": "Filtri afiŝon",
   "filter_warning.matches_filter": "Filtrilo de kongruoj “{title}”",
   "filtered_notifications_banner.pending_requests": "El {count, plural, =0 {neniu} one {unu persono} other {# homoj}} vi eble konas",
   "filtered_notifications_banner.title": "Filtritaj sciigoj",
@@ -351,7 +351,7 @@
   "hashtag.column_settings.tag_toggle": "Aldoni pliajn etikedojn por ĉi tiu kolumno",
   "hashtag.counter_by_accounts": "{count, plural,one {{counter} partoprenanto} other {{counter} partoprenantoj}}",
   "hashtag.counter_by_uses": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}}",
-  "hashtag.counter_by_uses_today": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}} hodiau",
+  "hashtag.counter_by_uses_today": "{count, plural,one {{counter} afiŝo} other {{counter} afiŝoj}} hodiaŭ",
   "hashtag.follow": "Sekvi la kradvorton",
   "hashtag.unfollow": "Ne plu sekvi la kradvorton",
   "hashtags.and_other": "…kaj {count, plural,other {# pli}}",
@@ -382,9 +382,9 @@
   "ignore_notifications_modal.not_following_title": "Ĉu ignori sciigojn de homoj, kiujn vi ne sekvas?",
   "ignore_notifications_modal.private_mentions_title": "Ĉu ignori sciigojn de nepetitaj privataj mencioj?",
   "interaction_modal.description.favourite": "Per konto ĉe Mastodon, vi povas stelumiti ĉi tiun afiŝon por sciigi la afiŝanton ke vi aprezigas ŝin kaj konservas por la estonteco.",
-  "interaction_modal.description.follow": "Kun konto ĉe Mastodon, vi povos sekvi {name} por vidi ties mesaĝojn en via hejmo.",
+  "interaction_modal.description.follow": "Kun konto ĉe Mastodon, vi povas sekvi {name} por ricevi iliajn afiŝojn en via hejma fluo.",
   "interaction_modal.description.reblog": "Kun konto ĉe Mastodon, vi povas diskonigi ĉi tiun afiŝon, por ke viaj propraj sekvantoj vidu ĝin.",
-  "interaction_modal.description.reply": "Kun konto ĉe Mastodon, vi povos respondi al ĉi tiu mesaĝo.",
+  "interaction_modal.description.reply": "Kun konto ĉe Mastodon, vi povos respondi al ĉi tiu afiŝo.",
   "interaction_modal.login.action": "Prenu min hejmen",
   "interaction_modal.login.prompt": "Domajno de via hejma servilo, ekz. mastodon.social",
   "interaction_modal.no_account_yet": "Ĉu ne estas ĉe Mastodon?",
@@ -402,12 +402,12 @@
   "keyboard_shortcuts.back": "reveni",
   "keyboard_shortcuts.blocked": "Malfermi la liston de blokitaj uzantoj",
   "keyboard_shortcuts.boost": "Diskonigi la mesaĝon",
-  "keyboard_shortcuts.column": "fokusi mesaĝon en unu el la kolumnoj",
+  "keyboard_shortcuts.column": "Fokusi kolumnon",
   "keyboard_shortcuts.compose": "enfokusigi la tekstujon",
   "keyboard_shortcuts.description": "Priskribo",
   "keyboard_shortcuts.direct": "por malfermi la kolumnon pri privataj mencioj",
   "keyboard_shortcuts.down": "iri suben en la listo",
-  "keyboard_shortcuts.enter": "malfermi mesaĝon",
+  "keyboard_shortcuts.enter": "Malfermi afiŝon",
   "keyboard_shortcuts.favourite": "Stelumi afiŝon",
   "keyboard_shortcuts.favourites": "Malfermi la liston de la stelumoj",
   "keyboard_shortcuts.federated": "Malfermi la frataran templinion",
@@ -421,16 +421,16 @@
   "keyboard_shortcuts.my_profile": "malfermi vian profilon",
   "keyboard_shortcuts.notifications": "malfermi la kolumnon de sciigoj",
   "keyboard_shortcuts.open_media": "Malfermi plurmedion",
-  "keyboard_shortcuts.pinned": "malfermi la liston de alpinglitaj mesaĝoj",
+  "keyboard_shortcuts.pinned": "Malfermu alpinglitajn afiŝojn-liston",
   "keyboard_shortcuts.profile": "malfermi la profilon de la aŭtoro",
-  "keyboard_shortcuts.reply": "respondi",
+  "keyboard_shortcuts.reply": "Respondu al afiŝo",
   "keyboard_shortcuts.requests": "Malfermi la liston de petoj por sekvado",
   "keyboard_shortcuts.search": "enfokusigi la serĉilon",
   "keyboard_shortcuts.spoilers": "Montri/kaŝi la kampon de averto de enhavo (\"CW\")",
   "keyboard_shortcuts.start": "malfermi la kolumnon «por komenci»",
   "keyboard_shortcuts.toggle_hidden": "Montri/kaŝi tekston malantaŭ la averto de enhavo (\"CW\")",
   "keyboard_shortcuts.toggle_sensitivity": "Montri/kaŝi plurmedion",
-  "keyboard_shortcuts.toot": "Krei novan mesaĝon",
+  "keyboard_shortcuts.toot": "Komencu novan afiŝon",
   "keyboard_shortcuts.unfocus": "malenfokusigi la tekstujon aŭ la serĉilon",
   "keyboard_shortcuts.up": "iri supren en la listo",
   "lightbox.close": "Fermi",
@@ -476,9 +476,9 @@
   "navigation_bar.blocks": "Blokitaj uzantoj",
   "navigation_bar.bookmarks": "Legosignoj",
   "navigation_bar.community_timeline": "Loka templinio",
-  "navigation_bar.compose": "Skribi novan mesaĝon",
+  "navigation_bar.compose": "Redakti novan afiŝon",
   "navigation_bar.direct": "Privataj mencioj",
-  "navigation_bar.discover": "Esplori",
+  "navigation_bar.discover": "Malkovri",
   "navigation_bar.domain_blocks": "Blokitaj domajnoj",
   "navigation_bar.explore": "Esplori",
   "navigation_bar.favourites": "Stelumoj",
@@ -487,12 +487,12 @@
   "navigation_bar.followed_tags": "Sekvataj kradvortoj",
   "navigation_bar.follows_and_followers": "Sekvatoj kaj sekvantoj",
   "navigation_bar.lists": "Listoj",
-  "navigation_bar.logout": "Adiaŭi",
+  "navigation_bar.logout": "Elsaluti",
   "navigation_bar.moderation": "Modereco",
   "navigation_bar.mutes": "Silentigitaj uzantoj",
   "navigation_bar.opened_in_classic_interface": "Afiŝoj, kontoj, kaj aliaj specifaj paĝoj kiuj estas malfermititaj defaulta en la klasika reta interfaco.",
   "navigation_bar.personal": "Persone",
-  "navigation_bar.pins": "Alpinglitaj mesaĝoj",
+  "navigation_bar.pins": "Alpinglitaj afiŝoj",
   "navigation_bar.preferences": "Preferoj",
   "navigation_bar.public_timeline": "Fratara templinio",
   "navigation_bar.search": "Serĉi",
@@ -572,7 +572,7 @@
   "notifications.column_settings.reblog": "Diskonigoj:",
   "notifications.column_settings.show": "Montri en kolumno",
   "notifications.column_settings.sound": "Eligi sonon",
-  "notifications.column_settings.status": "Novaj mesaĝoj:",
+  "notifications.column_settings.status": "Novaj afiŝoj:",
   "notifications.column_settings.unread_notifications.category": "Nelegitaj sciigoj",
   "notifications.column_settings.unread_notifications.highlight": "Marki nelegitajn sciigojn",
   "notifications.column_settings.update": "Redaktoj:",
@@ -660,7 +660,7 @@
   "poll.votes": "{votes, plural, one {# voĉdono} other {# voĉdonoj}}",
   "poll_button.add_poll": "Aldoni balotenketon",
   "poll_button.remove_poll": "Forigi balotenketon",
-  "privacy.change": "Agordi mesaĝan privatecon",
+  "privacy.change": "Ŝanĝu afiŝan privatecon",
   "privacy.direct.long": "Ĉiuj menciitaj en la afiŝo",
   "privacy.direct.short": "Specifaj homoj",
   "privacy.private.long": "Nur viaj sekvantoj",
@@ -775,13 +775,13 @@
   "sign_in_banner.sso_redirect": "Ensalutu aŭ Registriĝi",
   "status.admin_account": "Malfermi fasadon de moderigado por @{name}",
   "status.admin_domain": "Malfermu moderigan interfacon por {domain}",
-  "status.admin_status": "Malfermi ĉi tiun mesaĝon en la kontrola interfaco",
+  "status.admin_status": "Malfermi ĉi tiun afiŝon en la kontrola interfaco",
   "status.block": "Bloki @{name}",
   "status.bookmark": "Aldoni al la legosignoj",
   "status.cancel_reblog_private": "Ne plu diskonigi",
   "status.cannot_reblog": "Ĉi tiun afiŝon ne eblas diskonigi",
   "status.continued_thread": "Daŭrigis fadenon",
-  "status.copy": "Kopii la ligilon al la mesaĝo",
+  "status.copy": "Kopii la ligilon al la afiŝo",
   "status.delete": "Forigi",
   "status.detailed_status": "Detala konversacia vido",
   "status.direct": "Private mencii @{name}",
@@ -803,9 +803,9 @@
   "status.more": "Pli",
   "status.mute": "Silentigi @{name}",
   "status.mute_conversation": "Silentigi konversacion",
-  "status.open": "Disvolvi la mesaĝon",
+  "status.open": "Pligrandigu ĉi tiun afiŝon",
   "status.pin": "Alpingli al la profilo",
-  "status.pinned": "Alpinglita mesaĝo",
+  "status.pinned": "Alpinglita afiŝo",
   "status.read_more": "Legi pli",
   "status.reblog": "Diskonigi",
   "status.reblog_private": "Diskonigi kun la sama videbleco",
diff --git a/app/javascript/mastodon/locales/gl.json b/app/javascript/mastodon/locales/gl.json
index 3bc9e1ee5..2a31bfa46 100644
--- a/app/javascript/mastodon/locales/gl.json
+++ b/app/javascript/mastodon/locales/gl.json
@@ -193,7 +193,7 @@
   "confirmations.reply.message": "Ao responder sobrescribirás a mensaxe que estás a compor. Tes a certeza de que queres continuar?",
   "confirmations.reply.title": "Editar a publicación?",
   "confirmations.unfollow.confirm": "Deixar de seguir",
-  "confirmations.unfollow.message": "Desexas deixar de seguir a {name}?",
+  "confirmations.unfollow.message": "Tes certeza de querer deixar de seguir a {name}?",
   "confirmations.unfollow.title": "Deixar de seguir á usuaria?",
   "content_warning.hide": "Agochar publicación",
   "content_warning.show": "Mostrar igualmente",
diff --git a/app/javascript/mastodon/locales/he.json b/app/javascript/mastodon/locales/he.json
index 4ce0d4162..16f558e54 100644
--- a/app/javascript/mastodon/locales/he.json
+++ b/app/javascript/mastodon/locales/he.json
@@ -85,6 +85,7 @@
   "alert.rate_limited.title": "חלה הגבלה על קצב התעבורה",
   "alert.unexpected.message": "אירעה שגיאה בלתי צפויה.",
   "alert.unexpected.title": "אופס!",
+  "alt_text_badge.title": "כיתוב חלופי",
   "announcement.announcement": "הכרזה",
   "attachments_list.unprocessed": "(לא מעובד)",
   "audio.hide": "השתק",
@@ -221,6 +222,8 @@
   "domain_block_modal.they_cant_follow": "משתמש משרת זה לא יכול לעקוב אחריך.",
   "domain_block_modal.they_wont_know": "הם לא ידעו כי נחסמו.",
   "domain_block_modal.title": "לחסום שרת?",
+  "domain_block_modal.you_will_lose_num_followers": "{followersCount, plural,one {יאבד לך עוקב אחד}other {יאבדו לך {followersCountDisplay} עוקבים}} {followingCount, plural,one  {ונעקב אחד}other {ו־{followingCountDisplay} נעקבים}}.",
+  "domain_block_modal.you_will_lose_relationships": "יאבדו לך כל העוקבים והנעקבים משרת זה.",
   "domain_block_modal.you_wont_see_posts": "לא תוכלו לראות הודעות ממשתמשים על שרת זה.",
   "domain_pill.activitypub_lets_connect": "מאפשר לך להתחבר ולהתרועע עם אחרים לא רק במסטודון, אלא גם ביישומים חברתיים שונים אחרים.",
   "domain_pill.activitypub_like_language": "אקטיביטיפאב היא למעשה השפה בה מסטודון מדבר עם רשתות חברתיות אחרות.",
@@ -849,6 +852,11 @@
   "upload_error.poll": "לא ניתן להעלות קובץ עם סקר.",
   "upload_form.audio_description": "תאר/י עבור לקויי שמיעה",
   "upload_form.description": "תיאור לכבדי ראיה",
+  "upload_form.drag_and_drop.instructions": "כדי לבחור קובץ מוצמד, יש ללחוץ על מקש רווח או אנטר. בעת הגרירה, השתמשו במקשי החיצים כדי להזיז את הקובץ המוצמד בכל כיוון. לחצו רווח או אנטר בשנית כדי לעזוב את הקובץ במקומו החדש, או לחצו אסקייפ לביטול.",
+  "upload_form.drag_and_drop.on_drag_cancel": "הגרירה בוטלה. קובץ המדיה {item} נעזב.",
+  "upload_form.drag_and_drop.on_drag_end": "קובץ המדיה {item} נעזב.",
+  "upload_form.drag_and_drop.on_drag_over": "קובץ המדיה {item} הוזז.",
+  "upload_form.drag_and_drop.on_drag_start": "קובץ המדיה {item} נבחר.",
   "upload_form.edit": "עריכה",
   "upload_form.thumbnail": "שנה/י תמונה ממוזערת",
   "upload_form.video_description": "תאר/י עבור לקויי שמיעה ולקויי ראייה",
diff --git a/app/javascript/mastodon/locales/hu.json b/app/javascript/mastodon/locales/hu.json
index 8baf584b7..f9f403177 100644
--- a/app/javascript/mastodon/locales/hu.json
+++ b/app/javascript/mastodon/locales/hu.json
@@ -852,8 +852,8 @@
   "upload_error.poll": "Szavazásnál nem lehet fájlt feltölteni.",
   "upload_form.audio_description": "Leírás siket vagy hallássérült emberek számára",
   "upload_form.description": "Leírás vak vagy gyengénlátó emberek számára",
-  "upload_form.drag_and_drop.instructions": "Egy médiamelléklet kiválasztásához nyomjon Szóközt vagy Entert. Húzás közben használja a nyílgombokat a médiamelléklet adott irányba történő mozgatásához. A médiamelléklet új pozícióba helyezéséhez nyomja meg a Szóközt vagy az Entert, vagy a megszakításhoz nyomja meg az Esc gombot.",
-  "upload_form.drag_and_drop.on_drag_cancel": "Az áthúzást megszakította. A(z) {item} médiamelléklet el lett dobva.",
+  "upload_form.drag_and_drop.instructions": "Egy médiamelléklet kiválasztásához nyomj Szóközt vagy Entert. Húzás közben használd a nyílgombokat a médiamelléklet adott irányba történő mozgatásához. A médiamelléklet új pozícióba helyezéséhez nyomd meg a Szóközt vagy az Entert, vagy a megszakításhoz nyomd meg az Esc gombot.",
+  "upload_form.drag_and_drop.on_drag_cancel": "Az áthúzás megszakítva. A(z) {item} médiamelléklet el lett dobva.",
   "upload_form.drag_and_drop.on_drag_end": "A(z) {item} médiamelléklet el lett dobva.",
   "upload_form.drag_and_drop.on_drag_over": "A(z) {item} médiamelléklet át lett helyezve.",
   "upload_form.drag_and_drop.on_drag_start": "A(z) {item} médiamelléklet fel lett véve.",
diff --git a/app/javascript/mastodon/locales/ja.json b/app/javascript/mastodon/locales/ja.json
index f5e7cc9c0..46444863a 100644
--- a/app/javascript/mastodon/locales/ja.json
+++ b/app/javascript/mastodon/locales/ja.json
@@ -85,6 +85,7 @@
   "alert.rate_limited.title": "制限に達しました",
   "alert.unexpected.message": "不明なエラーが発生しました。",
   "alert.unexpected.title": "エラー!",
+  "alt_text_badge.title": "代替テキスト",
   "announcement.announcement": "お知らせ",
   "attachments_list.unprocessed": "(未処理)",
   "audio.hide": "音声を閉じる",
diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json
index ff06c9c46..07236bb73 100644
--- a/app/javascript/mastodon/locales/kab.json
+++ b/app/javascript/mastodon/locales/kab.json
@@ -585,6 +585,7 @@
   "status.bookmark": "Creḍ",
   "status.cancel_reblog_private": "Sefsex beṭṭu",
   "status.cannot_reblog": "Tasuffeɣt-a ur tezmir ara ad tettwabḍu tikelt-nniḍen",
+  "status.continued_thread": "Asentel yettkemmil",
   "status.copy": "Nɣel assaɣ ɣer tasuffeɣt",
   "status.delete": "Kkes",
   "status.direct": "Bder-d @{name} weḥd-s",
@@ -616,6 +617,7 @@
   "status.reblogs.empty": "Ula yiwen ur yebḍi tajewwiqt-agi ar tura. Ticki yebḍa-tt yiwen, ad d-iban da.",
   "status.redraft": "Kkes tɛiwdeḍ tira",
   "status.remove_bookmark": "Kkes tacreḍt",
+  "status.replied_in_thread": "Y·t·erra-d deg usentel",
   "status.replied_to": "Y·terra-yas i {name}",
   "status.reply": "Err",
   "status.replyAll": "Err i lxiḍ",
diff --git a/app/javascript/mastodon/locales/no.json b/app/javascript/mastodon/locales/no.json
index 8cd3387ea..1dd5df32b 100644
--- a/app/javascript/mastodon/locales/no.json
+++ b/app/javascript/mastodon/locales/no.json
@@ -502,6 +502,8 @@
   "notification.reblog": "{name} fremhevet ditt innlegg",
   "notification.status": "{name} la nettopp ut",
   "notification.update": "{name} redigerte et innlegg",
+  "notification_requests.minimize_banner": "Minimer banneret for filtrerte varsler",
+  "notification_requests.view": "Vis varsler",
   "notifications.clear": "Fjern varsler",
   "notifications.clear_confirmation": "Er du sikker på at du vil fjerne alle dine varsler permanent?",
   "notifications.column_settings.admin.report": "Nye rapporter:",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 6feae5d13..2d9c7321d 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -852,6 +852,11 @@
   "upload_error.poll": "Anketlerde dosya yüklemesine izin verilmez.",
   "upload_form.audio_description": "İşitme kaybı olan kişiler için yazı ekleyiniz",
   "upload_form.description": "Görme engelliler için açıklama",
+  "upload_form.drag_and_drop.instructions": "Bir medya eklentisini taşımak için, boşluk veya enter tuşuna basın. Sürükleme sırasında medya eklentisini herhangi bir yöne hareket ettirmek için ok tuşlarını kullanın. Medya eklentisini yeni konumuna bırakmak için tekrar boşluk veya enter tuşuna basın veya işlemi iptal etmek için escape tuşuna basın.",
+  "upload_form.drag_and_drop.on_drag_cancel": "Sürükleme iptal edildi. Medya eklentisi {item} bırakıldı.",
+  "upload_form.drag_and_drop.on_drag_end": "Medya eklentisi {item} bırakıldı.",
+  "upload_form.drag_and_drop.on_drag_over": "Medya eklentisi {item} hareket ettirildi.",
+  "upload_form.drag_and_drop.on_drag_start": "Medya eklentisi {item} tutuldu.",
   "upload_form.edit": "Düzenle",
   "upload_form.thumbnail": "Küçük resmi değiştir",
   "upload_form.video_description": "İşitme kaybı veya görme engeli olan kişiler için açıklama ekleyiniz",
diff --git a/config/locales/cy.yml b/config/locales/cy.yml
index 1ae6aa08f..a70d08ed8 100644
--- a/config/locales/cy.yml
+++ b/config/locales/cy.yml
@@ -931,6 +931,9 @@ cy:
         message_html: Nid ydych wedi diffinio unrhyw reolau gweinydd.
       sidekiq_process_check:
         message_html: Does dim proses Sidekiq yn rhedeg ar gyfer y ciw(iau) %{value}. Adolygwch eich ffurfweddiad Sidekiq
+      software_version_check:
+        action: Gweld y diweddariadau sydd ar gael
+        message_html: Mae diweddariad Mastodon ar gael.
       software_version_critical_check:
         action: Gweld y diweddariadau sydd ar gael
         message_html: Mae diweddariad hanfodol Mastodon ar gael, diweddarwch cyn gynted â phosibl.
@@ -1796,6 +1799,7 @@ cy:
     delete: Dileu cyfrif
     development: Datblygu
     edit_profile: Golygu proffil
+    export: Allforio
     featured_tags: Prif hashnodau
     import: Mewnforio
     import_and_export: Mewnforio ac allforio
diff --git a/config/locales/doorkeeper.gl.yml b/config/locales/doorkeeper.gl.yml
index adee6bd3d..5d7148b84 100644
--- a/config/locales/doorkeeper.gl.yml
+++ b/config/locales/doorkeeper.gl.yml
@@ -69,7 +69,7 @@ gl:
       buttons:
         revoke: Retirar autorización
       confirmations:
-        revoke: Estás segura?
+        revoke: Tes certeza?
       index:
         authorized_at: Autorizada o %{date}
         description_html: Estas aplicacións poden acceder á túa conta usando a API. Se ves aplicacións que non recoñeces, ou hai comportamentos non consentidos dalgunha delas, podes revogar o acceso.
diff --git a/config/locales/he.yml b/config/locales/he.yml
index c7af22660..846b0d14a 100644
--- a/config/locales/he.yml
+++ b/config/locales/he.yml
@@ -903,6 +903,9 @@ he:
         message_html: לא הוגדרו שום כללי שרת.
       sidekiq_process_check:
         message_html: שום הליכי Sidekiq לא רצים עבור %{value} תור(ות). בחנו בבקשה את הגדרות Sidekiq
+      software_version_check:
+        action: ראו עדכונים זמינים
+        message_html: עדכון מסטודון זמין כעת.
       software_version_critical_check:
         action: ראו עדכונים זמינים
         message_html: יצא עדכון קריטי למסטודון, נא לעדכן את תוכנת מסטודון בהקדם האפשרי.
@@ -1744,6 +1747,7 @@ he:
     delete: מחיקת חשבון
     development: פיתוח
     edit_profile: עריכת פרופיל
+    export: ייצוא
     featured_tags: תגיות נבחרות
     import: יבוא
     import_and_export: יבוא ויצוא
diff --git a/config/locales/nn.yml b/config/locales/nn.yml
index 8f6afc242..ca5e34ee5 100644
--- a/config/locales/nn.yml
+++ b/config/locales/nn.yml
@@ -1,7 +1,7 @@
 ---
 nn:
   about:
-    about_mastodon_html: 'Framtidas sosiale nettverk: Ingen annonsar, ingen verksemder som overvaker deg, etisk design og desentralisering! Eig idéane dine med Mastodon!'
+    about_mastodon_html: 'Framtidas sosiale nettverk: Ingen annonsar, ingen verksemder som overvaker deg, etisk design og desentralisering! Eig dataene dine med Mastodon!'
     contact_missing: Ikkje sett
     contact_unavailable: I/T
     hosted_on: "%{domain} er vert for Mastodon"
@@ -39,7 +39,7 @@ nn:
       avatar: Bilete
       by_domain: Domene
       change_email:
-        changed_msg: Konto-e-posten er endra!
+        changed_msg: E-post for konto er endra!
         current_email: Noverande e-post
         label: Byt e-post
         new_email: Ny e-post
@@ -62,7 +62,7 @@ nn:
       disable: Slå av
       disable_sign_in_token_auth: Slå av e-post-token-autentisering
       disable_two_factor_authentication: Slå av 2FA
-      disabled: Slege av
+      disabled: Inaktiv
       display_name: Synleg namn
       domain: Domene
       edit: Rediger

From f0716368e642c4c3d21d2327009ddd59d27cec8b Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 05:07:52 -0400
Subject: [PATCH 30/70] Update simplecov-html to version 0.13.1 (#32205)

---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index c623f92e7..3950a30d6 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -815,7 +815,7 @@ GEM
       docile (~> 1.1)
       simplecov-html (~> 0.11)
       simplecov_json_formatter (~> 0.1)
-    simplecov-html (0.12.3)
+    simplecov-html (0.13.1)
     simplecov-lcov (0.8.0)
     simplecov_json_formatter (0.1.4)
     stackprof (0.2.26)

From 33d3ca7cf19b5ae6e90163092d63d4a49fe45d4b Mon Sep 17 00:00:00 2001
From: Christian Schmidt <github@chsc.dk>
Date: Wed, 2 Oct 2024 11:23:44 +0200
Subject: [PATCH 31/70] Support /.well-known/host-meta.json (#32206)

---
 .../well_known/host_meta_controller.rb        | 18 ++++++++-
 config/routes.rb                              |  2 +-
 spec/requests/well_known/host_meta_spec.rb    | 40 ++++++++++++++-----
 spec/routing/well_known_routes_spec.rb        |  9 ++++-
 4 files changed, 55 insertions(+), 14 deletions(-)

diff --git a/app/controllers/well_known/host_meta_controller.rb b/app/controllers/well_known/host_meta_controller.rb
index 201da9fbc..6dee587ba 100644
--- a/app/controllers/well_known/host_meta_controller.rb
+++ b/app/controllers/well_known/host_meta_controller.rb
@@ -7,7 +7,23 @@ module WellKnown
     def show
       @webfinger_template = "#{webfinger_url}?resource={uri}"
       expires_in 3.days, public: true
-      render content_type: 'application/xrd+xml', formats: [:xml]
+
+      respond_to do |format|
+        format.any do
+          render content_type: 'application/xrd+xml', formats: [:xml]
+        end
+
+        format.json do
+          render json: {
+            links: [
+              {
+                rel: 'lrdd',
+                template: @webfinger_template,
+              },
+            ],
+          }
+        end
+      end
     end
   end
 end
diff --git a/config/routes.rb b/config/routes.rb
index 890102955..83170fba0 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -67,7 +67,7 @@ Rails.application.routes.draw do
   scope path: '.well-known' do
     scope module: :well_known do
       get 'oauth-authorization-server', to: 'oauth_metadata#show', as: :oauth_metadata, defaults: { format: 'json' }
-      get 'host-meta', to: 'host_meta#show', as: :host_meta, defaults: { format: 'xml' }
+      get 'host-meta', to: 'host_meta#show', as: :host_meta
       get 'nodeinfo', to: 'node_info#index', as: :nodeinfo, defaults: { format: 'json' }
       get 'webfinger', to: 'webfinger#show', as: :webfinger
     end
diff --git a/spec/requests/well_known/host_meta_spec.rb b/spec/requests/well_known/host_meta_spec.rb
index 09f17baa8..726911dda 100644
--- a/spec/requests/well_known/host_meta_spec.rb
+++ b/spec/requests/well_known/host_meta_spec.rb
@@ -9,19 +9,39 @@ RSpec.describe 'The /.well-known/host-meta request' do
     expect(response)
       .to have_http_status(200)
       .and have_attributes(
-        media_type: 'application/xrd+xml',
-        body: host_meta_xml_template
+        media_type: 'application/xrd+xml'
+      )
+
+    doc = Nokogiri::XML(response.parsed_body)
+    expect(doc.at_xpath('/xrd:XRD/xrd:Link[@rel="lrdd"]/@template', 'xrd' => 'http://docs.oasis-open.org/ns/xri/xrd-1.0').value)
+      .to eq 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}'
+  end
+
+  it 'returns http success with valid JSON response with .json extension' do
+    get '/.well-known/host-meta.json'
+
+    expect(response)
+      .to have_http_status(200)
+      .and have_attributes(
+        media_type: 'application/json'
+      )
+
+    expect(response.parsed_body)
+      .to include(
+        links: [
+          'rel' => 'lrdd',
+          'template' => 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}',
+        ]
       )
   end
 
-  private
+  it 'returns http success with valid JSON response with Accept header' do
+    get '/.well-known/host-meta', headers: { 'Accept' => 'application/json' }
 
-  def host_meta_xml_template
-    <<~XML
-      <?xml version="1.0" encoding="UTF-8"?>
-      <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
-        <Link rel="lrdd" template="https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}"/>
-      </XRD>
-    XML
+    expect(response)
+      .to have_http_status(200)
+      .and have_attributes(
+        media_type: 'application/json'
+      )
   end
 end
diff --git a/spec/routing/well_known_routes_spec.rb b/spec/routing/well_known_routes_spec.rb
index 6578e939a..84081059b 100644
--- a/spec/routing/well_known_routes_spec.rb
+++ b/spec/routing/well_known_routes_spec.rb
@@ -4,9 +4,14 @@ require 'rails_helper'
 
 RSpec.describe 'Well Known routes' do
   describe 'the host-meta route' do
-    it 'routes to correct place with xml format' do
+    it 'routes to correct place' do
       expect(get('/.well-known/host-meta'))
-        .to route_to('well_known/host_meta#show', format: 'xml')
+        .to route_to('well_known/host_meta#show')
+    end
+
+    it 'routes to correct place with json format' do
+      expect(get('/.well-known/host-meta.json'))
+        .to route_to('well_known/host_meta#show', format: 'json')
     end
   end
 

From ebab3b80c7e8abb7e29d1ab912427e52d89faa1c Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 05:43:04 -0400
Subject: [PATCH 32/70] Expand coverage for `Export` utility class (#32212)

---
 .../account_domain_block_fabricator.rb        |   2 +-
 spec/models/export_spec.rb                    | 177 ++++++++++++++----
 2 files changed, 144 insertions(+), 35 deletions(-)

diff --git a/spec/fabricators/account_domain_block_fabricator.rb b/spec/fabricators/account_domain_block_fabricator.rb
index 83df509da..a211b7c66 100644
--- a/spec/fabricators/account_domain_block_fabricator.rb
+++ b/spec/fabricators/account_domain_block_fabricator.rb
@@ -2,5 +2,5 @@
 
 Fabricator(:account_domain_block) do
   account { Fabricate.build(:account) }
-  domain 'example.com'
+  domain { sequence { |n| "host-#{n}.example" } }
 end
diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb
index 06bf07ed7..48e78830d 100644
--- a/spec/models/export_spec.rb
+++ b/spec/models/export_spec.rb
@@ -3,66 +3,175 @@
 require 'rails_helper'
 
 RSpec.describe Export do
+  subject { described_class.new(account) }
+
   let(:account) { Fabricate(:account) }
   let(:target_accounts) do
-    [{}, { username: 'one', domain: 'local.host' }].map(&method(:Fabricate).curry(2).call(:account))
+    [
+      Fabricate(:account),
+      Fabricate(:account, username: 'one', domain: 'local.host'),
+    ]
   end
 
-  describe 'to_csv' do
-    it 'returns a csv of the blocked accounts' do
-      target_accounts.each { |target_account| account.block!(target_account) }
+  describe '#to_bookmarks_csv' do
+    before { Fabricate.times(2, :bookmark, account: account) }
 
-      export = described_class.new(account).to_blocked_accounts_csv
-      results = export.strip.split
+    let(:export) { CSV.parse(subject.to_bookmarks_csv) }
 
-      expect(results.size).to eq 2
-      expect(results.first).to eq 'one@local.host'
+    it 'returns a csv of bookmarks' do
+      expect(export)
+        .to contain_exactly(
+          include(/statuses/),
+          include(/statuses/)
+        )
     end
+  end
+
+  describe '#to_blocked_accounts_csv' do
+    before { target_accounts.each { |target_account| account.block!(target_account) } }
+
+    let(:export) { CSV.parse(subject.to_blocked_accounts_csv) }
+
+    it 'returns a csv of the blocked accounts' do
+      expect(export)
+        .to contain_exactly(
+          include('one@local.host'),
+          include(be_present)
+        )
+    end
+  end
+
+  describe '#to_muted_accounts_csv' do
+    before { target_accounts.each { |target_account| account.mute!(target_account) } }
+
+    let(:export) { CSV.parse(subject.to_muted_accounts_csv) }
 
     it 'returns a csv of the muted accounts' do
-      target_accounts.each { |target_account| account.mute!(target_account) }
-
-      export = described_class.new(account).to_muted_accounts_csv
-      results = export.strip.split("\n")
-
-      expect(results.size).to eq 3
-      expect(results.first).to eq 'Account address,Hide notifications'
-      expect(results.second).to eq 'one@local.host,true'
+      expect(export)
+        .to contain_exactly(
+          contain_exactly('Account address', 'Hide notifications'),
+          include('one@local.host', 'true'),
+          include(be_present)
+        )
     end
+  end
+
+  describe '#to_following_accounts_csv' do
+    before { target_accounts.each { |target_account| account.follow!(target_account) } }
+
+    let(:export) { CSV.parse(subject.to_following_accounts_csv) }
 
     it 'returns a csv of the following accounts' do
-      target_accounts.each { |target_account| account.follow!(target_account) }
-
-      export = described_class.new(account).to_following_accounts_csv
-      results = export.strip.split("\n")
-
-      expect(results.size).to eq 3
-      expect(results.first).to eq 'Account address,Show boosts,Notify on new posts,Languages'
-      expect(results.second).to eq 'one@local.host,true,false,'
+      expect(export)
+        .to contain_exactly(
+          contain_exactly('Account address', 'Show boosts', 'Notify on new posts', 'Languages'),
+          include('one@local.host', 'true', 'false', be_blank),
+          include(be_present)
+        )
     end
   end
 
-  describe 'total_storage' do
+  describe '#to_lists_csv' do
+    before do
+      target_accounts.each do |target_account|
+        account.follow!(target_account)
+        Fabricate(:list, account: account).accounts << target_account
+      end
+    end
+
+    let(:export) { CSV.parse(subject.to_lists_csv) }
+
+    it 'returns a csv of the lists' do
+      expect(export)
+        .to contain_exactly(
+          include('one@local.host'),
+          include(be_present)
+        )
+    end
+  end
+
+  describe '#to_blocked_domains_csv' do
+    before { Fabricate.times(2, :account_domain_block, account: account) }
+
+    let(:export) { CSV.parse(subject.to_blocked_domains_csv) }
+
+    it 'returns a csv of the blocked domains' do
+      expect(export)
+        .to contain_exactly(
+          include(/example/),
+          include(/example/)
+        )
+    end
+  end
+
+  describe '#total_storage' do
     it 'returns the total size of the media attachments' do
       media_attachment = Fabricate(:media_attachment, account: account)
-      expect(described_class.new(account).total_storage).to eq media_attachment.file_file_size || 0
+      expect(subject.total_storage).to eq media_attachment.file_file_size || 0
     end
   end
 
-  describe 'total_follows' do
-    it 'returns the total number of the followed accounts' do
-      target_accounts.each { |target_account| account.follow!(target_account) }
-      expect(described_class.new(account.reload).total_follows).to eq 2
+  describe '#total_statuses' do
+    before { Fabricate.times(2, :status, account: account) }
+
+    it 'returns the total number of statuses' do
+      expect(subject.total_statuses).to eq(2)
     end
+  end
+
+  describe '#total_bookmarks' do
+    before { Fabricate.times(2, :bookmark, account: account) }
+
+    it 'returns the total number of bookmarks' do
+      expect(subject.total_bookmarks).to eq(2)
+    end
+  end
+
+  describe '#total_follows' do
+    before { target_accounts.each { |target_account| account.follow!(target_account) } }
+
+    it 'returns the total number of the followed accounts' do
+      expect(subject.total_follows).to eq(2)
+    end
+  end
+
+  describe '#total_lists' do
+    before { Fabricate.times(2, :list, account: account) }
+
+    it 'returns the total number of lists' do
+      expect(subject.total_lists).to eq(2)
+    end
+  end
+
+  describe '#total_followers' do
+    before { target_accounts.each { |target_account| target_account.follow!(account) } }
+
+    it 'returns the total number of the follower accounts' do
+      expect(subject.total_followers).to eq(2)
+    end
+  end
+
+  describe '#total_blocks' do
+    before { target_accounts.each { |target_account| account.block!(target_account) } }
 
     it 'returns the total number of the blocked accounts' do
-      target_accounts.each { |target_account| account.block!(target_account) }
-      expect(described_class.new(account.reload).total_blocks).to eq 2
+      expect(subject.total_blocks).to eq(2)
     end
+  end
+
+  describe '#total_mutes' do
+    before { target_accounts.each { |target_account| account.mute!(target_account) } }
 
     it 'returns the total number of the muted accounts' do
-      target_accounts.each { |target_account| account.mute!(target_account) }
-      expect(described_class.new(account.reload).total_mutes).to eq 2
+      expect(subject.total_mutes).to eq(2)
+    end
+  end
+
+  describe '#total_domain_blocks' do
+    before { Fabricate.times(2, :account_domain_block, account: account) }
+
+    it 'returns the total number of account domain blocks' do
+      expect(subject.total_domain_blocks).to eq(2)
     end
   end
 end

From 4a737a948a6aa8803ba366374569a93970437ec7 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Wed, 2 Oct 2024 12:03:04 +0200
Subject: [PATCH 33/70] Fix incorrect `'navigator'` check (#32219)

---
 app/javascript/mastodon/actions/markers.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/app/javascript/mastodon/actions/markers.ts b/app/javascript/mastodon/actions/markers.ts
index 0b3280c21..251546cb9 100644
--- a/app/javascript/mastodon/actions/markers.ts
+++ b/app/javascript/mastodon/actions/markers.ts
@@ -37,8 +37,7 @@ export const synchronouslySubmitMarkers = createAppAsyncThunk(
       });
 
       return;
-      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-    } else if ('navigator' && 'sendBeacon' in navigator) {
+    } else if ('sendBeacon' in navigator) {
       // Failing that, we can use sendBeacon, but we have to encode the data as
       // FormData for DoorKeeper to recognize the token.
       const formData = new FormData();

From 4aa26eba53a0c6c5e305f0de9367ea54c83f86db Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 07:11:52 -0400
Subject: [PATCH 34/70] Extract `WebPushRequest` from push notification worker
 and subscription (#32208)

---
 app/lib/web_push_request.rb                   | 72 +++++++++++++++++++
 app/models/web/push_subscription.rb           | 28 --------
 app/workers/web/push_notification_worker.rb   | 40 +++++++----
 .../web/push_notification_worker_spec.rb      | 47 ++++++++----
 4 files changed, 131 insertions(+), 56 deletions(-)
 create mode 100644 app/lib/web_push_request.rb

diff --git a/app/lib/web_push_request.rb b/app/lib/web_push_request.rb
new file mode 100644
index 000000000..a43e22480
--- /dev/null
+++ b/app/lib/web_push_request.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+class WebPushRequest
+  SIGNATURE_ALGORITHM = 'p256ecdsa'
+  AUTH_HEADER = 'WebPush'
+  PAYLOAD_EXPIRATION = 24.hours
+  JWT_ALGORITHM = 'ES256'
+  JWT_TYPE = 'JWT'
+
+  attr_reader :web_push_subscription
+
+  delegate(
+    :endpoint,
+    :key_auth,
+    :key_p256dh,
+    to: :web_push_subscription
+  )
+
+  def initialize(web_push_subscription)
+    @web_push_subscription = web_push_subscription
+  end
+
+  def audience
+    @audience ||= Addressable::URI.parse(endpoint).normalized_site
+  end
+
+  def authorization_header
+    [AUTH_HEADER, encoded_json_web_token].join(' ')
+  end
+
+  def crypto_key_header
+    [SIGNATURE_ALGORITHM, vapid_key.public_key_for_push_header].join('=')
+  end
+
+  def encrypt(payload)
+    Webpush::Encryption.encrypt(payload, key_p256dh, key_auth)
+  end
+
+  private
+
+  def encoded_json_web_token
+    JWT.encode(
+      web_token_payload,
+      vapid_key.curve,
+      JWT_ALGORITHM,
+      typ: JWT_TYPE
+    )
+  end
+
+  def web_token_payload
+    {
+      aud: audience,
+      exp: PAYLOAD_EXPIRATION.from_now.to_i,
+      sub: payload_subject,
+    }
+  end
+
+  def payload_subject
+    [:mailto, contact_email].join(':')
+  end
+
+  def vapid_key
+    @vapid_key ||= Webpush::VapidKey.from_keys(
+      Rails.configuration.x.vapid_public_key,
+      Rails.configuration.x.vapid_private_key
+    )
+  end
+
+  def contact_email
+    @contact_email ||= ::Setting.site_contact_email
+  end
+end
diff --git a/app/models/web/push_subscription.rb b/app/models/web/push_subscription.rb
index ddfd08146..9d30881bf 100644
--- a/app/models/web/push_subscription.rb
+++ b/app/models/web/push_subscription.rb
@@ -29,26 +29,6 @@ class Web::PushSubscription < ApplicationRecord
 
   delegate :locale, to: :associated_user
 
-  def encrypt(payload)
-    Webpush::Encryption.encrypt(payload, key_p256dh, key_auth)
-  end
-
-  def audience
-    @audience ||= Addressable::URI.parse(endpoint).normalized_site
-  end
-
-  def crypto_key_header
-    p256ecdsa = vapid_key.public_key_for_push_header
-
-    "p256ecdsa=#{p256ecdsa}"
-  end
-
-  def authorization_header
-    jwt = JWT.encode({ aud: audience, exp: 24.hours.from_now.to_i, sub: "mailto:#{contact_email}" }, vapid_key.curve, 'ES256', typ: 'JWT')
-
-    "WebPush #{jwt}"
-  end
-
   def pushable?(notification)
     policy_allows_notification?(notification) && alert_enabled_for_notification_type?(notification)
   end
@@ -92,14 +72,6 @@ class Web::PushSubscription < ApplicationRecord
     )
   end
 
-  def vapid_key
-    @vapid_key ||= Webpush::VapidKey.from_keys(Rails.configuration.x.vapid_public_key, Rails.configuration.x.vapid_private_key)
-  end
-
-  def contact_email
-    @contact_email ||= ::Setting.site_contact_email
-  end
-
   def alert_enabled_for_notification_type?(notification)
     truthy?(data&.dig('alerts', notification.type.to_s))
   end
diff --git a/app/workers/web/push_notification_worker.rb b/app/workers/web/push_notification_worker.rb
index 7e9691aab..104503f13 100644
--- a/app/workers/web/push_notification_worker.rb
+++ b/app/workers/web/push_notification_worker.rb
@@ -16,10 +16,10 @@ class Web::PushNotificationWorker
     # in the meantime, so we have to double-check before proceeding
     return unless @notification.activity.present? && @subscription.pushable?(@notification)
 
-    payload = @subscription.encrypt(push_notification_json)
+    payload = web_push_request.encrypt(push_notification_json)
 
-    request_pool.with(@subscription.audience) do |http_client|
-      request = Request.new(:post, @subscription.endpoint, body: payload.fetch(:ciphertext), http_client: http_client)
+    request_pool.with(web_push_request.audience) do |http_client|
+      request = Request.new(:post, web_push_request.endpoint, body: payload.fetch(:ciphertext), http_client: http_client)
 
       request.add_headers(
         'Content-Type' => 'application/octet-stream',
@@ -27,8 +27,8 @@ class Web::PushNotificationWorker
         'Urgency' => URGENCY,
         'Content-Encoding' => 'aesgcm',
         'Encryption' => "salt=#{Webpush.encode64(payload.fetch(:salt)).delete('=')}",
-        'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{@subscription.crypto_key_header}",
-        'Authorization' => @subscription.authorization_header
+        'Crypto-Key' => "dh=#{Webpush.encode64(payload.fetch(:server_public_key)).delete('=')};#{web_push_request.crypto_key_header}",
+        'Authorization' => web_push_request.authorization_header
       )
 
       request.perform do |response|
@@ -50,17 +50,27 @@ class Web::PushNotificationWorker
 
   private
 
-  def push_notification_json
-    json = I18n.with_locale(@subscription.locale.presence || I18n.default_locale) do
-      ActiveModelSerializers::SerializableResource.new(
-        @notification,
-        serializer: Web::NotificationSerializer,
-        scope: @subscription,
-        scope_name: :current_push_subscription
-      ).as_json
-    end
+  def web_push_request
+    @web_push_request || WebPushRequest.new(@subscription)
+  end
 
-    Oj.dump(json)
+  def push_notification_json
+    Oj.dump(serialized_notification_in_subscription_locale.as_json)
+  end
+
+  def serialized_notification_in_subscription_locale
+    I18n.with_locale(@subscription.locale.presence || I18n.default_locale) do
+      serialized_notification
+    end
+  end
+
+  def serialized_notification
+    ActiveModelSerializers::SerializableResource.new(
+      @notification,
+      serializer: Web::NotificationSerializer,
+      scope: @subscription,
+      scope_name: :current_push_subscription
+    )
   end
 
   def request_pool
diff --git a/spec/workers/web/push_notification_worker_spec.rb b/spec/workers/web/push_notification_worker_spec.rb
index ced21d5bf..7f836d99e 100644
--- a/spec/workers/web/push_notification_worker_spec.rb
+++ b/spec/workers/web/push_notification_worker_spec.rb
@@ -22,27 +22,48 @@ RSpec.describe Web::PushNotificationWorker do
   let(:payload) { { ciphertext: ciphertext, salt: salt, server_public_key: server_public_key, shared_secret: shared_secret } }
 
   describe 'perform' do
+    around do |example|
+      original_private = Rails.configuration.x.vapid_private_key
+      original_public = Rails.configuration.x.vapid_public_key
+      Rails.configuration.x.vapid_private_key = vapid_private_key
+      Rails.configuration.x.vapid_public_key = vapid_public_key
+      example.run
+      Rails.configuration.x.vapid_private_key = original_private
+      Rails.configuration.x.vapid_public_key = original_public
+    end
+
     before do
-      allow(subscription).to receive_messages(contact_email: contact_email, vapid_key: vapid_key)
-      allow(Web::PushSubscription).to receive(:find).with(subscription.id).and_return(subscription)
+      Setting.site_contact_email = contact_email
+
       allow(Webpush::Encryption).to receive(:encrypt).and_return(payload)
       allow(JWT).to receive(:encode).and_return('jwt.encoded.payload')
 
       stub_request(:post, endpoint).to_return(status: 201, body: '')
-
-      subject.perform(subscription.id, notification.id)
     end
 
     it 'calls the relevant service with the correct headers' do
-      expect(a_request(:post, endpoint).with(headers: {
-        'Content-Encoding' => 'aesgcm',
-        'Content-Type' => 'application/octet-stream',
-        'Crypto-Key' => "dh=BAgtUks5d90kFmxGevk9tH7GEmvz9DB0qcEMUsOBgKwMf-TMjsKIIG6LQvGcFAf6jcmAod15VVwmYwGIIxE4VWE;p256ecdsa=#{vapid_public_key.delete('=')}",
-        'Encryption' => 'salt=WJeVM-RY-F9351SVxTFx_g',
-        'Ttl' => '172800',
-        'Urgency' => 'normal',
-        'Authorization' => 'WebPush jwt.encoded.payload',
-      }, body: "+\xB8\xDBT}\u0013\xB6\xDD.\xF9\xB0\xA7\xC8Ҁ\xFD\x99#\xF7\xAC\x83\xA4\xDB,\u001F\xB5\xB9w\x85>\xF7\xADr")).to have_been_made
+      subject.perform(subscription.id, notification.id)
+
+      expect(web_push_endpoint_request)
+        .to have_been_made
+    end
+
+    def web_push_endpoint_request
+      a_request(
+        :post,
+        endpoint
+      ).with(
+        headers: {
+          'Content-Encoding' => 'aesgcm',
+          'Content-Type' => 'application/octet-stream',
+          'Crypto-Key' => "dh=BAgtUks5d90kFmxGevk9tH7GEmvz9DB0qcEMUsOBgKwMf-TMjsKIIG6LQvGcFAf6jcmAod15VVwmYwGIIxE4VWE;p256ecdsa=#{vapid_public_key.delete('=')}",
+          'Encryption' => 'salt=WJeVM-RY-F9351SVxTFx_g',
+          'Ttl' => '172800',
+          'Urgency' => 'normal',
+          'Authorization' => 'WebPush jwt.encoded.payload',
+        },
+        body: "+\xB8\xDBT}\u0013\xB6\xDD.\xF9\xB0\xA7\xC8Ҁ\xFD\x99#\xF7\xAC\x83\xA4\xDB,\u001F\xB5\xB9w\x85>\xF7\xADr"
+      )
     end
   end
 end

From 1f65a95421a4dcbe44e25bd9e36329830a396eef Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 08:26:16 -0400
Subject: [PATCH 35/70] Remove unneeded `reorder(nil)` conditions (#32200)

---
 app/lib/vacuum/imports_vacuum.rb                    | 4 ++--
 app/models/account_filter.rb                        | 2 +-
 app/models/admin/tag_filter.rb                      | 2 +-
 app/services/purge_domain_service.rb                | 4 ++--
 app/workers/filtered_notification_cleanup_worker.rb | 2 +-
 app/workers/scheduler/user_cleanup_scheduler.rb     | 2 +-
 6 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/app/lib/vacuum/imports_vacuum.rb b/app/lib/vacuum/imports_vacuum.rb
index 700bd8184..b67865194 100644
--- a/app/lib/vacuum/imports_vacuum.rb
+++ b/app/lib/vacuum/imports_vacuum.rb
@@ -9,10 +9,10 @@ class Vacuum::ImportsVacuum
   private
 
   def clean_unconfirmed_imports!
-    BulkImport.state_unconfirmed.where(created_at: ..10.minutes.ago).reorder(nil).in_batches.delete_all
+    BulkImport.state_unconfirmed.where(created_at: ..10.minutes.ago).in_batches.delete_all
   end
 
   def clean_old_imports!
-    BulkImport.where(created_at: ..1.week.ago).reorder(nil).in_batches.delete_all
+    BulkImport.where(created_at: ..1.week.ago).in_batches.delete_all
   end
 end
diff --git a/app/models/account_filter.rb b/app/models/account_filter.rb
index 42b1c4953..e2f359a8c 100644
--- a/app/models/account_filter.rb
+++ b/app/models/account_filter.rb
@@ -21,7 +21,7 @@ class AccountFilter
   end
 
   def results
-    scope = Account.includes(:account_stat, user: [:ips, :invite_request]).without_instance_actor.reorder(nil)
+    scope = Account.includes(:account_stat, user: [:ips, :invite_request]).without_instance_actor
 
     relevant_params.each do |key, value|
       next if key.to_s == 'page'
diff --git a/app/models/admin/tag_filter.rb b/app/models/admin/tag_filter.rb
index 6149c5217..5e75757b2 100644
--- a/app/models/admin/tag_filter.rb
+++ b/app/models/admin/tag_filter.rb
@@ -14,7 +14,7 @@ class Admin::TagFilter
   end
 
   def results
-    scope = Tag.reorder(nil)
+    scope = Tag.all
 
     params.each do |key, value|
       next if key == :page
diff --git a/app/services/purge_domain_service.rb b/app/services/purge_domain_service.rb
index ca0f0d441..feab8aa1d 100644
--- a/app/services/purge_domain_service.rb
+++ b/app/services/purge_domain_service.rb
@@ -16,12 +16,12 @@ class PurgeDomainService < BaseService
   end
 
   def purge_accounts!
-    Account.remote.where(domain: @domain).reorder(nil).find_each do |account|
+    Account.remote.where(domain: @domain).find_each do |account|
       DeleteAccountService.new.call(account, reserve_username: false, skip_side_effects: true)
     end
   end
 
   def purge_emojis!
-    CustomEmoji.remote.where(domain: @domain).reorder(nil).find_each(&:destroy)
+    CustomEmoji.remote.where(domain: @domain).find_each(&:destroy)
   end
 end
diff --git a/app/workers/filtered_notification_cleanup_worker.rb b/app/workers/filtered_notification_cleanup_worker.rb
index 2b955da3c..87ff6a9eb 100644
--- a/app/workers/filtered_notification_cleanup_worker.rb
+++ b/app/workers/filtered_notification_cleanup_worker.rb
@@ -4,6 +4,6 @@ class FilteredNotificationCleanupWorker
   include Sidekiq::Worker
 
   def perform(account_id, from_account_id)
-    Notification.where(account_id: account_id, from_account_id: from_account_id, filtered: true).reorder(nil).in_batches(order: :desc).delete_all
+    Notification.where(account_id: account_id, from_account_id: from_account_id, filtered: true).in_batches(order: :desc).delete_all
   end
 end
diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb
index 9f58d9225..f75512833 100644
--- a/app/workers/scheduler/user_cleanup_scheduler.rb
+++ b/app/workers/scheduler/user_cleanup_scheduler.rb
@@ -16,7 +16,7 @@ class Scheduler::UserCleanupScheduler
   private
 
   def clean_unconfirmed_accounts!
-    User.unconfirmed.where(confirmation_sent_at: ..UNCONFIRMED_ACCOUNTS_MAX_AGE_DAYS.days.ago).reorder(nil).find_in_batches do |batch|
+    User.unconfirmed.where(confirmation_sent_at: ..UNCONFIRMED_ACCOUNTS_MAX_AGE_DAYS.days.ago).find_in_batches do |batch|
       # We have to do it separately because of missing database constraints
       AccountModerationNote.where(target_account_id: batch.map(&:account_id)).delete_all
       Account.where(id: batch.map(&:account_id)).delete_all

From cf859d151b494a0786c07167f26dc52d0cfd407f Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 09:08:40 -0400
Subject: [PATCH 36/70] More link/button conversion across views (#32199)

---
 app/views/admin/accounts/show.html.haml              |  2 +-
 app/views/admin/instances/show.html.haml             | 12 ++++++------
 app/views/admin/invites/index.html.haml              |  2 +-
 app/views/admin/reports/_actions.html.haml           |  2 +-
 app/views/admin/reports/show.html.haml               |  4 ++--
 app/views/disputes/strikes/show.html.haml            |  4 ++--
 app/views/settings/exports/show.html.haml            |  2 +-
 .../otp_authentication/show.html.haml                |  2 +-
 .../index.html.haml                                  |  4 ++--
 9 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index f148b9a08..2d9e30e36 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -30,7 +30,7 @@
 = render 'admin/accounts/counters', account: @account
 
 - if @account.local? && @account.user.nil?
-  = link_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unblock_email, @account) && CanonicalEmailBlock.exists?(reference_account_id: @account.id)
+  = button_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), class: :button if can?(:unblock_email, @account) && CanonicalEmailBlock.exists?(reference_account_id: @account.id)
 - else
   .table-wrapper
     %table.table.inline-table
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index 812a9c887..dd4c50549 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -21,7 +21,7 @@
   - if @instance.domain_allow
     = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@instance.domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
   - else
-    = link_to t('admin.domain_allows.add_new'), admin_domain_allows_path(domain_allow: { domain: @instance.domain }), class: 'button', method: :post
+    = button_to t('admin.domain_allows.add_new'), admin_domain_allows_path(domain_allow: { domain: @instance.domain }), class: :button
 - else
   %p= t('admin.instances.content_policies.description_html')
 
@@ -40,7 +40,7 @@
             %td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ')
 
     = link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button'
-    = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
+    = button_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: :button, data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
   - else
     = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
 
@@ -70,16 +70,16 @@
       - if @instance.unavailable?
         %span.negative-hint
           = t('admin.instances.availability.failure_threshold_reached', date: l(@instance.unavailable_domain.created_at.to_date))
-          = link_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
+          = button_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') }
       - elsif @instance.exhausted_deliveries_days.empty?
         %span.positive-hint
           = t('admin.instances.availability.no_failures_recorded')
-          = link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
+          = button_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') }
       - else
         %span.negative-hint
           = t('admin.instances.availability.failures_recorded', count: @instance.delivery_failure_tracker.days)
-          %span= link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty?
-          %span= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
+          %span= button_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') } unless @instance.exhausted_deliveries_days.empty?
+          %span= button_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') }
 
   - if @instance.purgeable?
     %p= t('admin.instances.purge_description_html')
diff --git a/app/views/admin/invites/index.html.haml b/app/views/admin/invites/index.html.haml
index 964deaba8..bbccca314 100644
--- a/app/views/admin/invites/index.html.haml
+++ b/app/views/admin/invites/index.html.haml
@@ -34,4 +34,4 @@
 = paginate @invites
 
 - if policy(:invite).deactivate_all?
-  = link_to t('admin.invites.deactivate_all'), deactivate_all_admin_invites_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
+  = button_to t('admin.invites.deactivate_all'), deactivate_all_admin_invites_path, data: { confirm: t('admin.accounts.are_you_sure') }, class: :button
diff --git a/app/views/admin/reports/_actions.html.haml b/app/views/admin/reports/_actions.html.haml
index ef016e949..c25f45e16 100644
--- a/app/views/admin/reports/_actions.html.haml
+++ b/app/views/admin/reports/_actions.html.haml
@@ -2,7 +2,7 @@
   .report-actions
     .report-actions__item
       .report-actions__item__button
-        = link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(report), method: :post, class: 'button'
+        = button_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(report), class: :button
       .report-actions__item__description
         = t('admin.reports.actions.resolve_description_html')
     - if statuses.any? { |status| (status.with_media? || status.with_preview_card?) && !status.discarded? }
diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml
index 69e9c0292..32b6b3be8 100644
--- a/app/views/admin/reports/show.html.haml
+++ b/app/views/admin/reports/show.html.haml
@@ -3,9 +3,9 @@
 
 - content_for :heading_actions do
   - if @report.unresolved?
-    = link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button'
+    = button_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), class: :button
   - else
-    = link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button'
+    = button_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), class: :button
 
 - unless @report.account.local? || @report.target_account.local?
   .flash-message= t('admin.reports.forwarded_replies_explanation')
diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml
index 150dc0675..1d71e0ddd 100644
--- a/app/views/disputes/strikes/show.html.haml
+++ b/app/views/disputes/strikes/show.html.haml
@@ -3,8 +3,8 @@
 
 - content_for :heading_actions do
   - if @appeal.persisted?
-    = link_to t('disputes.strikes.approve_appeal'), approve_admin_disputes_appeal_path(@appeal), method: :post, class: 'button' if can?(:approve, @appeal)
-    = link_to t('disputes.strikes.reject_appeal'), reject_admin_disputes_appeal_path(@appeal), method: :post, class: 'button button--destructive' if can?(:reject, @appeal)
+    = button_to t('disputes.strikes.approve_appeal'), approve_admin_disputes_appeal_path(@appeal), class: :button if can?(:approve, @appeal)
+    = button_to t('disputes.strikes.reject_appeal'), reject_admin_disputes_appeal_path(@appeal), class: 'button button--destructive' if can?(:reject, @appeal)
 
 - if @strike.overruled?
   %p.hint
diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml
index 273c5a4ba..61d55350c 100644
--- a/app/views/settings/exports/show.html.haml
+++ b/app/views/settings/exports/show.html.haml
@@ -46,7 +46,7 @@
 %p.muted-hint= t('exports.archive_takeout.hint_html')
 
 - if policy(:backup).create?
-  %p= link_to t('exports.archive_takeout.request'), settings_export_path, class: 'button', method: :post
+  %p= button_to t('exports.archive_takeout.request'), settings_export_path, class: :button
 
 - unless @backups.empty?
   %hr.spacer/
diff --git a/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml b/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml
index d069ba12a..01f385dbe 100644
--- a/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml
+++ b/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml
@@ -6,4 +6,4 @@
 
   %hr.spacer/
 
-  = link_to t('otp_authentication.setup'), settings_otp_authentication_path, data: { method: :post }, class: 'block-button'
+  = button_to t('otp_authentication.setup'), settings_otp_authentication_path, class: 'block-button'
diff --git a/app/views/settings/two_factor_authentication_methods/index.html.haml b/app/views/settings/two_factor_authentication_methods/index.html.haml
index 8b670283b..e3a211a3d 100644
--- a/app/views/settings/two_factor_authentication_methods/index.html.haml
+++ b/app/views/settings/two_factor_authentication_methods/index.html.haml
@@ -2,7 +2,7 @@
   = t('settings.two_factor_authentication')
 
 - content_for :heading_actions do
-  = link_to t('two_factor_authentication.disable'), disable_settings_two_factor_authentication_methods_path, class: 'button button--destructive', method: :post
+  = button_to t('two_factor_authentication.disable'), disable_settings_two_factor_authentication_methods_path, class: 'button button--destructive'
 
 %p.hint
   %span.positive-hint
@@ -38,4 +38,4 @@
 %hr.spacer/
 
 .simple_form
-  = link_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, data: { method: :post }, class: 'block-button'
+  = button_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, class: 'block-button'

From 2151dfb8d687f08438fc76d8fdf670a473d6a1e8 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 09:24:40 -0400
Subject: [PATCH 37/70] Add `relevant_params` to ReportFilter (matches account
 filter) (#32136)

---
 app/models/report_filter.rb       | 14 +++++++++++++-
 spec/models/report_filter_spec.rb | 13 +++++++++++++
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/app/models/report_filter.rb b/app/models/report_filter.rb
index fd0e23cb8..9d2b0fb37 100644
--- a/app/models/report_filter.rb
+++ b/app/models/report_filter.rb
@@ -18,13 +18,25 @@ class ReportFilter
   def results
     scope = Report.unresolved
 
-    params.each do |key, value|
+    relevant_params.each do |key, value|
       scope = scope.merge scope_for(key, value)
     end
 
     scope
   end
 
+  private
+
+  def relevant_params
+    params.tap do |args|
+      args.delete(:target_origin) if origin_is_remote_and_domain_present?
+    end
+  end
+
+  def origin_is_remote_and_domain_present?
+    params[:target_origin] == 'remote' && params[:by_target_domain].present?
+  end
+
   def scope_for(key, value)
     case key.to_sym
     when :by_target_domain
diff --git a/spec/models/report_filter_spec.rb b/spec/models/report_filter_spec.rb
index 8668eb3d1..51933e475 100644
--- a/spec/models/report_filter_spec.rb
+++ b/spec/models/report_filter_spec.rb
@@ -30,4 +30,17 @@ RSpec.describe ReportFilter do
       expect(Report).to have_received(:resolved)
     end
   end
+
+  context 'when given remote target_origin and also by_target_domain' do
+    let!(:matching_report) { Fabricate :report, target_account: Fabricate(:account, domain: 'match.example') }
+    let!(:non_matching_report) { Fabricate :report, target_account: Fabricate(:account, domain: 'other.example') }
+
+    it 'preserves the domain value' do
+      filter = described_class.new(by_target_domain: 'match.example', target_origin: 'remote')
+
+      expect(filter.results)
+        .to include(matching_report)
+        .and not_include(non_matching_report)
+    end
+  end
 end

From d270d6dd4fce6dd15107ee1fe9b7877a5fca02d9 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 09:26:46 -0400
Subject: [PATCH 38/70] Provide `use_path` to qr generator for svg data size
 reduction (#32127)

---
 .../two_factor_authentication/confirmations/new.html.haml       | 2 +-
 .../two_factor_authentication/confirmations_controller_spec.rb  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/views/settings/two_factor_authentication/confirmations/new.html.haml b/app/views/settings/two_factor_authentication/confirmations/new.html.haml
index 0b8278a10..a35479b84 100644
--- a/app/views/settings/two_factor_authentication/confirmations/new.html.haml
+++ b/app/views/settings/two_factor_authentication/confirmations/new.html.haml
@@ -5,7 +5,7 @@
   %p.hint= t('otp_authentication.instructions_html')
 
   .qr-wrapper
-    .qr-code!= @qrcode.as_svg(padding: 0, module_size: 4)
+    .qr-code!= @qrcode.as_svg(padding: 0, module_size: 4, use_path: true)
 
     .qr-alternative
       %p.hint= t('otp_authentication.manual_instructions')
diff --git a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
index 34eaacdf4..224310b7e 100644
--- a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
+++ b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb
@@ -18,7 +18,7 @@ RSpec.describe Settings::TwoFactorAuthentication::ConfirmationsController do
     def qr_code_markup
       RQRCode::QRCode.new(
         'otpauth://totp/cb6e6126.ngrok.io:local-part%40domain?secret=thisisasecretforthespecofnewview&issuer=cb6e6126.ngrok.io'
-      ).as_svg(padding: 0, module_size: 4)
+      ).as_svg(padding: 0, module_size: 4, use_path: true)
     end
   end
 

From b5006539c995cbefda04db25888ffe5bb5995d3f Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Wed, 2 Oct 2024 15:28:36 +0200
Subject: [PATCH 39/70] Fix media uploads in composer appearing over search
 results in advanced interface (#32217)

---
 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 14de6e681..5f410ead9 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -3611,6 +3611,7 @@ $ui-header-logo-wordmark-width: 99px;
   overflow-y: auto;
   width: 100%;
   height: 100%;
+  z-index: 0;
 }
 
 .drawer__inner__mastodon {

From f760899b04ef5647d8688e9f1f08094fb3cf5894 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Wed, 2 Oct 2024 15:29:23 +0200
Subject: [PATCH 40/70] Fix editing description of media uploads with custom
 thumbnails (#32221)

---
 app/javascript/mastodon/reducers/compose.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index e77f2ac6e..ba975b75a 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -402,7 +402,7 @@ export default function compose(state = initialState, action) {
       .set('isUploadingThumbnail', false)
       .update('media_attachments', list => list.map(item => {
         if (item.get('id') === action.media.id) {
-          return fromJS(action.media);
+          return fromJS(action.media).set('unattached', item.get('unattached'));
         }
 
         return item;

From b7bb850efd485466d4424f4744c1795541ca592f Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 09:56:26 -0400
Subject: [PATCH 41/70] Enable hostname config for all system specs (#32109)

---
 spec/rails_helper.rb        | 5 +++++
 spec/system/invites_spec.rb | 5 +----
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index ee03b49bc..84cee0974 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -161,6 +161,11 @@ RSpec.configure do |config|
     host! Rails.configuration.x.local_domain
   end
 
+  config.before :each, type: :system do
+    # Align with capybara config so that rails helpers called from rspec use matching host
+    host! 'localhost:3000'
+  end
+
   config.after do
     Rails.cache.clear
     redis.del(redis.keys)
diff --git a/spec/system/invites_spec.rb b/spec/system/invites_spec.rb
index c57de871c..fc60ce591 100644
--- a/spec/system/invites_spec.rb
+++ b/spec/system/invites_spec.rb
@@ -7,10 +7,7 @@ RSpec.describe 'Invites' do
 
   let(:user) { Fabricate :user }
 
-  before do
-    host! 'localhost:3000' # TODO: Move into before for all system specs?
-    sign_in user
-  end
+  before { sign_in user }
 
   describe 'Viewing invites' do
     it 'Lists existing user invites' do

From 36f9c96812c313c5f782a702df221ad4b57da0f3 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 10:45:12 -0400
Subject: [PATCH 42/70] Clean up labels on development application form
 (#32116)

---
 app/helpers/application_helper.rb               | 13 +++++--------
 app/models/session_activation.rb                |  4 +++-
 app/views/settings/applications/_form.html.haml |  4 ++--
 3 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index de00f76d3..a40580fba 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,12 +1,6 @@
 # frozen_string_literal: true
 
 module ApplicationHelper
-  DANGEROUS_SCOPES = %w(
-    read
-    write
-    follow
-  ).freeze
-
   RTL_LOCALES = %i(
     ar
     ckb
@@ -95,8 +89,11 @@ module ApplicationHelper
     Rails.env.production? ? site_title : "#{site_title} (Dev)"
   end
 
-  def class_for_scope(scope)
-    'scope-danger' if DANGEROUS_SCOPES.include?(scope.to_s)
+  def label_for_scope(scope)
+    safe_join [
+      tag.samp(scope, class: { 'scope-danger' => SessionActivation::DEFAULT_SCOPES.include?(scope.to_s) }),
+      tag.span(t("doorkeeper.scopes.#{scope}"), class: :hint),
+    ]
   end
 
   def can?(action, record)
diff --git a/app/models/session_activation.rb b/app/models/session_activation.rb
index d0a77daf0..8b8e533d3 100644
--- a/app/models/session_activation.rb
+++ b/app/models/session_activation.rb
@@ -28,6 +28,8 @@ class SessionActivation < ApplicationRecord
 
   before_create :assign_access_token
 
+  DEFAULT_SCOPES = %w(read write follow).freeze
+
   class << self
     def active?(id)
       id && exists?(session_id: id)
@@ -64,7 +66,7 @@ class SessionActivation < ApplicationRecord
     {
       application_id: Doorkeeper::Application.find_by(superapp: true)&.id,
       resource_owner_id: user_id,
-      scopes: 'read write follow',
+      scopes: DEFAULT_SCOPES.join(' '),
       expires_in: Doorkeeper.configuration.access_token_expires_in,
       use_refresh_token: Doorkeeper.configuration.refresh_token_enabled?,
     }
diff --git a/app/views/settings/applications/_form.html.haml b/app/views/settings/applications/_form.html.haml
index 66ea8bc12..85fdefa0f 100644
--- a/app/views/settings/applications/_form.html.haml
+++ b/app/views/settings/applications/_form.html.haml
@@ -18,7 +18,7 @@
 
 .field-group
   .input.with_block_label
-    %label= t('activerecord.attributes.doorkeeper/application.scopes')
+    = form.label t('activerecord.attributes.doorkeeper/application.scopes'), required: true
     %span.hint= t('simple_form.hints.defaults.scopes')
 
   - Doorkeeper.configuration.scopes.group_by { |s| s.split(':').first }.each_value do |value|
@@ -29,7 +29,7 @@
                  hint: false,
                  include_blank: false,
                  item_wrapper_tag: 'li',
-                 label_method: ->(scope) { safe_join([content_tag(:samp, scope, class: class_for_scope(scope)), content_tag(:span, t("doorkeeper.scopes.#{scope}"), class: 'hint')]) },
+                 label_method: ->(scope) { label_for_scope(scope) },
                  label: false,
                  required: false,
                  selected: form.object.scopes.all,

From 7d6b9ccd34fd10cce33114ac13201905222a807e Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 10:45:54 -0400
Subject: [PATCH 43/70] Add `copyable_input` helper method to wrap shared
 options (#32119)

---
 app/helpers/application_helper.rb               | 4 ++++
 app/views/admin/invites/_invite.html.haml       | 2 +-
 app/views/invites/_invite.html.haml             | 2 +-
 app/views/oauth/authorizations/show.html.haml   | 2 +-
 app/views/settings/verifications/show.html.haml | 2 +-
 5 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index a40580fba..8f9a433d8 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -240,6 +240,10 @@ module ApplicationHelper
     full_asset_url(instance_presenter.mascot&.file&.url || frontend_asset_path('images/elephant_ui_plane.svg'))
   end
 
+  def copyable_input(options = {})
+    tag.input(type: :text, maxlength: 999, spellcheck: false, readonly: true, **options)
+  end
+
   private
 
   def storage_host_var
diff --git a/app/views/admin/invites/_invite.html.haml b/app/views/admin/invites/_invite.html.haml
index 8bd5f10fe..53eac1d0c 100644
--- a/app/views/admin/invites/_invite.html.haml
+++ b/app/views/admin/invites/_invite.html.haml
@@ -2,7 +2,7 @@
   %td
     .input-copy
       .input-copy__wrapper
-        %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) }
+        = copyable_input value: public_invite_url(invite_code: invite.code)
       %button{ type: :button }= t('generic.copy')
 
   %td
diff --git a/app/views/invites/_invite.html.haml b/app/views/invites/_invite.html.haml
index 892fdc5a0..7c94062de 100644
--- a/app/views/invites/_invite.html.haml
+++ b/app/views/invites/_invite.html.haml
@@ -2,7 +2,7 @@
   %td
     .input-copy
       .input-copy__wrapper
-        %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: public_invite_url(invite_code: invite.code) }
+        = copyable_input value: public_invite_url(invite_code: invite.code)
       %button{ type: :button }= t('generic.copy')
 
   - if invite.valid_for_use?
diff --git a/app/views/oauth/authorizations/show.html.haml b/app/views/oauth/authorizations/show.html.haml
index a5122a87f..bdff33636 100644
--- a/app/views/oauth/authorizations/show.html.haml
+++ b/app/views/oauth/authorizations/show.html.haml
@@ -3,5 +3,5 @@
     %p= t('doorkeeper.authorizations.show.title')
     .input-copy
       .input-copy__wrapper
-        %input.oauth-code{ type: 'text', spellcheck: 'false', readonly: true, value: params[:code] }
+        = copyable_input value: params[:code], class: 'oauth-code'
       %button{ type: :button }= t('generic.copy')
diff --git a/app/views/settings/verifications/show.html.haml b/app/views/settings/verifications/show.html.haml
index 00491866c..560807f27 100644
--- a/app/views/settings/verifications/show.html.haml
+++ b/app/views/settings/verifications/show.html.haml
@@ -16,7 +16,7 @@
 
   .input-copy.lead
     .input-copy__wrapper
-      %input{ type: :text, maxlength: '999', spellcheck: 'false', readonly: 'true', value: link_to('Mastodon', ActivityPub::TagManager.instance.url_for(@account), rel: 'me').to_str }
+      = copyable_input value: link_to('Mastodon', ActivityPub::TagManager.instance.url_for(@account), rel: :me)
     %button{ type: :button }= t('generic.copy')
 
   %p.lead= t('verification.extra_instructions_html')

From cec89613720c23cb18d566ae05a6f69ac52d115e Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Wed, 2 Oct 2024 10:47:00 -0400
Subject: [PATCH 44/70] Move admin action log type list generation to helper
 (#32178)

---
 app/helpers/admin/action_logs_helper.rb     | 7 +++++++
 app/views/admin/action_logs/index.html.haml | 2 +-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/app/helpers/admin/action_logs_helper.rb b/app/helpers/admin/action_logs_helper.rb
index e8d563412..51e28d8b4 100644
--- a/app/helpers/admin/action_logs_helper.rb
+++ b/app/helpers/admin/action_logs_helper.rb
@@ -35,4 +35,11 @@ module Admin::ActionLogsHelper
       end
     end
   end
+
+  def sorted_action_log_types
+    Admin::ActionLogFilter::ACTION_TYPE_MAP
+      .keys
+      .map { |key| [I18n.t("admin.action_logs.action_types.#{key}"), key] }
+      .sort_by(&:first)
+  end
 end
diff --git a/app/views/admin/action_logs/index.html.haml b/app/views/admin/action_logs/index.html.haml
index c02c8f0ad..a5d418829 100644
--- a/app/views/admin/action_logs/index.html.haml
+++ b/app/views/admin/action_logs/index.html.haml
@@ -16,7 +16,7 @@
       %strong= t('admin.action_logs.filter_by_action')
       .input.select.optional
         = form.select :action_type,
-                      options_for_select(Admin::ActionLogFilter::ACTION_TYPE_MAP.keys.map { |key| [I18n.t("admin.action_logs.action_types.#{key}"), key] }, params[:action_type]),
+                      options_for_select(sorted_action_log_types, params[:action_type]),
                       prompt: I18n.t('admin.accounts.moderation.all')
 
 - if @action_logs.empty?

From f768a6eb16880f4e584db530e4106be9b9dcf206 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Wed, 2 Oct 2024 17:51:05 +0200
Subject: [PATCH 45/70] Hide badges in media gallery when media are hidden
 (#32224)

---
 app/javascript/mastodon/components/media_gallery.jsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx
index 84cb4e04d..1380d244a 100644
--- a/app/javascript/mastodon/components/media_gallery.jsx
+++ b/app/javascript/mastodon/components/media_gallery.jsx
@@ -196,7 +196,7 @@ class Item extends PureComponent {
 
         {visible && thumbnail}
 
-        {badges && (
+        {visible && badges && (
           <div className='media-gallery__item__badges'>
             {badges}
           </div>

From 6ef510fe3f4a08908df223d33d2788f851d0cb52 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 3 Oct 2024 11:04:12 +0200
Subject: [PATCH 46/70] Update dependency json-schema to v5.0.1 (#32234)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Gemfile.lock | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 3950a30d6..987234a73 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -369,7 +369,7 @@ GEM
     json-ld-preloaded (3.3.0)
       json-ld (~> 3.3)
       rdf (~> 3.3)
-    json-schema (5.0.0)
+    json-schema (5.0.1)
       addressable (~> 2.8)
     jsonapi-renderer (0.2.2)
     jwt (2.7.1)

From 0be1d332f28ca31e734954d7b8d73b41d60f485f Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 3 Oct 2024 11:04:33 +0200
Subject: [PATCH 47/70] Update dependency aws-sdk-s3 to v1.167.0 (#32231)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Gemfile.lock | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/Gemfile.lock b/Gemfile.lock
index 987234a73..1c2bb8df0 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -100,8 +100,8 @@ GEM
     attr_required (1.0.2)
     awrence (1.2.1)
     aws-eventstream (1.3.0)
-    aws-partitions (1.978.0)
-    aws-sdk-core (3.209.0)
+    aws-partitions (1.983.0)
+    aws-sdk-core (3.209.1)
       aws-eventstream (~> 1, >= 1.3.0)
       aws-partitions (~> 1, >= 1.651.0)
       aws-sigv4 (~> 1.9)
@@ -109,7 +109,7 @@ GEM
     aws-sdk-kms (1.94.0)
       aws-sdk-core (~> 3, >= 3.207.0)
       aws-sigv4 (~> 1.5)
-    aws-sdk-s3 (1.166.0)
+    aws-sdk-s3 (1.167.0)
       aws-sdk-core (~> 3, >= 3.207.0)
       aws-sdk-kms (~> 1)
       aws-sigv4 (~> 1.5)

From d96351a87d7078e8d6a07486d7d30e4a7956745d Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 3 Oct 2024 09:05:11 +0000
Subject: [PATCH 48/70] Update babel monorepo to v7.25.7 (#32225)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 yarn.lock | 1366 ++++++++++++++++++++++++++---------------------------
 1 file changed, 679 insertions(+), 687 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 8970981d8..0a7f89990 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -42,127 +42,127 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/code-frame@npm:7.24.7"
+"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/code-frame@npm:7.25.7"
   dependencies:
-    "@babel/highlight": "npm:^7.24.7"
+    "@babel/highlight": "npm:^7.25.7"
     picocolors: "npm:^1.0.0"
-  checksum: 10c0/ab0af539473a9f5aeaac7047e377cb4f4edd255a81d84a76058595f8540784cc3fbe8acf73f1e073981104562490aabfb23008cd66dc677a456a4ed5390fdde6
+  checksum: 10c0/14825c298bdec914caf3d24d1383b6d4cd6b030714686004992f4fc251831ecf432236652896f99d5d341f17170ae9a07b58d8d7b15aa0df8cfa1c5a7d5474bc
   languageName: node
   linkType: hard
 
-"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.2, @babel/compat-data@npm:^7.25.4":
-  version: 7.25.4
-  resolution: "@babel/compat-data@npm:7.25.4"
-  checksum: 10c0/50d79734d584a28c69d6f5b99adfaa064d0f41609a378aef04eb06accc5b44f8520e68549eba3a082478180957b7d5783f1bfb1672e4ae8574e797ce8bae79fa
+"@babel/compat-data@npm:^7.22.6, @babel/compat-data@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/compat-data@npm:7.25.7"
+  checksum: 10c0/e5cc915abdd18d021236474a96606b2d4a915c4fb620c1ad776b8a08d91111e788cb3b7e9bad43593d4e0bfa4f06894357bcb0984102de1861b9e7322b6bc9f8
   languageName: node
   linkType: hard
 
 "@babel/core@npm:^7.10.4, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3, @babel/core@npm:^7.22.1, @babel/core@npm:^7.24.4":
-  version: 7.25.2
-  resolution: "@babel/core@npm:7.25.2"
+  version: 7.25.7
+  resolution: "@babel/core@npm:7.25.7"
   dependencies:
     "@ampproject/remapping": "npm:^2.2.0"
-    "@babel/code-frame": "npm:^7.24.7"
-    "@babel/generator": "npm:^7.25.0"
-    "@babel/helper-compilation-targets": "npm:^7.25.2"
-    "@babel/helper-module-transforms": "npm:^7.25.2"
-    "@babel/helpers": "npm:^7.25.0"
-    "@babel/parser": "npm:^7.25.0"
-    "@babel/template": "npm:^7.25.0"
-    "@babel/traverse": "npm:^7.25.2"
-    "@babel/types": "npm:^7.25.2"
+    "@babel/code-frame": "npm:^7.25.7"
+    "@babel/generator": "npm:^7.25.7"
+    "@babel/helper-compilation-targets": "npm:^7.25.7"
+    "@babel/helper-module-transforms": "npm:^7.25.7"
+    "@babel/helpers": "npm:^7.25.7"
+    "@babel/parser": "npm:^7.25.7"
+    "@babel/template": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
     convert-source-map: "npm:^2.0.0"
     debug: "npm:^4.1.0"
     gensync: "npm:^1.0.0-beta.2"
     json5: "npm:^2.2.3"
     semver: "npm:^6.3.1"
-  checksum: 10c0/a425fa40e73cb72b6464063a57c478bc2de9dbcc19c280f1b55a3d88b35d572e87e8594e7d7b4880331addb6faef641bbeb701b91b41b8806cd4deae5d74f401
+  checksum: 10c0/dad20af39624086afc3a0910bd97ae712c9ad0e9dda09fc5da93876e8ea1802b63ddd81c44f4aa8a9834db46de801eaab1ce9b81ab54b4fe907ae052c24de136
   languageName: node
   linkType: hard
 
-"@babel/generator@npm:^7.25.0, @babel/generator@npm:^7.25.4, @babel/generator@npm:^7.7.2":
-  version: 7.25.4
-  resolution: "@babel/generator@npm:7.25.4"
+"@babel/generator@npm:^7.25.7, @babel/generator@npm:^7.7.2":
+  version: 7.25.7
+  resolution: "@babel/generator@npm:7.25.7"
   dependencies:
-    "@babel/types": "npm:^7.25.4"
+    "@babel/types": "npm:^7.25.7"
     "@jridgewell/gen-mapping": "npm:^0.3.5"
     "@jridgewell/trace-mapping": "npm:^0.3.25"
-    jsesc: "npm:^2.5.1"
-  checksum: 10c0/a2d8cc39e759214740f836360c8d9c17aa93e16e41afe73368a9e7ccd1d5c3303a420ce3aca1c9a31fdb93d1899de471d5aac97d1c64f741f8750a25a6e91fbc
+    jsesc: "npm:^3.0.2"
+  checksum: 10c0/c03a26c79864d60d04ce36b649c3fa0d6fd7b2bf6a22e22854a0457aa09206508392dd73ee40e7bc8d50b3602f9ff068afa47770cda091d332e7db1ca382ee96
   languageName: node
   linkType: hard
 
-"@babel/helper-annotate-as-pure@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/helper-annotate-as-pure@npm:7.24.7"
+"@babel/helper-annotate-as-pure@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-annotate-as-pure@npm:7.25.7"
   dependencies:
-    "@babel/types": "npm:^7.24.7"
-  checksum: 10c0/4679f7df4dffd5b3e26083ae65228116c3da34c3fff2c11ae11b259a61baec440f51e30fd236f7a0435b9d471acd93d0bc5a95df8213cbf02b1e083503d81b9a
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/2f020b0fa9d336b5778485cc2de3141561ec436a7591b685457a5bcdae4ce41d9ddee68169c95504e0789e5a4327e73b8b7e72e5b60e82e96d730c4d19255248
   languageName: node
   linkType: hard
 
-"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.24.7"
+"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.25.7"
   dependencies:
-    "@babel/traverse": "npm:^7.24.7"
-    "@babel/types": "npm:^7.24.7"
-  checksum: 10c0/0ed84abf848c79fb1cd4c1ddac12c771d32c1904d87fc3087f33cfdeb0c2e0db4e7892b74b407d9d8d0c000044f3645a7391a781f788da8410c290bb123a1f13
+    "@babel/traverse": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/e9dc5a7920a1d74150dec53ccd5e34f2b31ae307df7cdeec6289866f7bda97ecb1328b49a7710ecde5db5b6daad768c904a030f9a0fa3184963b0017622c42aa
   languageName: node
   linkType: hard
 
-"@babel/helper-builder-react-jsx@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/helper-builder-react-jsx@npm:7.24.7"
+"@babel/helper-builder-react-jsx@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-builder-react-jsx@npm:7.25.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
-    "@babel/types": "npm:^7.24.7"
-  checksum: 10c0/c95c8856c67c57060461f39b669707b2dca3501149bcc54b7c3e49c95069afce52179fc7c2bd809981acfbfdf0860ef1035dd7512cdba46c83bdb1ad6f6dc53f
+    "@babel/helper-annotate-as-pure": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/f8123a76e8c3fcdbb24cc14bfefc80e4c7bf58112ab26bea3247298748c674cbaee70591c362d0d881e4d88154ea4809b145f3ddcf96221cf55ba27bfde535c6
   languageName: node
   linkType: hard
 
-"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.24.7, @babel/helper-compilation-targets@npm:^7.24.8, @babel/helper-compilation-targets@npm:^7.25.2":
-  version: 7.25.2
-  resolution: "@babel/helper-compilation-targets@npm:7.25.2"
+"@babel/helper-compilation-targets@npm:^7.22.6, @babel/helper-compilation-targets@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-compilation-targets@npm:7.25.7"
   dependencies:
-    "@babel/compat-data": "npm:^7.25.2"
-    "@babel/helper-validator-option": "npm:^7.24.8"
-    browserslist: "npm:^4.23.1"
+    "@babel/compat-data": "npm:^7.25.7"
+    "@babel/helper-validator-option": "npm:^7.25.7"
+    browserslist: "npm:^4.24.0"
     lru-cache: "npm:^5.1.1"
     semver: "npm:^6.3.1"
-  checksum: 10c0/de10e986b5322c9f807350467dc845ec59df9e596a5926a3b5edbb4710d8e3b8009d4396690e70b88c3844fe8ec4042d61436dd4b92d1f5f75655cf43ab07e99
+  checksum: 10c0/705be7e5274a3fdade68e3e2cf42e2b600316ab52794e13b91299a16f16c926f15886b6e9d6df20eb943ccc1cdba5a363d4766f8d01e47b8e6f4e01175f5e66c
   languageName: node
   linkType: hard
 
-"@babel/helper-create-class-features-plugin@npm:^7.24.7, @babel/helper-create-class-features-plugin@npm:^7.25.4":
-  version: 7.25.4
-  resolution: "@babel/helper-create-class-features-plugin@npm:7.25.4"
+"@babel/helper-create-class-features-plugin@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-create-class-features-plugin@npm:7.25.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
-    "@babel/helper-member-expression-to-functions": "npm:^7.24.8"
-    "@babel/helper-optimise-call-expression": "npm:^7.24.7"
-    "@babel/helper-replace-supers": "npm:^7.25.0"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7"
-    "@babel/traverse": "npm:^7.25.4"
+    "@babel/helper-annotate-as-pure": "npm:^7.25.7"
+    "@babel/helper-member-expression-to-functions": "npm:^7.25.7"
+    "@babel/helper-optimise-call-expression": "npm:^7.25.7"
+    "@babel/helper-replace-supers": "npm:^7.25.7"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
     semver: "npm:^6.3.1"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/a765d9e0482e13cf96642fa8aa28e6f7d4d7d39f37840d6246e5e10a7c47f47c52d52522edd3073f229449d17ec0db6f9b7b5e398bff6bb0b4994d65957a164c
+  checksum: 10c0/405c3c1a137acda1206380a96993cf2cfd808b3bee1c11c4af47ee0f03a20858497aa53394d6adc5431793c543be5e02010620e871a5ab39d938ae90a54b50f2
   languageName: node
   linkType: hard
 
-"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.24.7, @babel/helper-create-regexp-features-plugin@npm:^7.25.0, @babel/helper-create-regexp-features-plugin@npm:^7.25.2":
-  version: 7.25.2
-  resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.2"
+"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-create-regexp-features-plugin@npm:7.25.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
-    regexpu-core: "npm:^5.3.1"
+    "@babel/helper-annotate-as-pure": "npm:^7.25.7"
+    regexpu-core: "npm:^6.1.1"
     semver: "npm:^6.3.1"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/85a7e3639c118856fb1113f54fb7e3bf7698171ddfd0cd6fccccd5426b3727bc1434fe7f69090441dcde327feef9de917e00d35e47ab820047057518dd675317
+  checksum: 10c0/75919fd5a67cd7be8497b56f7b9ed6b4843cb401956ba8d403aa9ae5b005bc28e35c7f27e704d820edbd1154394ed7a7984d4719916795d89d716f6980fe8bd4
   languageName: node
   linkType: hard
 
@@ -181,223 +181,223 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/helper-member-expression-to-functions@npm:^7.24.8":
-  version: 7.24.8
-  resolution: "@babel/helper-member-expression-to-functions@npm:7.24.8"
+"@babel/helper-member-expression-to-functions@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-member-expression-to-functions@npm:7.25.7"
   dependencies:
-    "@babel/traverse": "npm:^7.24.8"
-    "@babel/types": "npm:^7.24.8"
-  checksum: 10c0/7e14a5acc91f6cd26305a4441b82eb6f616bd70b096a4d2099a968f16b26d50207eec0b9ebfc466fefd62bd91587ac3be878117cdfec819b7151911183cb0e5a
+    "@babel/traverse": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/1e948162ab48d84593a7c6ec9570d14c906146f1697144fc369c59dbeb00e4a062da67dd06cb0d8f98a044cd8389002dcf2ab6f5613d99c35748307846ec63fc
   languageName: node
   linkType: hard
 
-"@babel/helper-module-imports@npm:^7.0.0-beta.49, @babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/helper-module-imports@npm:7.24.7"
+"@babel/helper-module-imports@npm:^7.0.0-beta.49, @babel/helper-module-imports@npm:^7.10.4, @babel/helper-module-imports@npm:^7.16.7, @babel/helper-module-imports@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-module-imports@npm:7.25.7"
   dependencies:
-    "@babel/traverse": "npm:^7.24.7"
-    "@babel/types": "npm:^7.24.7"
-  checksum: 10c0/97c57db6c3eeaea31564286e328a9fb52b0313c5cfcc7eee4bc226aebcf0418ea5b6fe78673c0e4a774512ec6c86e309d0f326e99d2b37bfc16a25a032498af0
+    "@babel/traverse": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/0fd0c3673835e5bf75558e184bcadc47c1f6dd2fe2016d53ebe1e5a6ae931a44e093015c2f9a6651c1a89f25c76d9246710c2b0b460b95ee069c464f2837fa2c
   languageName: node
   linkType: hard
 
-"@babel/helper-module-transforms@npm:^7.24.7, @babel/helper-module-transforms@npm:^7.24.8, @babel/helper-module-transforms@npm:^7.25.0, @babel/helper-module-transforms@npm:^7.25.2":
-  version: 7.25.2
-  resolution: "@babel/helper-module-transforms@npm:7.25.2"
+"@babel/helper-module-transforms@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-module-transforms@npm:7.25.7"
   dependencies:
-    "@babel/helper-module-imports": "npm:^7.24.7"
-    "@babel/helper-simple-access": "npm:^7.24.7"
-    "@babel/helper-validator-identifier": "npm:^7.24.7"
-    "@babel/traverse": "npm:^7.25.2"
+    "@babel/helper-module-imports": "npm:^7.25.7"
+    "@babel/helper-simple-access": "npm:^7.25.7"
+    "@babel/helper-validator-identifier": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/adaa15970ace0aee5934b5a633789b5795b6229c6a9cf3e09a7e80aa33e478675eee807006a862aa9aa517935d81f88a6db8a9f5936e3a2a40ec75f8062bc329
+  checksum: 10c0/f37fa7d1d4df21690535b278468cbd5faf0133a3080f282000cfa4f3ffc9462a1458f866b04b6a2f2d1eec4691236cba9a867da61270dab3ab19846e62f05090
   languageName: node
   linkType: hard
 
-"@babel/helper-optimise-call-expression@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/helper-optimise-call-expression@npm:7.24.7"
+"@babel/helper-optimise-call-expression@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-optimise-call-expression@npm:7.25.7"
   dependencies:
-    "@babel/types": "npm:^7.24.7"
-  checksum: 10c0/ca6a9884705dea5c95a8b3ce132d1e3f2ae951ff74987d400d1d9c215dae9c0f9e29924d8f8e131e116533d182675bc261927be72f6a9a2968eaeeaa51eb1d0f
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/19b4cc7e77811b1fedca4928dbc14026afef913c2ba4142e5e110ebdcb5c3b2efc0f0fbee9f362c23a194674147b9d627adea71c289b9be08b9067bc0085308b
   languageName: node
   linkType: hard
 
-"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.24.7, @babel/helper-plugin-utils@npm:^7.24.8, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3":
-  version: 7.24.8
-  resolution: "@babel/helper-plugin-utils@npm:7.24.8"
-  checksum: 10c0/0376037f94a3bfe6b820a39f81220ac04f243eaee7193774b983e956c1750883ff236b30785795abbcda43fac3ece74750566830c2daa4d6e3870bb0dff34c2d
+"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.25.7, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3":
+  version: 7.25.7
+  resolution: "@babel/helper-plugin-utils@npm:7.25.7"
+  checksum: 10c0/241f8cf3c5b7700e91cab7cfe5b432a3c710ae3cd5bb96dc554da536a6d25f5b9f000cc0c0917501ceb4f76ba92599ee3beb25e10adaf96be59f8df89a842faf
   languageName: node
   linkType: hard
 
-"@babel/helper-remap-async-to-generator@npm:^7.24.7, @babel/helper-remap-async-to-generator@npm:^7.25.0":
-  version: 7.25.0
-  resolution: "@babel/helper-remap-async-to-generator@npm:7.25.0"
+"@babel/helper-remap-async-to-generator@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-remap-async-to-generator@npm:7.25.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
-    "@babel/helper-wrap-function": "npm:^7.25.0"
-    "@babel/traverse": "npm:^7.25.0"
+    "@babel/helper-annotate-as-pure": "npm:^7.25.7"
+    "@babel/helper-wrap-function": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/0d17b5f7bb6a607edc9cc62fff8056dd9f341bf2f919884f97b99170d143022a5e7ae57922c4891e4fc360ad291e708d2f8cd8989f1d3cd7a17600159984f5a6
+  checksum: 10c0/972d84876adce6ab61c87a2df47e1afc790b73cff0d1767d0a1c5d9f7aa5e91d8c581a272b66b2051a26cfbb167d8a780564705e488e3ce1f477f1c15059bc5f
   languageName: node
   linkType: hard
 
-"@babel/helper-replace-supers@npm:^7.24.7, @babel/helper-replace-supers@npm:^7.25.0":
-  version: 7.25.0
-  resolution: "@babel/helper-replace-supers@npm:7.25.0"
+"@babel/helper-replace-supers@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-replace-supers@npm:7.25.7"
   dependencies:
-    "@babel/helper-member-expression-to-functions": "npm:^7.24.8"
-    "@babel/helper-optimise-call-expression": "npm:^7.24.7"
-    "@babel/traverse": "npm:^7.25.0"
+    "@babel/helper-member-expression-to-functions": "npm:^7.25.7"
+    "@babel/helper-optimise-call-expression": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/b4b6650ab3d56c39a259367cd97f8df2f21c9cebb3716fea7bca40a150f8847bfb82f481e98927c7c6579b48a977b5a8f77318a1c6aeb497f41ecd6dbc3fdfef
+  checksum: 10c0/761d64ee74429f7326a6aa65e2cd5bfcb8de9e3bc3f1efb14b8f610d2410f003b0fca52778dc801d49ff8fbc90b057e8f51b27c62b0b05c95eaf23140ca1287b
   languageName: node
   linkType: hard
 
-"@babel/helper-simple-access@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/helper-simple-access@npm:7.24.7"
+"@babel/helper-simple-access@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-simple-access@npm:7.25.7"
   dependencies:
-    "@babel/traverse": "npm:^7.24.7"
-    "@babel/types": "npm:^7.24.7"
-  checksum: 10c0/7230e419d59a85f93153415100a5faff23c133d7442c19e0cd070da1784d13cd29096ee6c5a5761065c44e8164f9f80e3a518c41a0256df39e38f7ad6744fed7
+    "@babel/traverse": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/eed1b499bfb4f613c18debd61517e3de77b6da2727ca025aa05ac81599e0269f1dddb5237db04e8bb598115d015874752e0a7f11ff38672d74a4976097417059
   languageName: node
   linkType: hard
 
-"@babel/helper-skip-transparent-expression-wrappers@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.24.7"
+"@babel/helper-skip-transparent-expression-wrappers@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.25.7"
   dependencies:
-    "@babel/traverse": "npm:^7.24.7"
-    "@babel/types": "npm:^7.24.7"
-  checksum: 10c0/e3a9b8ac9c262ac976a1bcb5fe59694db5e6f0b4f9e7bdba5c7693b8b5e28113c23bdaa60fe8d3ec32a337091b67720b2053bcb3d5655f5406536c3d0584242b
+    "@babel/traverse": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/5804adb893849a9d8cfb548e3812566a81d95cb0c9a10d66b52912d13f488e577c33063bf19bc06ac70e6333162a7370d67ba1a1c3544d37fb50d5f4a00db4de
   languageName: node
   linkType: hard
 
-"@babel/helper-string-parser@npm:^7.24.8":
-  version: 7.24.8
-  resolution: "@babel/helper-string-parser@npm:7.24.8"
-  checksum: 10c0/6361f72076c17fabf305e252bf6d580106429014b3ab3c1f5c4eb3e6d465536ea6b670cc0e9a637a77a9ad40454d3e41361a2909e70e305116a23d68ce094c08
+"@babel/helper-string-parser@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-string-parser@npm:7.25.7"
+  checksum: 10c0/73ef2ceb81f8294678a0afe8ab0103729c0370cac2e830e0d5128b03be5f6a2635838af31d391d763e3c5a4460ed96f42fd7c9b552130670d525be665913bc4c
   languageName: node
   linkType: hard
 
-"@babel/helper-validator-identifier@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/helper-validator-identifier@npm:7.24.7"
-  checksum: 10c0/87ad608694c9477814093ed5b5c080c2e06d44cb1924ae8320474a74415241223cc2a725eea2640dd783ff1e3390e5f95eede978bc540e870053152e58f1d651
+"@babel/helper-validator-identifier@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-validator-identifier@npm:7.25.7"
+  checksum: 10c0/07438e5bf01ab2882a15027fdf39ac3b0ba1b251774a5130917907014684e2f70fef8fd620137ca062c4c4eedc388508d2ea7a3a7d9936a32785f4fe116c68c0
   languageName: node
   linkType: hard
 
-"@babel/helper-validator-option@npm:^7.24.7, @babel/helper-validator-option@npm:^7.24.8":
-  version: 7.24.8
-  resolution: "@babel/helper-validator-option@npm:7.24.8"
-  checksum: 10c0/73db93a34ae89201351288bee7623eed81a54000779462a986105b54ffe82069e764afd15171a428b82e7c7a9b5fec10b5d5603b216317a414062edf5c67a21f
+"@babel/helper-validator-option@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-validator-option@npm:7.25.7"
+  checksum: 10c0/12ed418c8e3ed9ed44c8c80d823f4e42d399b5eb2e423adccb975e31a31a008cd3b5d8eab688b31f740caff4a1bb28fe06ea2fa7d635aee34cc0ad6995d50f0a
   languageName: node
   linkType: hard
 
-"@babel/helper-wrap-function@npm:^7.25.0":
-  version: 7.25.0
-  resolution: "@babel/helper-wrap-function@npm:7.25.0"
+"@babel/helper-wrap-function@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helper-wrap-function@npm:7.25.7"
   dependencies:
-    "@babel/template": "npm:^7.25.0"
-    "@babel/traverse": "npm:^7.25.0"
-    "@babel/types": "npm:^7.25.0"
-  checksum: 10c0/d54601a98384c191cbc1ff07b03a19e288ef8d5c6bfafe270b2a303d96e7304eb296002921ed464cc1b105a547d1db146eb86b0be617924dee1ba1b379cdc216
+    "@babel/template": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/b5d412f72697f4a4ce4cb9784fbaf82501c63cf95066c0eadd3179e3439cbbf0aa5fa4858d93590083671943cd357aeb87286958df34aa56fdf8a4c9dea39755
   languageName: node
   linkType: hard
 
-"@babel/helpers@npm:^7.25.0":
-  version: 7.25.0
-  resolution: "@babel/helpers@npm:7.25.0"
+"@babel/helpers@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/helpers@npm:7.25.7"
   dependencies:
-    "@babel/template": "npm:^7.25.0"
-    "@babel/types": "npm:^7.25.0"
-  checksum: 10c0/b7fe007fc4194268abf70aa3810365085e290e6528dcb9fbbf7a765d43c74b6369ce0f99c5ccd2d44c413853099daa449c9a0123f0b212ac8d18643f2e8174b8
+    "@babel/template": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/3b3ae9e373bd785414195ef8f59976a69d5a6ebe0ef2165fdcc5165e5c3ee09e0fcee94bb457df2ddb8c0532e4146d0a9b7a96b3497399a4bff4ffe196b30228
   languageName: node
   linkType: hard
 
-"@babel/highlight@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/highlight@npm:7.24.7"
+"@babel/highlight@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/highlight@npm:7.25.7"
   dependencies:
-    "@babel/helper-validator-identifier": "npm:^7.24.7"
+    "@babel/helper-validator-identifier": "npm:^7.25.7"
     chalk: "npm:^2.4.2"
     js-tokens: "npm:^4.0.0"
     picocolors: "npm:^1.0.0"
-  checksum: 10c0/674334c571d2bb9d1c89bdd87566383f59231e16bcdcf5bb7835babdf03c9ae585ca0887a7b25bdf78f303984af028df52831c7989fecebb5101cc132da9393a
+  checksum: 10c0/1f5894fdb0a0af6101fb2822369b2eeeae32cbeae2ef73ff73fc6a0a4a20471565cd9cfa589f54ed69df66adeca7c57266031ca9134b7bd244d023a488d419aa
   languageName: node
   linkType: hard
 
-"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.0, @babel/parser@npm:^7.25.4":
-  version: 7.25.4
-  resolution: "@babel/parser@npm:7.25.4"
+"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/parser@npm:7.25.7"
   dependencies:
-    "@babel/types": "npm:^7.25.4"
+    "@babel/types": "npm:^7.25.7"
   bin:
     parser: ./bin/babel-parser.js
-  checksum: 10c0/bdada5662f15d1df11a7266ec3bc9bb769bf3637ecf3d051eafcfc8f576dcf5a3ac1007c5e059db4a1e1387db9ae9caad239fc4f79e4c2200930ed610e779993
+  checksum: 10c0/b771469bb6b636c18a8d642b9df3c73913c3860a979591e1a29a98659efd38b81d3e393047b5251fe382d4c82c681c12da9ce91c98d69316d2604d155a214bcf
   languageName: node
   linkType: hard
 
-"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.3":
-  version: 7.25.3
-  resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.3"
+"@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-bugfix-firefox-class-in-computed-class-key@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
-    "@babel/traverse": "npm:^7.25.3"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/814b4d3f102e7556a5053d1acf57ef601cfcff39a2c81b8cdc6a5c842e3cb9838f5925d1466a5f1e6416e74c9c83586a3c07fbd7fb8610a396c2becdf9ae5790
+  checksum: 10c0/c6ba97c39973897a2ab021c4a77221e1e93e853a5811d498db325da1bd692e41fa521db6d91bb709ccafd4e54ddd00869ffb35846923c3ccd49d46124b316904
   languageName: node
   linkType: hard
 
-"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.0":
-  version: 7.25.0
-  resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.0"
+"@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-bugfix-safari-class-field-initializer-scope@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/9645a1f47b3750acadb1353c02e71cc712d072aafe5ce115ed3a886bc14c5d9200cfb0b5b5e60e813baa549b800cf798f8714019fd246c699053cf68c428e426
+  checksum: 10c0/ac284868bf410f952c6959b0d77708464127160416f003b05c8127d30e64792d671abc167ebf778b17707e32174223ea8d3ff487276991fa90297d92f0dac6e2
   languageName: node
   linkType: hard
 
-"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.0":
-  version: 7.25.0
-  resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.0"
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/ed1ce1c90cac46c01825339fd0f2a96fa071b016fb819d8dfaf8e96300eae30e74870cb47e4dc80d4ce2fb287869f102878b4f3b35bc927fec8b1d0d76bcf612
+  checksum: 10c0/1bffc0a20c8c82b4c77515eb4c99b961b38184116f008bb42bed4e12d3379ba7b2bc6cf299bcea8118d645bb7a5e0caa83969842f16dd1fce49fb3a050e4ac65
   languageName: node
   linkType: hard
 
-"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.24.7"
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7"
-    "@babel/plugin-transform-optional-chaining": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7"
+    "@babel/plugin-transform-optional-chaining": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.13.0
-  checksum: 10c0/aeb6e7aa363a47f815cf956ea1053c5dd8b786a17799f065c9688ba4b0051fe7565d258bbe9400bfcbfb3114cb9fda66983e10afe4d750bc70ff75403e15dd36
+  checksum: 10c0/32223f012614a0b2657579317ded7d0d09af2aa316285715c5012f974d0f15c2ce2fe0d8e80fdd9bac6c10c21c93cc925a9dfd6c8e21ce7ba1a9fe06a58088b4
   languageName: node
   linkType: hard
 
-"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.0":
-  version: 7.25.0
-  resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.0"
+"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
-    "@babel/traverse": "npm:^7.25.0"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/45988025537a9d4a27b610fd696a18fd9ba9336621a69b4fb40560eeb10c79657f85c92a37f30c7c8fb29c22970eea0b373315795a891f1a05549a6cfe5a6bfe
+  checksum: 10c0/aa2ee7a5954d187de6cbcca0e0b64cfb79c4d224c332d1eb1e0e4afd92ef1a1f4bc4af24f66154097ccb348c08121a875456f47baed220b1b9e93584e6a19b65
   languageName: node
   linkType: hard
 
@@ -476,25 +476,25 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-import-assertions@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-syntax-import-assertions@npm:7.24.7"
+"@babel/plugin-syntax-import-assertions@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-syntax-import-assertions@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/b82c53e095274ee71c248551352d73441cf65b3b3fc0107258ba4e9aef7090772a425442b3ed1c396fa207d0efafde8929c87a17d3c885b3ca2021316e87e246
+  checksum: 10c0/0fee0d971f3c654749fdf92e09b6556bba26ab014c8e99b7252f6a7f1ca108f17edd7ceefb5401d7b7008e98ab1b6f8c3c6a5db72862e7c7b2fcd649d000d690
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-import-attributes@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-syntax-import-attributes@npm:7.24.7"
+"@babel/plugin-syntax-import-attributes@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-syntax-import-attributes@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/eccc54d0f03c96d0eec7a6e2fa124dadbc7298345b62ffc4238f173308c4325b5598f139695ff05a95cf78412ef6903599e4b814496612bf39aad4715a16375b
+  checksum: 10c0/fe00cdb96fd289ab126830a98e1dcf5ab7b529a6ef1c01a72506b5e7b1197d6e46c3c4d029cd90d1d61eb9a15ef77c282d156d0c02c7e32f168bb09d84150db4
   languageName: node
   linkType: hard
 
@@ -520,14 +520,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-jsx@npm:7, @babel/plugin-syntax-jsx@npm:^7.24.7, @babel/plugin-syntax-jsx@npm:^7.7.2":
-  version: 7.24.7
-  resolution: "@babel/plugin-syntax-jsx@npm:7.24.7"
+"@babel/plugin-syntax-jsx@npm:7, @babel/plugin-syntax-jsx@npm:^7.25.7, @babel/plugin-syntax-jsx@npm:^7.7.2":
+  version: 7.25.7
+  resolution: "@babel/plugin-syntax-jsx@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/f44d927a9ae8d5ef016ff5b450e1671e56629ddc12e56b938e41fd46e141170d9dfc9a53d6cb2b9a20a7dd266a938885e6a3981c60c052a2e1daed602ac80e51
+  checksum: 10c0/17db499c31fcfaa94d5408726d943955d51d478353d1e2dd84eda6024f7e3d104b9456a77f8aabfae0db7f4dc32f810d08357112f7fcbe305e7c9fcf5b3cac13
   languageName: node
   linkType: hard
 
@@ -619,14 +619,14 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-syntax-typescript@npm:^7.24.7, @babel/plugin-syntax-typescript@npm:^7.7.2":
-  version: 7.24.7
-  resolution: "@babel/plugin-syntax-typescript@npm:7.24.7"
+"@babel/plugin-syntax-typescript@npm:^7.25.7, @babel/plugin-syntax-typescript@npm:^7.7.2":
+  version: 7.25.7
+  resolution: "@babel/plugin-syntax-typescript@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/cdabd2e8010fb0ad15b49c2c270efc97c4bfe109ead36c7bbcf22da7a74bc3e49702fc4f22f12d2d6049e8e22a5769258df1fd05f0420ae45e11bdd5bc07805a
+  checksum: 10c0/ed51fd81a5cf571a89fc4cf4c0e3b0b91285c367237374c133d2e5e718f3963cfa61b81997df39220a8837dc99f9e9a8ab7701d259c09fae379e4843d9db60c2
   languageName: node
   linkType: hard
 
@@ -642,466 +642,466 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-arrow-functions@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-arrow-functions@npm:7.24.7"
+"@babel/plugin-transform-arrow-functions@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-arrow-functions@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/6ac05a54e5582f34ac6d5dc26499e227227ec1c7fa6fc8de1f3d40c275f140d3907f79bbbd49304da2d7008a5ecafb219d0b71d78ee3290ca22020d878041245
+  checksum: 10c0/c8d75ead93f130bf113b6d29493aca695092661ef039336d2a227169c3b7895aa5e9bcc548c42a95a6eaaaf49e512317b00699940bd40ccefd77443e703d3935
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-async-generator-functions@npm:^7.25.4":
-  version: 7.25.4
-  resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.4"
+"@babel/plugin-transform-async-generator-functions@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-async-generator-functions@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
-    "@babel/helper-remap-async-to-generator": "npm:^7.25.0"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-remap-async-to-generator": "npm:^7.25.7"
     "@babel/plugin-syntax-async-generators": "npm:^7.8.4"
-    "@babel/traverse": "npm:^7.25.4"
+    "@babel/traverse": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/efed6f6be90b25ad77c15a622a0dc0b22dbf5d45599c207ab8fbc4e959aef21f574fa467d9cf872e45de664a46c32334e78dee2332d82f5f27e26249a34a0920
+  checksum: 10c0/dcdd17d8cafafe0eb2edd0a46a7abe86c72235c957c8eb1157ccadb2b199572d5d1aa36a2d3bce5cb99990f7d3c6290ecf09959c62f3081c4df9ff717a1c84a4
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-async-to-generator@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-async-to-generator@npm:7.24.7"
+"@babel/plugin-transform-async-to-generator@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-async-to-generator@npm:7.25.7"
   dependencies:
-    "@babel/helper-module-imports": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
-    "@babel/helper-remap-async-to-generator": "npm:^7.24.7"
+    "@babel/helper-module-imports": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-remap-async-to-generator": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/83c82e243898875af8457972a26ab29baf8a2078768ee9f35141eb3edff0f84b165582a2ff73e90a9e08f5922bf813dbf15a85c1213654385198f4591c0dc45d
+  checksum: 10c0/1dbefba9c1455f7a92b8c59a93c622091db945294c936fc2c09b1648308c5b4cb2ecaae92baae0d07a324ab890a8a2ee27ceb046bc120932845d27aede275821
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-block-scoped-functions@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.24.7"
+"@babel/plugin-transform-block-scoped-functions@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/113e86de4612ae91773ff5cb6b980f01e1da7e26ae6f6012127415d7ae144e74987bc23feb97f63ba4bc699331490ddea36eac004d76a20d5369e4cc6a7f61cd
+  checksum: 10c0/b1e77492295d1b271ef850a81b0404cf3d0dd6a2bcbeab28a0fd99e61c6de4bda91dff583bb42138eec61bf71282bdd3b1bebcb53b7e373035e77fd6ba66caeb
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-block-scoping@npm:^7.25.0":
-  version: 7.25.0
-  resolution: "@babel/plugin-transform-block-scoping@npm:7.25.0"
+"@babel/plugin-transform-block-scoping@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-block-scoping@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/382931c75a5d0ea560387e76cb57b03461300527e4784efcb2fb62f36c1eb0ab331327b6034def256baa0cad9050925a61f9c0d56261b6afd6a29c3065fb0bd4
+  checksum: 10c0/b2057e00535cd0e8bd5ee5d4640aa2e952564aeafb1bcf4e7b6de33442422877bb0ca8669ad0a48262ec077271978c61eae87b6b3bc8f472d830fa781d6f7e44
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-class-properties@npm:^7.25.4":
-  version: 7.25.4
-  resolution: "@babel/plugin-transform-class-properties@npm:7.25.4"
+"@babel/plugin-transform-class-properties@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-class-properties@npm:7.25.7"
   dependencies:
-    "@babel/helper-create-class-features-plugin": "npm:^7.25.4"
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-create-class-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/0b41bc8a5920d3d17c7c06220b601cf43e0a32ac34f05f05cd0cdf08915e4521b1b707cb1e60942b4fc68a5dfac09f0444a8720e0c72ce76fb039e8ec5263115
+  checksum: 10c0/1f41e6934b20ad3e05df63959cff9bc600ff3119153b9acbbd44c1731e7df04866397e6e17799173f4c53cdee6115e155632859aee20bf47ec7dcef3f2168a47
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-class-static-block@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-class-static-block@npm:7.24.7"
+"@babel/plugin-transform-class-static-block@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-class-static-block@npm:7.25.7"
   dependencies:
-    "@babel/helper-create-class-features-plugin": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-create-class-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     "@babel/plugin-syntax-class-static-block": "npm:^7.14.5"
   peerDependencies:
     "@babel/core": ^7.12.0
-  checksum: 10c0/b0ade39a3d09dce886f79dbd5907c3d99b48167eddb6b9bbde24a0598129654d7017e611c20494cdbea48b07ac14397cd97ea34e3754bbb2abae4e698128eccb
+  checksum: 10c0/cbb4b46cbd8ad10106eb2bedb5a0665661a1d1d5b6f3ab565ff454b802dab4718e02b25670fe0d40835494aedb3dc26757c06cc4da6ff3e80291c5f882269bd3
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-classes@npm:^7.25.4":
-  version: 7.25.4
-  resolution: "@babel/plugin-transform-classes@npm:7.25.4"
+"@babel/plugin-transform-classes@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-classes@npm:7.25.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
-    "@babel/helper-compilation-targets": "npm:^7.25.2"
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
-    "@babel/helper-replace-supers": "npm:^7.25.0"
-    "@babel/traverse": "npm:^7.25.4"
+    "@babel/helper-annotate-as-pure": "npm:^7.25.7"
+    "@babel/helper-compilation-targets": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-replace-supers": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
     globals: "npm:^11.1.0"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/c68424d9dd64860825111aa4a4ed5caf29494b7a02ddb9c36351d768c41e8e05127d89274795cdfcade032d9d299e6c677418259df58c71e68f1741583dcf467
+  checksum: 10c0/8121781e1d8acd80e6169019106f73a399475ad9c895c1988a344dfed5a6ddd340938ac55123dc1e423bb8f25f255f5d11031116ad756ba3c314595a97c973af
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-computed-properties@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-computed-properties@npm:7.24.7"
+"@babel/plugin-transform-computed-properties@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-computed-properties@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
-    "@babel/template": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/template": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/25636dbc1f605c0b8bc60aa58628a916b689473d11551c9864a855142e36742fe62d4a70400ba3b74902338e77fb3d940376c0a0ba154b6b7ec5367175233b49
+  checksum: 10c0/7ad0a1c126f50935a02e77d438ebc39078a9d644b3a60de60bec32c5d9f49e7f2b193fcecb8c61bb1bc3cdd4af1e93f72d022d448511fa76a171527c633cd1bf
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-destructuring@npm:^7.24.8":
-  version: 7.24.8
-  resolution: "@babel/plugin-transform-destructuring@npm:7.24.8"
+"@babel/plugin-transform-destructuring@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-destructuring@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/804968c1d5f5072c717505296c1e5d5ec33e90550423de66de82bbcb78157156e8470bbe77a04ab8c710a88a06360a30103cf223ac7eff4829adedd6150de5ce
+  checksum: 10c0/a563123b2fb267e03aa50104005f00b56226a685938906c42c1b251462e0cc9fc89e587d5656d3324159071eb8ebda8c68a6011f11d5a00fb1436cb5a5411b7b
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-dotall-regex@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-dotall-regex@npm:7.24.7"
+"@babel/plugin-transform-dotall-regex@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-dotall-regex@npm:7.25.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/793f14c9494972d294b7e7b97b747f47874b6d57d7804d3443c701becf5db192c9311be6a1835c07664486df1f5c60d33196c36fb7e11a53015e476b4c145b33
+  checksum: 10c0/7f1db3ec20b7fae46db4a9c4c257d75418b0896b72c0a3de20b3044f952801480f0a2e75ebb0d64f13e8cd4db0e49aa42c5c0edff372b23c41679b1ea5dd3ed4
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-duplicate-keys@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-duplicate-keys@npm:7.24.7"
+"@babel/plugin-transform-duplicate-keys@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-duplicate-keys@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/75ff7ec1117ac500e77bf20a144411d39c0fdd038f108eec061724123ce6d1bb8d5bd27968e466573ee70014f8be0043361cdb0ef388f8a182d1d97ad67e51b9
+  checksum: 10c0/b4079981e2db19737a0f1a00254e7388e2d3c01ce36e9fd826e4d86d3c1755339495e29c71fd7c84a068201ec24687328d48f3bf53b32b6d6224f51d9a34da74
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.0":
-  version: 7.25.0
-  resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.0"
+"@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-duplicate-named-capturing-groups-regex@npm:7.25.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.0"
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/1c9b57ddd9b33696e88911d0e7975e1573ebc46219c4b30eb1dc746cbb71aedfac6f6dab7fdfdec54dd58f31468bf6ab56b157661ea4ffe58f906d71f89544c8
+  checksum: 10c0/e4946090ff6d88d54b78265ee653079ec34c117ac046e22f66f7c4ac44249cdc2dfca385bc5bf4386db668b9948eeb12985589500188bc252e684c7714c31475
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-dynamic-import@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-dynamic-import@npm:7.24.7"
+"@babel/plugin-transform-dynamic-import@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-dynamic-import@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/eeda48372efd0a5103cb22dadb13563c975bce18ae85daafbb47d57bb9665d187da9d4fe8d07ac0a6e1288afcfcb73e4e5618bf75ff63fddf9736bfbf225203b
+  checksum: 10c0/c733252ff20a32d9747dd081916270f5a073856597e849a5f458b12f4354499b18714f5e7049e341432851d9975077cb37effcd276c7f816faa6f5ff708dc5e1
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-exponentiation-operator@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.24.7"
+"@babel/plugin-transform-exponentiation-operator@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.25.7"
   dependencies:
-    "@babel/helper-builder-binary-assignment-operator-visitor": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-builder-binary-assignment-operator-visitor": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/ace3e11c94041b88848552ba8feb39ae4d6cad3696d439ff51445bd2882d8b8775d85a26c2c0edb9b5e38c9e6013cc11b0dea89ec8f93c7d9d7ee95e3645078c
+  checksum: 10c0/c8537b9f3cddc5a8d3710f6980196dc7a0f4389f8f82617312a5f7b8b15bcd8ddaeba783c687c3ac6031eb0a4ba0bc380a98da6bf7efe98e225602a98ad42a1e
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-export-namespace-from@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-export-namespace-from@npm:7.24.7"
+"@babel/plugin-transform-export-namespace-from@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-export-namespace-from@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/4e144d7f1c57bc63b4899dbbbdfed0880f2daa75ea9c7251c7997f106e4b390dc362175ab7830f11358cb21f6b972ca10a43a2e56cd789065f7606b082674c0c
+  checksum: 10c0/ef61fc5d54c9c8b075cbd9db62beaf295e38e08a1edb1882995105d3e959763be1631f7d7f7cb7461b702ebd0b4a601f2eb2cd6521acaf061310a3a3305fa756
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-for-of@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-for-of@npm:7.24.7"
+"@babel/plugin-transform-for-of@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-for-of@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/77629b1173e55d07416f05ba7353caa09d2c2149da2ca26721ab812209b63689d1be45116b68eadc011c49ced59daf5320835b15245eb7ae93ae0c5e8277cfc0
+  checksum: 10c0/08a37a1742368a422d095c998ed76f60f6bf3f9cc060033be121d803fd2dddc08fe543e48ee49c022bdc9ed80893ca79d084958d83d30684178b088774754277
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-function-name@npm:^7.25.1":
-  version: 7.25.1
-  resolution: "@babel/plugin-transform-function-name@npm:7.25.1"
+"@babel/plugin-transform-function-name@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-function-name@npm:7.25.7"
   dependencies:
-    "@babel/helper-compilation-targets": "npm:^7.24.8"
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
-    "@babel/traverse": "npm:^7.25.1"
+    "@babel/helper-compilation-targets": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/e74912174d5e33d1418b840443c2e226a7b76cc017c1ed20ee30a566e4f1794d4a123be03180da046241576e8b692731807ba1f52608922acf1cb2cb6957593f
+  checksum: 10c0/ca98e1116c0ada7211ed43e4b7f21ca15f95bbbdad70f2fbe1ec2d90a97daedf9f22fcb0a25c8b164a5e394f509f2e4d1f7609d26dc938a58d37c5ee9b80088a
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-json-strings@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-json-strings@npm:7.24.7"
+"@babel/plugin-transform-json-strings@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-json-strings@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     "@babel/plugin-syntax-json-strings": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/17c72cd5bf3e90e722aabd333559275f3309e3fa0b9cea8c2944ab83ae01502c71a2be05da5101edc02b3fc8df15a8dbb9b861cbfcc8a52bf5e797cf01d3a40a
+  checksum: 10c0/aa6e5f65c8a5f2459d7daa9b5b4ff97ff43bab21f4a8513ed84d35300b0323ec542dc101c5f11622e442dfc93b3a229c7f41ebc7645370dfec6d066bda800a0b
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-literals@npm:^7.25.2":
-  version: 7.25.2
-  resolution: "@babel/plugin-transform-literals@npm:7.25.2"
+"@babel/plugin-transform-literals@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-literals@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/0796883217b0885d37e7f6d350773be349e469a812b6bf11ccf862a6edf65103d3e7c849529d65381b441685c12e756751d8c2489a0fd3f8139bb5ef93185f58
+  checksum: 10c0/c2c2488102f33e566f45becdcb632e53bd052ecfb2879deb07a614b3e9437e3b624c3b16d080096d50b0b622edebd03e438acbf9260bcc41167897963f64560e
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-logical-assignment-operators@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.24.7"
+"@babel/plugin-transform-logical-assignment-operators@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-logical-assignment-operators@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/dbe882eb9053931f2ab332c50fc7c2a10ef507d6421bd9831adbb4cb7c9f8e1e5fbac4fbd2e007f6a1bf1df1843547559434012f118084dc0bf42cda3b106272
+  checksum: 10c0/d610a8a2c1be83e03cce2256f29519e705dc68289c09d67f1f362d1fd80f4b36eaf2affc05710abb53a272895041e24d9e95ec73a516a23a67cb907023fbe37b
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-member-expression-literals@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-member-expression-literals@npm:7.24.7"
+"@babel/plugin-transform-member-expression-literals@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-member-expression-literals@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/e789ae359bdf2d20e90bedef18dfdbd965c9ebae1cee398474a0c349590fda7c8b874e1a2ceee62e47e5e6ec1730e76b0f24e502164357571854271fc12cc684
+  checksum: 10c0/d6936b98ae4d3daed850dc4e064042ea4375f815219ba9d8591373bf1fba4cfdb5be42623ae8882f2d666cc34af650a4855e2a5ad89e3c235d73a6f172f9969c
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-amd@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-modules-amd@npm:7.24.7"
+"@babel/plugin-transform-modules-amd@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-modules-amd@npm:7.25.7"
   dependencies:
-    "@babel/helper-module-transforms": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-module-transforms": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/6df7de7fce34117ca4b2fa07949b12274c03668cbfe21481c4037b6300796d50ae40f4f170527b61b70a67f26db906747797e30dbd0d9809a441b6e220b5728f
+  checksum: 10c0/c0bc999206c3834c090e6559a6c8a55d7672d3573104e832223ebe7df99bd1b82fc850e15ba32f512c84b0db1cdb613b66fa60abe9abb9c7e8dcbff91649b356
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-commonjs@npm:^7.24.7, @babel/plugin-transform-modules-commonjs@npm:^7.24.8":
-  version: 7.24.8
-  resolution: "@babel/plugin-transform-modules-commonjs@npm:7.24.8"
+"@babel/plugin-transform-modules-commonjs@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-modules-commonjs@npm:7.25.7"
   dependencies:
-    "@babel/helper-module-transforms": "npm:^7.24.8"
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
-    "@babel/helper-simple-access": "npm:^7.24.7"
+    "@babel/helper-module-transforms": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-simple-access": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/f1cf552307ebfced20d3907c1dd8be941b277f0364aa655e2b5fee828c84c54065745183104dae86f1f93ea0406db970a463ef7ceaaed897623748e99640e5a7
+  checksum: 10c0/2f1c945fc3c9b690b0ddcf2c80156b2e4fbf2cf15aac43ac8fe6e4b34125869528839a53d07c564e62e4aed394ebdc1d2c3b796b547374455522581c11b7599c
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-systemjs@npm:^7.25.0":
-  version: 7.25.0
-  resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.0"
+"@babel/plugin-transform-modules-systemjs@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-modules-systemjs@npm:7.25.7"
   dependencies:
-    "@babel/helper-module-transforms": "npm:^7.25.0"
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
-    "@babel/helper-validator-identifier": "npm:^7.24.7"
-    "@babel/traverse": "npm:^7.25.0"
+    "@babel/helper-module-transforms": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-validator-identifier": "npm:^7.25.7"
+    "@babel/traverse": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/fca6198da71237e4bb1274b3b67a0c81d56013c9535361242b6bfa87d70a9597854aadb45d4d8203369be4a655e158be2a5d20af0040b1f8d1bfc47db3ad7b68
+  checksum: 10c0/95eaea7082636710c61e49e58b3907e85ec79db4327411d3784f28592509fbe94a53cc3d20a36a1cf245efc6d3f0017eae15b45ffd645c1ab949bb4e1670e6bb
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-modules-umd@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-modules-umd@npm:7.24.7"
+"@babel/plugin-transform-modules-umd@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-modules-umd@npm:7.25.7"
   dependencies:
-    "@babel/helper-module-transforms": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-module-transforms": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/7791d290121db210e4338b94b4a069a1a79e4c7a8d7638d8159a97b281851bbed3048dac87a4ae718ad963005e6c14a5d28e6db2eeb2b04e031cee92fb312f85
+  checksum: 10c0/8849ab04eecdb73cd37e2d7289449fa5256331832b0304c220b2a6aaa12e2d2dd87684f2813412d1fc5bdb3d6b55cc08c6386d3273fe05a65177c09bee5b6769
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.24.7"
+"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.25.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/41a0b0f2d0886318237440aa3b489f6d0305361d8671121777d9ff89f9f6de9d0c02ce93625049061426c8994064ef64deae8b819d1b14c00374a6a2336fb5d9
+  checksum: 10c0/eb55fec55dc930cd122911f3e4a421320fa8b1b4de85bfd7ef11b46c611ec69b0213c114a6e1c6bc224d6b954ff183a0caa7251267d5258ecc0f00d6d9ca1d52
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-new-target@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-new-target@npm:7.24.7"
+"@babel/plugin-transform-new-target@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-new-target@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/2540808a35e1a978e537334c43dab439cf24c93e7beb213a2e71902f6710e60e0184316643790c0a6644e7a8021e52f7ab8165e6b3e2d6651be07bdf517b67df
+  checksum: 10c0/8e5dce6d027e0f3fd394578ea1af7f515de157793a15c23a5aad7034a6d8a4005ef280238e67a232bb4dd4fafd3a264fed462deb149128ddd9ce59ff6f575cff
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.3, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.24.7"
+"@babel/plugin-transform-nullish-coalescing-operator@npm:^7.22.3, @babel/plugin-transform-nullish-coalescing-operator@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-nullish-coalescing-operator@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     "@babel/plugin-syntax-nullish-coalescing-operator": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/7243c8ff734ed5ef759dd8768773c4b443c12e792727e759a1aec2c7fa2bfdd24f1ecb42e292a7b3d8bd3d7f7b861cf256a8eb4ba144fc9cc463892c303083d9
+  checksum: 10c0/b35a96a79ef4895b00e4f758d3185cb17e4fbfada311894ad5f0988a55fc2c21820dc789b26a3cb8fbd620434faa516e52acb6e2da105c2edbd29de8b6b0facf
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-numeric-separator@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-numeric-separator@npm:7.24.7"
+"@babel/plugin-transform-numeric-separator@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-numeric-separator@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     "@babel/plugin-syntax-numeric-separator": "npm:^7.10.4"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/e18e09ca5a6342645d00ede477731aa6e8714ff357efc9d7cda5934f1703b3b6fb7d3298dce3ce3ba53e9ff1158eab8f1aadc68874cc21a6099d33a1ca457789
+  checksum: 10c0/c028ae89e6b4e1d757f8f1ebcb3b420e6559bb35002728f6f5651d5f669fbf73764adf6e3597908fa12adf8dbae683e5f74b3a7f68e8774a9663c18c0f999539
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-object-rest-spread@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-object-rest-spread@npm:7.24.7"
+"@babel/plugin-transform-object-rest-spread@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-object-rest-spread@npm:7.25.7"
   dependencies:
-    "@babel/helper-compilation-targets": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-compilation-targets": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     "@babel/plugin-syntax-object-rest-spread": "npm:^7.8.3"
-    "@babel/plugin-transform-parameters": "npm:^7.24.7"
+    "@babel/plugin-transform-parameters": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/9ad64bc003f583030f9da50614b485852f8edac93f8faf5d1cd855201a4852f37c5255ae4daf70dd4375bdd4874e16e39b91f680d4668ec219ba05441ce286eb
+  checksum: 10c0/e8b978d9d1020452da0d5d92f80fe57e302761dac20137bb8bf863478a4779fcd63d314db89e796125d9d76da2a38f64f012d6e0c4913815951b3eb3fba2feb6
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-object-super@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-object-super@npm:7.24.7"
+"@babel/plugin-transform-object-super@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-object-super@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
-    "@babel/helper-replace-supers": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-replace-supers": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/770cebb4b4e1872c216b17069db9a13b87dfee747d359dc56d9fcdd66e7544f92dc6ab1861a4e7e0528196aaff2444e4f17dc84efd8eaf162d542b4ba0943869
+  checksum: 10c0/7f2968d4da997101b63fd3b74445c9b16f56bd32cd8a0a16c368af9d3e983e7675c1b05d18601f32307cb06e7d884ee11d13ff18a1f6830c0db243a9a852afab
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-optional-catch-binding@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.24.7"
+"@babel/plugin-transform-optional-catch-binding@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-optional-catch-binding@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     "@babel/plugin-syntax-optional-catch-binding": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/1e2f10a018f7d03b3bde6c0b70d063df8d5dd5209861d4467726cf834f5e3d354e2276079dc226aa8e6ece35f5c9b264d64b8229a8bb232829c01e561bcfb07a
+  checksum: 10c0/bb609e5103780be0825a255ffe1fefbb5335aead88a46eecc2257053279ea2c45ff66b0ef1fb54302c8c8c57146e88e52f3ecb62b4c6f619218d7b3843b352d9
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-optional-chaining@npm:^7.24.7, @babel/plugin-transform-optional-chaining@npm:^7.24.8":
-  version: 7.24.8
-  resolution: "@babel/plugin-transform-optional-chaining@npm:7.24.8"
+"@babel/plugin-transform-optional-chaining@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-optional-chaining@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7"
     "@babel/plugin-syntax-optional-chaining": "npm:^7.8.3"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/4ffbe1aad7dec7c9aa2bf6ceb4b2f91f96815b2784f2879bde80e46934f59d64a12cb2c6262e40897c4754d77d2c35d8a5cfed63044fdebf94978b1ed3d14b17
+  checksum: 10c0/887441ada6c2bc1b789984b7531d9bc585f335ece99642886d3d9fd8aee7e6b8d4f7ca61d76b5f23477f3aa607284d5056eadaa1eb17e7b39af6b0e834cbe878
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-parameters@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-parameters@npm:7.24.7"
+"@babel/plugin-transform-parameters@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-parameters@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/53bf190d6926771545d5184f1f5f3f5144d0f04f170799ad46a43f683a01fab8d5fe4d2196cf246774530990c31fe1f2b9f0def39f0a5ddbb2340b924f5edf01
+  checksum: 10c0/b40ba70278842ce1e800d7ab400df730994941550da547ef453780023bd61a9b8acf4b9fb8419c1b5bcbe09819a1146ff59369db11db07eb71870bef86a12422
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-private-methods@npm:^7.25.4":
-  version: 7.25.4
-  resolution: "@babel/plugin-transform-private-methods@npm:7.25.4"
+"@babel/plugin-transform-private-methods@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-private-methods@npm:7.25.7"
   dependencies:
-    "@babel/helper-create-class-features-plugin": "npm:^7.25.4"
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-create-class-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/7abdb427c3984a2c8a2e9d806297d8509b02f78a3501b7760e544be532446e9df328b876daa8fc38718f3dce7ccc45083016ee7aeaab169b81c142bc18700794
+  checksum: 10c0/92e076f63f7c4696e1321dafdd56c4212eb41784cdadba0ebc39091f959a76d357c3df61a6c668be81d6b6ad8964ee458e85752ab0c6cfbbaf2066903edda732
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-private-property-in-object@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-private-property-in-object@npm:7.24.7"
+"@babel/plugin-transform-private-property-in-object@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-private-property-in-object@npm:7.25.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
-    "@babel/helper-create-class-features-plugin": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-annotate-as-pure": "npm:^7.25.7"
+    "@babel/helper-create-class-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/c6fa7defb90b1b0ed46f24ff94ff2e77f44c1f478d1090e81712f33cf992dda5ba347016f030082a2f770138bac6f4a9c2c1565e9f767a125901c77dd9c239ba
+  checksum: 10c0/5ad8832ba54e2079c1f558b8680e170265e3f376424e5fbb75b17b7f08696fb0af6c96d23d92f7df3dcc559f5971a02587281fcec38a853174aa95478565f5fc
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-property-literals@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-property-literals@npm:7.24.7"
+"@babel/plugin-transform-property-literals@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-property-literals@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/52564b58f3d111dc02d241d5892a4b01512e98dfdf6ef11b0ed62f8b11b0acacccef0fc229b44114fe8d1a57a8b70780b11bdd18b807d3754a781a07d8f57433
+  checksum: 10c0/6d5bccdc772207906666ad5201bd91e4e132e1d806dbcf4163a1d08e18c57cc3795578c4e10596514bcd6afaf9696f478ea4f0dea890176d93b9cb077b9e5c55
   languageName: node
   linkType: hard
 
@@ -1116,244 +1116,245 @@ __metadata:
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-display-name@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-react-display-name@npm:7.24.7"
+"@babel/plugin-transform-react-display-name@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-react-display-name@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/c14a07a9e75723c96f1a0a306b8a8e899ff1c6a0cc3d62bcda79bb1b54e4319127b258651c513a1a47da152cdc22e16525525a30ae5933a2980c7036fd0b4d24
+  checksum: 10c0/a0c537cc7c328ed7468d3b6a37bf0d9cb15d94afcdf3f2849ce6e5a68494fc61f0fa4fc529482a6b95b00f3c5c734f310bf18085293bff40702789f06c816f36
   languageName: node
   linkType: hard
 
 "@babel/plugin-transform-react-inline-elements@npm:^7.21.0":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-react-inline-elements@npm:7.24.7"
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-react-inline-elements@npm:7.25.7"
   dependencies:
-    "@babel/helper-builder-react-jsx": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-builder-react-jsx": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/affe44efc641e5dc4de077db74c80e3dee896a5dfea04491cbd8167ac40dcbb6eaa1ad0ba599e4a85071acdaa08732e7f5d9cf3236a2aa635406c232c8f08236
+  checksum: 10c0/de85180c09002083cb330ef28d5b31b44e6ac6565c700b603be76e629f5a92c59ff69f79c08cc1dbd2dd1f271f44fe8d4c751deaf66c059ba721aa3cb43c881e
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-jsx-development@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-react-jsx-development@npm:7.24.7"
+"@babel/plugin-transform-react-jsx-development@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-react-jsx-development@npm:7.25.7"
   dependencies:
-    "@babel/plugin-transform-react-jsx": "npm:^7.24.7"
+    "@babel/plugin-transform-react-jsx": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/fce647db50f90a5291681f0f97865d9dc76981262dff71d6d0332e724b85343de5860c26f9e9a79e448d61e1d70916b07ce91e8c7f2b80dceb4b16aee41794d8
+  checksum: 10c0/a3dc14644d09a6d22875af7b5584393ab53e467e0531cd192fc6242504dacaffa421e89265ba7f84fd4edef2b7b100d2e2ebf092a4dce2b55cf9c5fe29390c18
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-jsx@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-react-jsx@npm:7.24.7"
+"@babel/plugin-transform-react-jsx@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-react-jsx@npm:7.25.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
-    "@babel/helper-module-imports": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
-    "@babel/plugin-syntax-jsx": "npm:^7.24.7"
-    "@babel/types": "npm:^7.24.7"
+    "@babel/helper-annotate-as-pure": "npm:^7.25.7"
+    "@babel/helper-module-imports": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/plugin-syntax-jsx": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/5c46d2c1c06a30e6bde084839df9cc689bf9c9cb0292105d61c225ca731f64247990724caee7dfc7f817dc964c062e8319e7f05394209590c476b65d75373435
+  checksum: 10c0/6766b0357b8bbfcb77fca5350f06cf822c89bbe75ddcaea24614601ef23957504da24e76597d743038ce8fa081373b0663c8ad0c86d7c7226e8185f0680b8b56
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-react-pure-annotations@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.24.7"
+"@babel/plugin-transform-react-pure-annotations@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.25.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-annotate-as-pure": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/fae517d293d9c93b7b920458c3e4b91cb0400513889af41ba184a5f3acc8bfef27242cc262741bb8f87870df376f1733a0d0f52b966d342e2aaaf5607af8f73d
+  checksum: 10c0/d92c9b511850fb6dea71966a0d4f313d67e317db7fc3633a7ff2e27d6df2e95cbc91c4c25abdb6c8db651fcda842a0cb7433835a8a9d4a3fdc5d452068428101
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-regenerator@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-regenerator@npm:7.24.7"
+"@babel/plugin-transform-regenerator@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-regenerator@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     regenerator-transform: "npm:^0.15.2"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/d2dc2c788fdae9d97217e70d46ba8ca9db0035c398dc3e161552b0c437113719a75c04f201f9c91ddc8d28a1da60d0b0853f616dead98a396abb9c845c44892b
+  checksum: 10c0/7ee3a57c4050bc908ef7ac392d810826b294970a7182f4ec34a8ca93dbe36deb21bc862616d46a6f3d881d6b5749930e1679e875b638a00866d844a4250df212
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-reserved-words@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-reserved-words@npm:7.24.7"
+"@babel/plugin-transform-reserved-words@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-reserved-words@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/2229de2768615e7f5dc0bbc55bc121b5678fd6d2febd46c74a58e42bb894d74cd5955c805880f4e02d0e1cf94f6886270eda7fafc1be9305a1ec3b9fd1d063f5
+  checksum: 10c0/920c98130daff6c1288fb13a9a2d2e45863bba93e619cb88d90e1f5b5cb358a3ee8880a425a3adb1b4bd5dbb6bd0500eea3370fc612633045eec851b08cc586c
   languageName: node
   linkType: hard
 
 "@babel/plugin-transform-runtime@npm:^7.22.4":
-  version: 7.25.4
-  resolution: "@babel/plugin-transform-runtime@npm:7.25.4"
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-runtime@npm:7.25.7"
   dependencies:
-    "@babel/helper-module-imports": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-module-imports": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
     babel-plugin-polyfill-corejs2: "npm:^0.4.10"
     babel-plugin-polyfill-corejs3: "npm:^0.10.6"
     babel-plugin-polyfill-regenerator: "npm:^0.6.1"
     semver: "npm:^6.3.1"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/c08698276596d58bf49e222ead3c414c35d099a7e5a6174b11e2db9b74420e94783ada596820437622c3eccc8852c0e750ad053bd8e775f0050839479ba76e6a
+  checksum: 10c0/9b2514e9079361ac8e7e500ffd522dad869d61a3894302da7e29bbac80de00276c8a1b4394d1dcf0b51c57b2c854919928df9648be336139fdf1d6ecd6d1bb32
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-shorthand-properties@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-shorthand-properties@npm:7.24.7"
+"@babel/plugin-transform-shorthand-properties@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-shorthand-properties@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/41b155bdbb3be66618358488bf7731b3b2e8fff2de3dbfd541847720a9debfcec14db06a117abedd03c9cd786db20a79e2a86509a4f19513f6e1b610520905cf
+  checksum: 10c0/4250f89a0072f0f400be7a2e3515227b8e2518737899bd57d497e5173284a0e05d812e4a3c219ffcd484e9fa9a01c19fce5acd77bbb898f4d594512c56701eb4
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-spread@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-spread@npm:7.24.7"
+"@babel/plugin-transform-spread@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-spread@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
-    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/facba1553035f76b0d2930d4ada89a8cd0f45b79579afd35baefbfaf12e3b86096995f4b0c402cf9ee23b3f2ea0a4460c3b1ec0c192d340962c948bb223d4e66
+  checksum: 10c0/258bd1b52388cd7425d0ae25fa39538734f7540ea503a1d8a72211d33f6f214cb4e3b73d6cd03016cbcff5d41169f1e578b9ea331965ad224d223591983e90a7
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-sticky-regex@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-sticky-regex@npm:7.24.7"
+"@babel/plugin-transform-sticky-regex@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-sticky-regex@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/5a74ed2ed0a3ab51c3d15fcaf09d9e2fe915823535c7a4d7b019813177d559b69677090e189ec3d5d08b619483eb5ad371fbcfbbff5ace2a76ba33ee566a1109
+  checksum: 10c0/0e466cfc3ca1e0db4bb11eb630215b0e1f43066d7678325e5ddadcf5a118b2351a528f67205729c32ac5b78ab68ab7f40517dd33bcb1fb6b456509f5f54ce097
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-template-literals@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-template-literals@npm:7.24.7"
+"@babel/plugin-transform-template-literals@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-template-literals@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/3630f966257bcace122f04d3157416a09d40768c44c3a800855da81146b009187daa21859d1c3b7d13f4e19e8888e60613964b175b2275d451200fb6d8d6cfe6
+  checksum: 10c0/a3455303b6841cb536ac66d1a2d03c194b9f371519482d8d1e8edbd33bf5ca7cdd5db1586b2b0ea5f909ebf74a0eafacf0fb28d257e4905445282dcdccfa6139
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-typeof-symbol@npm:^7.24.8":
-  version: 7.24.8
-  resolution: "@babel/plugin-transform-typeof-symbol@npm:7.24.8"
+"@babel/plugin-transform-typeof-symbol@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-typeof-symbol@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/2f570a4fbbdc5fd85f48165a97452826560051e3b8efb48c3bb0a0a33ee8485633439e7b71bfe3ef705583a1df43f854f49125bd759abdedc195b2cf7e60012a
+  checksum: 10c0/ce1a0744a900b05de1372a70508c4148f17eb941c482da26eb369b9f0347570dce45470c8a86d907bc3a0443190344da1e18489ecfecb30388ab6178e8a9916b
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-typescript@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-typescript@npm:7.24.7"
+"@babel/plugin-transform-typescript@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-typescript@npm:7.25.7"
   dependencies:
-    "@babel/helper-annotate-as-pure": "npm:^7.24.7"
-    "@babel/helper-create-class-features-plugin": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
-    "@babel/plugin-syntax-typescript": "npm:^7.24.7"
+    "@babel/helper-annotate-as-pure": "npm:^7.25.7"
+    "@babel/helper-create-class-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-skip-transparent-expression-wrappers": "npm:^7.25.7"
+    "@babel/plugin-syntax-typescript": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/e8dacdc153a4c4599014b66eb01b94e3dc933d58d4f0cc3039c1a8f432e77b9df14f34a61964e014b975bf466f3fefd8c4768b3e887d3da1be9dc942799bdfdf
+  checksum: 10c0/5fa839b9560221698edff5e00b5cccc658c7875efaa7971c66d478f5b026770f12dd47b1be024463a44f9e29b4e14e8ddddbf4a2b324b0b94f58370dd5ae7195
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-unicode-escapes@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-unicode-escapes@npm:7.24.7"
+"@babel/plugin-transform-unicode-escapes@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-unicode-escapes@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/8b18e2e66af33471a6971289492beff5c240e56727331db1d34c4338a6a368a82a7ed6d57ec911001b6d65643aed76531e1e7cac93265fb3fb2717f54d845e69
+  checksum: 10c0/8b1f71fda0a832c6e26ba4c00f99e9033e6f9b36ced542a512921f4ad861a70e2fec2bd54a91a5ca2efa46aaa8c8893e4c602635c4ef172bd3ed6eef3178c70b
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-unicode-property-regex@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.24.7"
+"@babel/plugin-transform-unicode-property-regex@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-unicode-property-regex@npm:7.25.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/bc57656eb94584d1b74a385d378818ac2b3fca642e3f649fead8da5fb3f9de22f8461185936915dfb33d5a9104e62e7a47828331248b09d28bb2d59e9276de3e
+  checksum: 10c0/b4bfcf7529138d00671bf5cdfe606603d52cfe57ec1be837da57683f404fc0b0c171834a02515eb03379e5c806121866d097b90e31cb437d21d0ea59368ad82b
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-unicode-regex@npm:^7.24.7":
-  version: 7.24.7
-  resolution: "@babel/plugin-transform-unicode-regex@npm:7.24.7"
+"@babel/plugin-transform-unicode-regex@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-unicode-regex@npm:7.25.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.24.7"
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/83f72a345b751566b601dc4d07e9f2c8f1bc0e0c6f7abb56ceb3095b3c9d304de73f85f2f477a09f8cc7edd5e65afd0ff9e376cdbcbea33bc0c28f3705b38fd9
+  checksum: 10c0/73ae34c02ea8b7ac7e4efa690f8c226089c074e3fef658d2a630ad898a93550d84146ce05e073c271c8b2bbba61cbbfd5a2002a7ea940dcad3274e5b5dcb6bcf
   languageName: node
   linkType: hard
 
-"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.4":
-  version: 7.25.4
-  resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.4"
+"@babel/plugin-transform-unicode-sets-regex@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/plugin-transform-unicode-sets-regex@npm:7.25.7"
   dependencies:
-    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.2"
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
+    "@babel/helper-create-regexp-features-plugin": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0
-  checksum: 10c0/f65749835a98d8d6242e961f9276bdcdb09020e791d151ccc145acaca9a66f025b2c7cb761104f139180d35eb066a429596ee6edece81f5fd9244e0edb97d7ec
+  checksum: 10c0/39e45ae3db7adfc3457b1d6ba5608ffbace957ad019785967e5357a6639f261765bda12363f655d39265f5a2834af26327037751420191d0b73152ccc7ce3c35
   languageName: node
   linkType: hard
 
 "@babel/preset-env@npm:^7.11.0, @babel/preset-env@npm:^7.12.1, @babel/preset-env@npm:^7.22.4":
-  version: 7.25.4
-  resolution: "@babel/preset-env@npm:7.25.4"
+  version: 7.25.7
+  resolution: "@babel/preset-env@npm:7.25.7"
   dependencies:
-    "@babel/compat-data": "npm:^7.25.4"
-    "@babel/helper-compilation-targets": "npm:^7.25.2"
-    "@babel/helper-plugin-utils": "npm:^7.24.8"
-    "@babel/helper-validator-option": "npm:^7.24.8"
-    "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.25.3"
-    "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.25.0"
-    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.25.0"
-    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.24.7"
-    "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.0"
+    "@babel/compat-data": "npm:^7.25.7"
+    "@babel/helper-compilation-targets": "npm:^7.25.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-validator-option": "npm:^7.25.7"
+    "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "npm:^7.25.7"
+    "@babel/plugin-bugfix-safari-class-field-initializer-scope": "npm:^7.25.7"
+    "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "npm:^7.25.7"
+    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "npm:^7.25.7"
+    "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "npm:^7.25.7"
     "@babel/plugin-proposal-private-property-in-object": "npm:7.21.0-placeholder-for-preset-env.2"
     "@babel/plugin-syntax-async-generators": "npm:^7.8.4"
     "@babel/plugin-syntax-class-properties": "npm:^7.12.13"
     "@babel/plugin-syntax-class-static-block": "npm:^7.14.5"
     "@babel/plugin-syntax-dynamic-import": "npm:^7.8.3"
     "@babel/plugin-syntax-export-namespace-from": "npm:^7.8.3"
-    "@babel/plugin-syntax-import-assertions": "npm:^7.24.7"
-    "@babel/plugin-syntax-import-attributes": "npm:^7.24.7"
+    "@babel/plugin-syntax-import-assertions": "npm:^7.25.7"
+    "@babel/plugin-syntax-import-attributes": "npm:^7.25.7"
     "@babel/plugin-syntax-import-meta": "npm:^7.10.4"
     "@babel/plugin-syntax-json-strings": "npm:^7.8.3"
     "@babel/plugin-syntax-logical-assignment-operators": "npm:^7.10.4"
@@ -1365,64 +1366,64 @@ __metadata:
     "@babel/plugin-syntax-private-property-in-object": "npm:^7.14.5"
     "@babel/plugin-syntax-top-level-await": "npm:^7.14.5"
     "@babel/plugin-syntax-unicode-sets-regex": "npm:^7.18.6"
-    "@babel/plugin-transform-arrow-functions": "npm:^7.24.7"
-    "@babel/plugin-transform-async-generator-functions": "npm:^7.25.4"
-    "@babel/plugin-transform-async-to-generator": "npm:^7.24.7"
-    "@babel/plugin-transform-block-scoped-functions": "npm:^7.24.7"
-    "@babel/plugin-transform-block-scoping": "npm:^7.25.0"
-    "@babel/plugin-transform-class-properties": "npm:^7.25.4"
-    "@babel/plugin-transform-class-static-block": "npm:^7.24.7"
-    "@babel/plugin-transform-classes": "npm:^7.25.4"
-    "@babel/plugin-transform-computed-properties": "npm:^7.24.7"
-    "@babel/plugin-transform-destructuring": "npm:^7.24.8"
-    "@babel/plugin-transform-dotall-regex": "npm:^7.24.7"
-    "@babel/plugin-transform-duplicate-keys": "npm:^7.24.7"
-    "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.0"
-    "@babel/plugin-transform-dynamic-import": "npm:^7.24.7"
-    "@babel/plugin-transform-exponentiation-operator": "npm:^7.24.7"
-    "@babel/plugin-transform-export-namespace-from": "npm:^7.24.7"
-    "@babel/plugin-transform-for-of": "npm:^7.24.7"
-    "@babel/plugin-transform-function-name": "npm:^7.25.1"
-    "@babel/plugin-transform-json-strings": "npm:^7.24.7"
-    "@babel/plugin-transform-literals": "npm:^7.25.2"
-    "@babel/plugin-transform-logical-assignment-operators": "npm:^7.24.7"
-    "@babel/plugin-transform-member-expression-literals": "npm:^7.24.7"
-    "@babel/plugin-transform-modules-amd": "npm:^7.24.7"
-    "@babel/plugin-transform-modules-commonjs": "npm:^7.24.8"
-    "@babel/plugin-transform-modules-systemjs": "npm:^7.25.0"
-    "@babel/plugin-transform-modules-umd": "npm:^7.24.7"
-    "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.24.7"
-    "@babel/plugin-transform-new-target": "npm:^7.24.7"
-    "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.24.7"
-    "@babel/plugin-transform-numeric-separator": "npm:^7.24.7"
-    "@babel/plugin-transform-object-rest-spread": "npm:^7.24.7"
-    "@babel/plugin-transform-object-super": "npm:^7.24.7"
-    "@babel/plugin-transform-optional-catch-binding": "npm:^7.24.7"
-    "@babel/plugin-transform-optional-chaining": "npm:^7.24.8"
-    "@babel/plugin-transform-parameters": "npm:^7.24.7"
-    "@babel/plugin-transform-private-methods": "npm:^7.25.4"
-    "@babel/plugin-transform-private-property-in-object": "npm:^7.24.7"
-    "@babel/plugin-transform-property-literals": "npm:^7.24.7"
-    "@babel/plugin-transform-regenerator": "npm:^7.24.7"
-    "@babel/plugin-transform-reserved-words": "npm:^7.24.7"
-    "@babel/plugin-transform-shorthand-properties": "npm:^7.24.7"
-    "@babel/plugin-transform-spread": "npm:^7.24.7"
-    "@babel/plugin-transform-sticky-regex": "npm:^7.24.7"
-    "@babel/plugin-transform-template-literals": "npm:^7.24.7"
-    "@babel/plugin-transform-typeof-symbol": "npm:^7.24.8"
-    "@babel/plugin-transform-unicode-escapes": "npm:^7.24.7"
-    "@babel/plugin-transform-unicode-property-regex": "npm:^7.24.7"
-    "@babel/plugin-transform-unicode-regex": "npm:^7.24.7"
-    "@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.4"
+    "@babel/plugin-transform-arrow-functions": "npm:^7.25.7"
+    "@babel/plugin-transform-async-generator-functions": "npm:^7.25.7"
+    "@babel/plugin-transform-async-to-generator": "npm:^7.25.7"
+    "@babel/plugin-transform-block-scoped-functions": "npm:^7.25.7"
+    "@babel/plugin-transform-block-scoping": "npm:^7.25.7"
+    "@babel/plugin-transform-class-properties": "npm:^7.25.7"
+    "@babel/plugin-transform-class-static-block": "npm:^7.25.7"
+    "@babel/plugin-transform-classes": "npm:^7.25.7"
+    "@babel/plugin-transform-computed-properties": "npm:^7.25.7"
+    "@babel/plugin-transform-destructuring": "npm:^7.25.7"
+    "@babel/plugin-transform-dotall-regex": "npm:^7.25.7"
+    "@babel/plugin-transform-duplicate-keys": "npm:^7.25.7"
+    "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "npm:^7.25.7"
+    "@babel/plugin-transform-dynamic-import": "npm:^7.25.7"
+    "@babel/plugin-transform-exponentiation-operator": "npm:^7.25.7"
+    "@babel/plugin-transform-export-namespace-from": "npm:^7.25.7"
+    "@babel/plugin-transform-for-of": "npm:^7.25.7"
+    "@babel/plugin-transform-function-name": "npm:^7.25.7"
+    "@babel/plugin-transform-json-strings": "npm:^7.25.7"
+    "@babel/plugin-transform-literals": "npm:^7.25.7"
+    "@babel/plugin-transform-logical-assignment-operators": "npm:^7.25.7"
+    "@babel/plugin-transform-member-expression-literals": "npm:^7.25.7"
+    "@babel/plugin-transform-modules-amd": "npm:^7.25.7"
+    "@babel/plugin-transform-modules-commonjs": "npm:^7.25.7"
+    "@babel/plugin-transform-modules-systemjs": "npm:^7.25.7"
+    "@babel/plugin-transform-modules-umd": "npm:^7.25.7"
+    "@babel/plugin-transform-named-capturing-groups-regex": "npm:^7.25.7"
+    "@babel/plugin-transform-new-target": "npm:^7.25.7"
+    "@babel/plugin-transform-nullish-coalescing-operator": "npm:^7.25.7"
+    "@babel/plugin-transform-numeric-separator": "npm:^7.25.7"
+    "@babel/plugin-transform-object-rest-spread": "npm:^7.25.7"
+    "@babel/plugin-transform-object-super": "npm:^7.25.7"
+    "@babel/plugin-transform-optional-catch-binding": "npm:^7.25.7"
+    "@babel/plugin-transform-optional-chaining": "npm:^7.25.7"
+    "@babel/plugin-transform-parameters": "npm:^7.25.7"
+    "@babel/plugin-transform-private-methods": "npm:^7.25.7"
+    "@babel/plugin-transform-private-property-in-object": "npm:^7.25.7"
+    "@babel/plugin-transform-property-literals": "npm:^7.25.7"
+    "@babel/plugin-transform-regenerator": "npm:^7.25.7"
+    "@babel/plugin-transform-reserved-words": "npm:^7.25.7"
+    "@babel/plugin-transform-shorthand-properties": "npm:^7.25.7"
+    "@babel/plugin-transform-spread": "npm:^7.25.7"
+    "@babel/plugin-transform-sticky-regex": "npm:^7.25.7"
+    "@babel/plugin-transform-template-literals": "npm:^7.25.7"
+    "@babel/plugin-transform-typeof-symbol": "npm:^7.25.7"
+    "@babel/plugin-transform-unicode-escapes": "npm:^7.25.7"
+    "@babel/plugin-transform-unicode-property-regex": "npm:^7.25.7"
+    "@babel/plugin-transform-unicode-regex": "npm:^7.25.7"
+    "@babel/plugin-transform-unicode-sets-regex": "npm:^7.25.7"
     "@babel/preset-modules": "npm:0.1.6-no-external-plugins"
     babel-plugin-polyfill-corejs2: "npm:^0.4.10"
     babel-plugin-polyfill-corejs3: "npm:^0.10.6"
     babel-plugin-polyfill-regenerator: "npm:^0.6.1"
-    core-js-compat: "npm:^3.37.1"
+    core-js-compat: "npm:^3.38.1"
     semver: "npm:^6.3.1"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/ed210a1974b5a1e7f80a933c87253907ec869457cea900bc97892642fa9a690c47627a9bac08a7c9495deb992a2b15f308ffca2741e1876ba47172c96fa27e14
+  checksum: 10c0/bf704a06a69420250c1de2b126cd5c859a851002c2fb2cce0910cd85a8e6755b9b31577021e94feb7e1e53519923726349aaf07580923928791583db61438fb8
   languageName: node
   linkType: hard
 
@@ -1440,40 +1441,33 @@ __metadata:
   linkType: hard
 
 "@babel/preset-react@npm:^7.12.5, @babel/preset-react@npm:^7.22.3":
-  version: 7.24.7
-  resolution: "@babel/preset-react@npm:7.24.7"
+  version: 7.25.7
+  resolution: "@babel/preset-react@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
-    "@babel/helper-validator-option": "npm:^7.24.7"
-    "@babel/plugin-transform-react-display-name": "npm:^7.24.7"
-    "@babel/plugin-transform-react-jsx": "npm:^7.24.7"
-    "@babel/plugin-transform-react-jsx-development": "npm:^7.24.7"
-    "@babel/plugin-transform-react-pure-annotations": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-validator-option": "npm:^7.25.7"
+    "@babel/plugin-transform-react-display-name": "npm:^7.25.7"
+    "@babel/plugin-transform-react-jsx": "npm:^7.25.7"
+    "@babel/plugin-transform-react-jsx-development": "npm:^7.25.7"
+    "@babel/plugin-transform-react-pure-annotations": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/9658b685b25cedaadd0b65c4e663fbc7f57394b5036ddb4c99b1a75b0711fb83292c1c625d605c05b73413fc7a6dc20e532627f6a39b6dc8d4e00415479b054c
+  checksum: 10c0/b133b1a2f46c70a337d8b1ef442e09e3dbdaecb0d6bed8f1cb64dfddc31c16e248b017385ab909caeebd8462111c9c0e1c5409deb10f2be5cb5bcfdaa4d27718
   languageName: node
   linkType: hard
 
 "@babel/preset-typescript@npm:^7.21.5":
-  version: 7.24.7
-  resolution: "@babel/preset-typescript@npm:7.24.7"
+  version: 7.25.7
+  resolution: "@babel/preset-typescript@npm:7.25.7"
   dependencies:
-    "@babel/helper-plugin-utils": "npm:^7.24.7"
-    "@babel/helper-validator-option": "npm:^7.24.7"
-    "@babel/plugin-syntax-jsx": "npm:^7.24.7"
-    "@babel/plugin-transform-modules-commonjs": "npm:^7.24.7"
-    "@babel/plugin-transform-typescript": "npm:^7.24.7"
+    "@babel/helper-plugin-utils": "npm:^7.25.7"
+    "@babel/helper-validator-option": "npm:^7.25.7"
+    "@babel/plugin-syntax-jsx": "npm:^7.25.7"
+    "@babel/plugin-transform-modules-commonjs": "npm:^7.25.7"
+    "@babel/plugin-transform-typescript": "npm:^7.25.7"
   peerDependencies:
     "@babel/core": ^7.0.0-0
-  checksum: 10c0/986bc0978eedb4da33aba8e1e13a3426dd1829515313b7e8f4ba5d8c18aff1663b468939d471814e7acf4045d326ae6cff37239878d169ac3fe53a8fde71f8ee
-  languageName: node
-  linkType: hard
-
-"@babel/regjsgen@npm:^0.8.0":
-  version: 0.8.0
-  resolution: "@babel/regjsgen@npm:0.8.0"
-  checksum: 10c0/4f3ddd8c7c96d447e05c8304c1d5ba3a83fcabd8a716bc1091c2f31595cdd43a3a055fff7cb5d3042b8cb7d402d78820fcb4e05d896c605a7d8bcf30f2424c4a
+  checksum: 10c0/8dc1258e3c5230bbe42ff9811f08924509238e6bd32fa0b7b0c0a6c5e1419512a8e1f733e1b114454d367b7c164beca2cf33acf2ed9e0d99be010c1c5cdbef0c
   languageName: node
   linkType: hard
 
@@ -1487,48 +1481,48 @@ __metadata:
   linkType: hard
 
 "@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.13, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.13.8, @babel/runtime@npm:^7.18.3, @babel/runtime@npm:^7.2.0, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.22.3, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.3, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2":
-  version: 7.25.6
-  resolution: "@babel/runtime@npm:7.25.6"
+  version: 7.25.7
+  resolution: "@babel/runtime@npm:7.25.7"
   dependencies:
     regenerator-runtime: "npm:^0.14.0"
-  checksum: 10c0/d6143adf5aa1ce79ed374e33fdfd74fa975055a80bc6e479672ab1eadc4e4bfd7484444e17dd063a1d180e051f3ec62b357c7a2b817e7657687b47313158c3d2
+  checksum: 10c0/86b7829d2fc9343714a9afe92757cf96c4dc799006ca61d73cda62f4b9e29bfa1ce36794955bc6cb4c188f5b10db832c949339895e1bbe81a69022d9d578ce29
   languageName: node
   linkType: hard
 
-"@babel/template@npm:^7.24.7, @babel/template@npm:^7.25.0, @babel/template@npm:^7.3.3":
-  version: 7.25.0
-  resolution: "@babel/template@npm:7.25.0"
+"@babel/template@npm:^7.25.7, @babel/template@npm:^7.3.3":
+  version: 7.25.7
+  resolution: "@babel/template@npm:7.25.7"
   dependencies:
-    "@babel/code-frame": "npm:^7.24.7"
-    "@babel/parser": "npm:^7.25.0"
-    "@babel/types": "npm:^7.25.0"
-  checksum: 10c0/4e31afd873215744c016e02b04f43b9fa23205d6d0766fb2e93eb4091c60c1b88897936adb895fb04e3c23de98dfdcbe31bc98daaa1a4e0133f78bb948e1209b
+    "@babel/code-frame": "npm:^7.25.7"
+    "@babel/parser": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
+  checksum: 10c0/8ae9e36e4330ee83d4832531d1d9bec7dc2ef6a2a8afa1ef1229506fd60667abcb17f306d1c3d7e582251270597022990c845d5d69e7add70a5aea66720decb9
   languageName: node
   linkType: hard
 
-"@babel/traverse@npm:7, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.0, @babel/traverse@npm:^7.25.1, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.25.3, @babel/traverse@npm:^7.25.4":
-  version: 7.25.4
-  resolution: "@babel/traverse@npm:7.25.4"
+"@babel/traverse@npm:7, @babel/traverse@npm:^7.25.7":
+  version: 7.25.7
+  resolution: "@babel/traverse@npm:7.25.7"
   dependencies:
-    "@babel/code-frame": "npm:^7.24.7"
-    "@babel/generator": "npm:^7.25.4"
-    "@babel/parser": "npm:^7.25.4"
-    "@babel/template": "npm:^7.25.0"
-    "@babel/types": "npm:^7.25.4"
+    "@babel/code-frame": "npm:^7.25.7"
+    "@babel/generator": "npm:^7.25.7"
+    "@babel/parser": "npm:^7.25.7"
+    "@babel/template": "npm:^7.25.7"
+    "@babel/types": "npm:^7.25.7"
     debug: "npm:^4.3.1"
     globals: "npm:^11.1.0"
-  checksum: 10c0/37c9b49b277e051fe499ef5f6f217370c4f648d6370564d70b5e6beb2da75bfda6d7dab1d39504d89e9245448f8959bc1a5880d2238840cdc3979b35338ed0f5
+  checksum: 10c0/75d73e52c507a7a7a4c7971d6bf4f8f26fdd094e0d3a0193d77edf6a5efa36fc3db91ec5cc48e8b94e6eb5d5ad21af0a1040e71309172851209415fd105efb1a
   languageName: node
   linkType: hard
 
-"@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.24.7, @babel/types@npm:^7.24.8, @babel/types@npm:^7.25.0, @babel/types@npm:^7.25.2, @babel/types@npm:^7.25.4, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
-  version: 7.25.4
-  resolution: "@babel/types@npm:7.25.4"
+"@babel/types@npm:^7.0.0, @babel/types@npm:^7.0.0-beta.49, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.25.7, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4":
+  version: 7.25.7
+  resolution: "@babel/types@npm:7.25.7"
   dependencies:
-    "@babel/helper-string-parser": "npm:^7.24.8"
-    "@babel/helper-validator-identifier": "npm:^7.24.7"
+    "@babel/helper-string-parser": "npm:^7.25.7"
+    "@babel/helper-validator-identifier": "npm:^7.25.7"
     to-fast-properties: "npm:^2.0.0"
-  checksum: 10c0/9aa25dfcd89cc4e4dde3188091c34398a005a49e2c2b069d0367b41e1122c91e80fd92998c52a90f2fb500f7e897b6090ec8be263d9cb53d0d75c756f44419f2
+  checksum: 10c0/e03e1e2e08600fa1e8eb90632ac9c253dd748176c8d670d85f85b0dc83a0573b26ae748a1cbcb81f401903a3d95f43c3f4f8d516a5ed779929db27de56289633
   languageName: node
   linkType: hard
 
@@ -5687,17 +5681,17 @@ __metadata:
   languageName: node
   linkType: hard
 
-"browserslist@npm:^4.0.0, browserslist@npm:^4.23.0, browserslist@npm:^4.23.1, browserslist@npm:^4.23.3":
-  version: 4.23.3
-  resolution: "browserslist@npm:4.23.3"
+"browserslist@npm:^4.0.0, browserslist@npm:^4.23.0, browserslist@npm:^4.23.1, browserslist@npm:^4.23.3, browserslist@npm:^4.24.0":
+  version: 4.24.0
+  resolution: "browserslist@npm:4.24.0"
   dependencies:
-    caniuse-lite: "npm:^1.0.30001646"
-    electron-to-chromium: "npm:^1.5.4"
+    caniuse-lite: "npm:^1.0.30001663"
+    electron-to-chromium: "npm:^1.5.28"
     node-releases: "npm:^2.0.18"
     update-browserslist-db: "npm:^1.1.0"
   bin:
     browserslist: cli.js
-  checksum: 10c0/3063bfdf812815346447f4796c8f04601bf5d62003374305fd323c2a463e42776475bcc5309264e39bcf9a8605851e53560695991a623be988138b3ff8c66642
+  checksum: 10c0/95e76ad522753c4c470427f6e3c8a4bb5478ff448841e22b3d3e53f89ecaf17b6984666d6c7e715c370f1e7fa0cf684f42e34e554236a8b2fab38ea76b9e4c52
   languageName: node
   linkType: hard
 
@@ -5899,10 +5893,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001599, caniuse-lite@npm:^1.0.30001646":
-  version: 1.0.30001651
-  resolution: "caniuse-lite@npm:1.0.30001651"
-  checksum: 10c0/7821278952a6dbd17358e5d08083d258f092e2a530f5bc1840657cb140fbbc5ec44293bc888258c44a18a9570cde149ed05819ac8320b9710cf22f699891e6ad
+"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001599, caniuse-lite@npm:^1.0.30001663":
+  version: 1.0.30001666
+  resolution: "caniuse-lite@npm:1.0.30001666"
+  checksum: 10c0/2d49e9be676233c24717f12aad3d01b3e5f902b457fe1deefaa8d82e64786788a8f79381ae437c61b50e15c9aea8aeb59871b1d54cb4c28b9190d53d292e2339
   languageName: node
   linkType: hard
 
@@ -6412,7 +6406,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"core-js-compat@npm:^3.37.1, core-js-compat@npm:^3.38.0":
+"core-js-compat@npm:^3.38.0, core-js-compat@npm:^3.38.1":
   version: 3.38.1
   resolution: "core-js-compat@npm:3.38.1"
   dependencies:
@@ -7474,10 +7468,10 @@ __metadata:
   languageName: node
   linkType: hard
 
-"electron-to-chromium@npm:^1.5.4":
-  version: 1.5.7
-  resolution: "electron-to-chromium@npm:1.5.7"
-  checksum: 10c0/be4460bbe3d2186a16d53a03da67fde6fd06ad41943553ce517a45d52e03424732a982f75528e8a2d5fb042d6afde64186aa482caec0fb925daa5a74cf5ef060
+"electron-to-chromium@npm:^1.5.28":
+  version: 1.5.31
+  resolution: "electron-to-chromium@npm:1.5.31"
+  checksum: 10c0/e8aecd88c4c6d50a9d459b4b222865b855bab8f1b52e82913804e18b7884f2887bd76c61b3aa08c2ccbdcda098dd8486443f75bf770f0138f21dd9e63548fca7
   languageName: node
   linkType: hard
 
@@ -11190,21 +11184,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"jsesc@npm:^2.5.1":
-  version: 2.5.2
-  resolution: "jsesc@npm:2.5.2"
+"jsesc@npm:^3.0.2, jsesc@npm:~3.0.2":
+  version: 3.0.2
+  resolution: "jsesc@npm:3.0.2"
   bin:
     jsesc: bin/jsesc
-  checksum: 10c0/dbf59312e0ebf2b4405ef413ec2b25abb5f8f4d9bc5fb8d9f90381622ebca5f2af6a6aa9a8578f65903f9e33990a6dc798edd0ce5586894bf0e9e31803a1de88
-  languageName: node
-  linkType: hard
-
-"jsesc@npm:~0.5.0":
-  version: 0.5.0
-  resolution: "jsesc@npm:0.5.0"
-  bin:
-    jsesc: bin/jsesc
-  checksum: 10c0/f93792440ae1d80f091b65f8ceddf8e55c4bb7f1a09dee5dcbdb0db5612c55c0f6045625aa6b7e8edb2e0a4feabd80ee48616dbe2d37055573a84db3d24f96d9
+  checksum: 10c0/ef22148f9e793180b14d8a145ee6f9f60f301abf443288117b4b6c53d0ecd58354898dc506ccbb553a5f7827965cd38bc5fb726575aae93c5e8915e2de8290e1
   languageName: node
   linkType: hard
 
@@ -15042,12 +15027,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"regenerate-unicode-properties@npm:^10.1.0":
-  version: 10.1.1
-  resolution: "regenerate-unicode-properties@npm:10.1.1"
+"regenerate-unicode-properties@npm:^10.2.0":
+  version: 10.2.0
+  resolution: "regenerate-unicode-properties@npm:10.2.0"
   dependencies:
     regenerate: "npm:^1.4.2"
-  checksum: 10c0/89adb5ee5ba081380c78f9057c02e156a8181969f6fcca72451efc45612e0c3df767b4333f8d8479c274d9c6fe52ec4854f0d8a22ef95dccbe87da8e5f2ac77d
+  checksum: 10c0/5510785eeaf56bbfdf4e663d6753f125c08d2a372d4107bc1b756b7bf142e2ed80c2733a8b54e68fb309ba37690e66a0362699b0e21d5c1f0255dea1b00e6460
   languageName: node
   linkType: hard
 
@@ -15110,28 +15095,35 @@ __metadata:
   languageName: node
   linkType: hard
 
-"regexpu-core@npm:^5.3.1":
-  version: 5.3.2
-  resolution: "regexpu-core@npm:5.3.2"
+"regexpu-core@npm:^6.1.1":
+  version: 6.1.1
+  resolution: "regexpu-core@npm:6.1.1"
   dependencies:
-    "@babel/regjsgen": "npm:^0.8.0"
     regenerate: "npm:^1.4.2"
-    regenerate-unicode-properties: "npm:^10.1.0"
-    regjsparser: "npm:^0.9.1"
+    regenerate-unicode-properties: "npm:^10.2.0"
+    regjsgen: "npm:^0.8.0"
+    regjsparser: "npm:^0.11.0"
     unicode-match-property-ecmascript: "npm:^2.0.0"
     unicode-match-property-value-ecmascript: "npm:^2.1.0"
-  checksum: 10c0/7945d5ab10c8bbed3ca383d4274687ea825aee4ab93a9c51c6e31e1365edd5ea807f6908f800ba017b66c462944ba68011164e7055207747ab651f8111ef3770
+  checksum: 10c0/07d49697e20f9b65977535abba4858b7f5171c13f7c366be53ec1886d3d5f69f1b98cc6a6e63cf271adda077c3366a4c851c7473c28bbd69cf5a6b6b008efc3e
   languageName: node
   linkType: hard
 
-"regjsparser@npm:^0.9.1":
-  version: 0.9.1
-  resolution: "regjsparser@npm:0.9.1"
+"regjsgen@npm:^0.8.0":
+  version: 0.8.0
+  resolution: "regjsgen@npm:0.8.0"
+  checksum: 10c0/44f526c4fdbf0b29286101a282189e4dbb303f4013cf3fea058668d96d113b9180d3d03d1e13f6d4cbde38b7728bf951aecd9dc199938c080093a9a6f0d7a6bd
+  languageName: node
+  linkType: hard
+
+"regjsparser@npm:^0.11.0":
+  version: 0.11.0
+  resolution: "regjsparser@npm:0.11.0"
   dependencies:
-    jsesc: "npm:~0.5.0"
+    jsesc: "npm:~3.0.2"
   bin:
     regjsparser: bin/parser
-  checksum: 10c0/fe44fcf19a99fe4f92809b0b6179530e5ef313ff7f87df143b08ce9a2eb3c4b6189b43735d645be6e8f4033bfb015ed1ca54f0583bc7561bed53fd379feb8225
+  checksum: 10c0/155143a8f2c95e3170df4fff10ddf3f16a351b5d2b8cbb257e9f4a50abb9a980a28af0936b5bf850fee767537ffa8eb77c6b211fe8be19834dbe584dfd950c62
   languageName: node
   linkType: hard
 

From fe3f5375e3c0a2f138558947db69267813c0e726 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 3 Oct 2024 11:15:31 +0200
Subject: [PATCH 49/70] New Crowdin Translations (automated) (#32233)

Co-authored-by: GitHub Actions <noreply@github.com>
---
 app/javascript/mastodon/locales/de.json |   4 +-
 app/javascript/mastodon/locales/nl.json |  10 +-
 app/javascript/mastodon/locales/sc.json | 124 ++++++++++++++++++++++++
 app/javascript/mastodon/locales/tr.json |   4 +-
 app/javascript/mastodon/locales/uk.json |   5 +
 config/locales/de.yml                   |   2 +-
 config/locales/sc.yml                   |  43 ++++++++
 config/locales/tr.yml                   |   2 +-
 8 files changed, 183 insertions(+), 11 deletions(-)

diff --git a/app/javascript/mastodon/locales/de.json b/app/javascript/mastodon/locales/de.json
index 6f45d4fe5..0b541b2e4 100644
--- a/app/javascript/mastodon/locales/de.json
+++ b/app/javascript/mastodon/locales/de.json
@@ -41,7 +41,7 @@
   "account.go_to_profile": "Profil aufrufen",
   "account.hide_reblogs": "Geteilte Beiträge von @{name} ausblenden",
   "account.in_memoriam": "Zum Andenken.",
-  "account.joined_short": "Beigetreten",
+  "account.joined_short": "Mitglied seit",
   "account.languages": "Ausgewählte Sprachen ändern",
   "account.link_verified_on": "Das Profil mit dieser E-Mail-Adresse wurde bereits am {date} bestätigt",
   "account.locked_info": "Die Privatsphäre dieses Kontos wurde auf „geschützt“ gesetzt. Die Person bestimmt manuell, wer ihrem Profil folgen darf.",
@@ -534,7 +534,7 @@
   "notification.relationships_severance_event.domain_block": "Ein Admin von {from} hat {target} blockiert – darunter {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst.",
   "notification.relationships_severance_event.learn_more": "Mehr erfahren",
   "notification.relationships_severance_event.user_domain_block": "Du hast {target} blockiert – {followersCount} deiner Follower und {followingCount, plural, one {# Konto, dem} other {# Konten, denen}} du folgst, wurden entfernt.",
-  "notification.status": "{name} hat gerade etwas gepostet",
+  "notification.status": "{name} veröffentlichte gerade",
   "notification.update": "{name} bearbeitete einen Beitrag",
   "notification_requests.accept": "Genehmigen",
   "notification_requests.accept_multiple": "{count, plural, one {# Anfrage genehmigen …} other {# Anfragen genehmigen …}}",
diff --git a/app/javascript/mastodon/locales/nl.json b/app/javascript/mastodon/locales/nl.json
index 1c31574de..71523b37d 100644
--- a/app/javascript/mastodon/locales/nl.json
+++ b/app/javascript/mastodon/locales/nl.json
@@ -504,13 +504,13 @@
   "notification.admin.report_statuses": "{name} rapporteerde {target} voor {category}",
   "notification.admin.report_statuses_other": "{name} rapporteerde {target}",
   "notification.admin.sign_up": "{name} heeft zich geregistreerd",
-  "notification.admin.sign_up.name_and_others": "{name} en {count, plural, one {# ander} other {# anderen}} hebben zich geregistreerd",
+  "notification.admin.sign_up.name_and_others": "{name} en {count, plural, one {# ander persoon} other {# andere personen}} hebben zich geregistreerd",
   "notification.favourite": "{name} markeerde jouw bericht als favoriet",
-  "notification.favourite.name_and_others_with_link": "{name} en <a>{count, plural, one {# ander} other {# anderen}}</a> hebben jouw bericht als favoriet gemarkeerd",
+  "notification.favourite.name_and_others_with_link": "{name} en <a>{count, plural, one {# ander persoon} other {# andere personen}}</a> hebben jouw bericht als favoriet gemarkeerd",
   "notification.follow": "{name} volgt jou nu",
-  "notification.follow.name_and_others": "{name} en {count, plural, one {# ander} other {# anderen}} hebben je gevolgd",
+  "notification.follow.name_and_others": "{name} en {count, plural, one {# ander persoon} other {# andere personen}} hebben je gevolgd",
   "notification.follow_request": "{name} wil jou graag volgen",
-  "notification.follow_request.name_and_others": "{name} en {count, plural, one {# ander} other {# anderen}} hebben gevraagd om je te volgen",
+  "notification.follow_request.name_and_others": "{name} en {count, plural, one {# ander persoon} other {# andere personen}} hebben gevraagd om je te volgen",
   "notification.label.mention": "Vermelding",
   "notification.label.private_mention": "Privébericht",
   "notification.label.private_reply": "Privéreactie",
@@ -528,7 +528,7 @@
   "notification.own_poll": "Jouw peiling is beëindigd",
   "notification.poll": "Een peiling waaraan jij hebt meegedaan is beëindigd",
   "notification.reblog": "{name} boostte jouw bericht",
-  "notification.reblog.name_and_others_with_link": "{name} en <a>{count, plural, one {# ander} other {# anderen}}</a> hebben jouw bericht geboost",
+  "notification.reblog.name_and_others_with_link": "{name} en <a>{count, plural, one {# ander persoon} other {# andere personen}}</a> hebben jouw bericht geboost",
   "notification.relationships_severance_event": "Verloren verbindingen met {name}",
   "notification.relationships_severance_event.account_suspension": "Een beheerder van {from} heeft {target} geschorst, wat betekent dat je geen updates meer van hen kunt ontvangen of met hen kunt communiceren.",
   "notification.relationships_severance_event.domain_block": "Een beheerder van {from} heeft {target} geblokkeerd, inclusief {followersCount} van jouw volgers en {followingCount, plural, one {# account} other {# accounts}} die jij volgt.",
diff --git a/app/javascript/mastodon/locales/sc.json b/app/javascript/mastodon/locales/sc.json
index bb7d062b9..227a7483a 100644
--- a/app/javascript/mastodon/locales/sc.json
+++ b/app/javascript/mastodon/locales/sc.json
@@ -85,6 +85,7 @@
   "alert.rate_limited.title": "Màssimu de rechestas barigadu",
   "alert.unexpected.message": "Ddoe est istada una faddina.",
   "alert.unexpected.title": "Oh!",
+  "alt_text_badge.title": "Testu alternativu",
   "announcement.announcement": "Annùntziu",
   "attachments_list.unprocessed": "(non protzessadu)",
   "audio.hide": "Cua s'àudio",
@@ -97,6 +98,8 @@
   "block_modal.title": "Boles blocare s'utente?",
   "block_modal.you_wont_see_mentions": "No as a bìdere is publicatziones chi mèntovent custa persone.",
   "boost_modal.combo": "Podes incarcare {combo} pro brincare custu sa borta chi benit",
+  "boost_modal.reblog": "Boles potentziare sa publicatzione?",
+  "boost_modal.undo_reblog": "Boles tzessare de potentziare sa publicatzione?",
   "bundle_column_error.copy_stacktrace": "Còpia s'informe de faddina",
   "bundle_column_error.error.body": "Sa pàgina pedida non faghiat a dda renderizare. Diat pòdere èssere pro neghe de una faddina in su còdighe nostru, o de unu problema de cumpatibilidade de su navigadore.",
   "bundle_column_error.error.title": "Oh, no!",
@@ -104,11 +107,15 @@
   "bundle_column_error.network.title": "Faddina de connessione",
   "bundle_column_error.retry": "Torra·bi a proare",
   "bundle_column_error.return": "Torra a sa pàgina printzipale",
+  "bundle_column_error.routing.body": "Impossìbile agatare sa pàgina rechesta. Seguru chi s'URL in sa barra de indiritzos est curretu?",
   "bundle_column_error.routing.title": "404",
   "bundle_modal_error.close": "Serra",
   "bundle_modal_error.message": "Faddina in su carrigamentu de custu cumponente.",
   "bundle_modal_error.retry": "Torra·bi a proare",
+  "closed_registrations.other_server_instructions": "Dae chi Mastodon est detzentralizadu, podes creare unu contu in un'àteru serbidore e interagire cun custu.",
+  "closed_registrations_modal.description": "Sa creatzione de contos in {domain} no est possìbile in custu momentu, però tene in cunsideru chi non tenes bisòngiu de unu contu ispetzìficu in {domain} pro impreare Mastodon.",
   "closed_registrations_modal.find_another_server": "Agata un'àteru serbidore",
+  "closed_registrations_modal.title": "Su registru a Mastodon",
   "column.about": "Informatziones",
   "column.blocks": "Persones blocadas",
   "column.bookmarks": "Sinnalibros",
@@ -142,6 +149,7 @@
   "compose.published.open": "Aberi",
   "compose.saved.body": "Publicatzione sarvada.",
   "compose_form.direct_message_warning_learn_more": "Àteras informatziones",
+  "compose_form.encryption_warning": "Is publicatziones a Mastodon no sunt critografados a nodu terminale. Non cumpartzas informatziones delicadas in Mastodon.",
   "compose_form.hashtag_warning": "Custa publicatzione no at a èssere ammustrada in peruna eticheta, dae chi no est pùblica. Isceti is publicatziones pùblicas podent èssere chircadas cun etichetas.",
   "compose_form.lock_disclaimer": "Su contu tuo no est {locked}. Cale si siat persone ti podet sighire pro bìdere is messàgios tuos chi imbies a sa gente chi ti sighit.",
   "compose_form.lock_disclaimer.lock": "blocadu",
@@ -171,15 +179,23 @@
   "confirmations.discard_edit_media.confirm": "Iscarta",
   "confirmations.discard_edit_media.message": "Tenes modìficas non sarvadas a is descritziones o a is anteprimas de is cuntenutos, ddas boles iscartare su matessi?",
   "confirmations.edit.confirm": "Modìfica",
+  "confirmations.edit.message": "Modifichende immoe as a subrascrìere su messàgiu chi ses iscriende. Seguru chi boles sighire?",
+  "confirmations.edit.title": "Boles subraiscrìere sa publicatzione?",
   "confirmations.logout.confirm": "Essi·nche",
   "confirmations.logout.message": "Seguru chi boles essire?",
+  "confirmations.logout.title": "Boles serrare sa sessione?",
   "confirmations.mute.confirm": "A sa muda",
   "confirmations.redraft.confirm": "Cantzella e torra a fàghere",
   "confirmations.redraft.message": "Seguru chi boles cantzellare e torrare a fàghere custa publicatzione? As a pèrdere is preferidos e is cumpartziduras, e is rispostas a su messàgiu originale ant a abarrare òrfanas.",
+  "confirmations.redraft.title": "Boles cantzellare e torrare a iscrìere sa publicatzione?",
   "confirmations.reply.confirm": "Risponde",
   "confirmations.reply.message": "Rispondende immoe as a subrascrìere su messàgiu chi ses iscriende. Seguru chi boles sighire?",
+  "confirmations.reply.title": "Boles subraiscrìere sa publicatzione?",
   "confirmations.unfollow.confirm": "Non sigas prus",
   "confirmations.unfollow.message": "Seguru chi non boles sighire prus a {name}?",
+  "confirmations.unfollow.title": "Boles tzessare de sighire s'utente?",
+  "content_warning.hide": "Cua sa publicatzione",
+  "content_warning.show": "Ammustra·dda su pròpiu",
   "conversation.delete": "Cantzella arresonada",
   "conversation.mark_as_read": "Signala comente lèghidu",
   "conversation.open": "Ammustra arresonada",
@@ -193,7 +209,10 @@
   "directory.recently_active": "Cun atividade dae pagu",
   "disabled_account_banner.account_settings": "Cunfiguratziones de su contu",
   "disabled_account_banner.text": "Su contu tuo {disabledAccount} no est ativu.",
+  "dismissable_banner.community_timeline": "Custas sunt is publicatziones pùblicas prus reghentes dae gente cun contu in {domain}.",
   "dismissable_banner.dismiss": "Iscarta",
+  "dismissable_banner.explore_links": "Custas sunt is istòrias de noas prus cumpartzidas in sa rete oe. Is istòrias prus noas publicadas dae gente prus diversa ant a èssere priorizadas.",
+  "dismissable_banner.explore_statuses": "Custas sunt publicatziones dae sa rete detzentralizada chi sunt retzende atentzione oe. Is publicatziones prus noas cun prus cumpartziduras e preferèntzias ant a èssere priorizadas.",
   "domain_block_modal.block": "Bloca su serbidore",
   "domain_block_modal.block_account_instead": "Bloca imbetzes a @{name}",
   "domain_block_modal.they_can_interact_with_old_posts": "Is persones de custu serbidore podent ancora interagire cun is publicatziones betzas tuas.",
@@ -207,6 +226,7 @@
   "domain_pill.their_handle": "S'identificadore suo:",
   "domain_pill.their_server": "Sa domo digitale sua, in ue istant totu is publicatziones suas.",
   "domain_pill.username": "Nòmine de utente",
+  "domain_pill.whats_in_a_handle": "Ite est un'identificadore?",
   "domain_pill.your_handle": "S'identificadore tuo:",
   "domain_pill.your_server": "Sa domo digitale tua, in ue istant totu is publicatziones tuas. Custa non t'agradat? Tràmuda serbidore in cale si siat momentu e bati·ti fintzas in fatu is sighidores tuos.",
   "domain_pill.your_username": "S'identificadore ùnicu tuo in custu serbidore. Si podent agatare utentes cun su matessi nòmine de utente in àteros serbidores.",
@@ -254,6 +274,7 @@
   "explore.trending_links": "Noas",
   "explore.trending_statuses": "Publicatziones",
   "explore.trending_tags": "Etichetas",
+  "filter_modal.added.context_mismatch_title": "Su cuntestu non currispondet.",
   "filter_modal.added.expired_title": "Filtru iscadidu.",
   "filter_modal.added.review_and_configure_title": "Cunfiguratziones de filtru",
   "filter_modal.added.settings_link": "pàgina de cunfiguratzione",
@@ -277,7 +298,13 @@
   "follow_suggestions.featured_longer": "Seberadu a manu dae s'iscuadra de {domain}",
   "follow_suggestions.friends_of_friends_longer": "Populare intre persones chi sighis",
   "follow_suggestions.hints.featured": "Custu profilu est istadu seberadu a manu dae s'iscuadra {domain}.",
+  "follow_suggestions.personalized_suggestion": "Cussìgiu personalizadu",
+  "follow_suggestions.popular_suggestion": "Cussìgiu populare",
+  "follow_suggestions.popular_suggestion_longer": "Populare a {domain}",
+  "follow_suggestions.similar_to_recently_followed_longer": "Profilos sìmiles a is chi as sighidu de reghente",
   "follow_suggestions.view_all": "Ammustra totu",
+  "follow_suggestions.who_to_follow": "Chie sighire",
+  "followed_tags": "Etichetas sighidas",
   "footer.about": "Informatziones",
   "footer.directory": "Diretòriu de profilos",
   "footer.get_app": "Otene s'aplicatzione",
@@ -302,6 +329,11 @@
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} publicatzione} other {{counter} publicatziones}} oe",
   "hashtag.follow": "Sighi su hashtag",
   "hashtag.unfollow": "Non sigas prus s'eticheta",
+  "hashtags.and_other": "… e {count, plural, one {un'àteru} other {àteros #}}",
+  "hints.profiles.posts_may_be_missing": "Podet èssere chi ammanchent tzertas publicatziones de custu profilu.",
+  "hints.profiles.see_more_posts": "Bide prus publicatziones a {domain}",
+  "hints.threads.replies_may_be_missing": "Podet èssere chi ammanchent rispostas dae àteros serbidores.",
+  "hints.threads.see_more": "Bide prus rispostas a {domain}",
   "home.column_settings.show_reblogs": "Ammustra is cumpartziduras",
   "home.column_settings.show_replies": "Ammustra rispostas",
   "home.hide_announcements": "Cua annùntzios",
@@ -309,7 +341,14 @@
   "home.pending_critical_update.link": "Ammustra is atualizatziones",
   "home.pending_critical_update.title": "Atualizatzione de seguresa crìtica a disponimentu.",
   "home.show_announcements": "Ammustra annùntzios",
+  "ignore_notifications_modal.disclaimer": "Mastodon non podet informare is utentes chi as innioradu is notìficas issoro. Inniorare notìficas no at a evitare chi s'imbient is messàgios.",
+  "ignore_notifications_modal.filter_instead": "Opuru filtra",
+  "ignore_notifications_modal.filter_to_act_users": "As a pòdere ancora atzetare, refudare o sinnalare a utentes",
+  "ignore_notifications_modal.filter_to_avoid_confusion": "Filtrare agiudat a evitare possìbiles confusiones",
   "interaction_modal.description.reply": "Podes rispòndere a custa publicatzione cun unu contu de Mastodon.",
+  "interaction_modal.login.action": "Torra a sa pàgina printzipale",
+  "interaction_modal.login.prompt": "Su domìniu de su serbidore domèsticu tuo, pro esempru mastodon.social",
+  "interaction_modal.no_account_yet": "Non ses in Mastodon?",
   "interaction_modal.on_this_server": "In custu serbidore",
   "interaction_modal.title.follow": "Sighi a {name}",
   "interaction_modal.title.reply": "Risponde a sa publicatzione de {name}",
@@ -353,11 +392,13 @@
   "lightbox.next": "Imbeniente",
   "lightbox.previous": "Pretzedente",
   "limited_account_hint.title": "Custu profilu est istadu cuadu dae sa moderatzione de {domain}.",
+  "link_preview.shares": "{count, plural, one {{counter} publicatzione} other {{counter} publicatziones}}",
   "lists.account.add": "Agiunghe a sa lista",
   "lists.account.remove": "Boga dae sa lista",
   "lists.delete": "Cantzella sa lista",
   "lists.edit": "Modìfica sa lista",
   "lists.edit.submit": "Muda su tìtulu",
+  "lists.exclusive": "Cua custas publicatziones dae sa pàgina printzipale",
   "lists.new.create": "Agiunghe lista",
   "lists.new.title_placeholder": "Tìtulu de sa lista noa",
   "lists.replies_policy.followed": "Cale si siat persone chi sighis",
@@ -368,7 +409,20 @@
   "lists.subheading": "Is listas tuas",
   "load_pending": "{count, plural, one {# elementu nou} other {# elementos noos}}",
   "loading_indicator.label": "Carrighende…",
+  "media_gallery.hide": "Cua",
+  "moved_to_account_banner.text": "Su contu tuo {disabledAccount} est disativadu in custu momentu ca est istadu tramudadu a {movedToAccount}.",
+  "mute_modal.hide_from_notifications": "Cua dae is notìficas",
+  "mute_modal.hide_options": "Cua is optziones",
+  "mute_modal.indefinite": "Fintzas a cando no apo a torrare a ativare is notìficas",
+  "mute_modal.show_options": "Ammustra is optziones",
+  "mute_modal.they_can_mention_and_follow": "Ti podent mentovare e sighire, però no ddos as a bìdere.",
+  "mute_modal.they_wont_know": "No ant a ischire chi ddos as postu a sa muda.",
+  "mute_modal.title": "Boles pònnere a custu contu a sa muda?",
+  "mute_modal.you_wont_see_mentions": "No as a bìdere is publicatziones chi mèntovent a custa persone.",
+  "mute_modal.you_wont_see_posts": "At a pòdere bìdere is publicatziones tuas, però tue no as a bìdere cussas suas.",
   "navigation_bar.about": "Informatziones",
+  "navigation_bar.administration": "Amministratzione",
+  "navigation_bar.advanced_interface": "Aberi s'interfache web avantzada",
   "navigation_bar.blocks": "Persones blocadas",
   "navigation_bar.bookmarks": "Sinnalibros",
   "navigation_bar.community_timeline": "Lìnia de tempus locale",
@@ -380,10 +434,13 @@
   "navigation_bar.favourites": "Preferidos",
   "navigation_bar.filters": "Faeddos a sa muda",
   "navigation_bar.follow_requests": "Rechestas de sighidura",
+  "navigation_bar.followed_tags": "Etichetas sighidas",
   "navigation_bar.follows_and_followers": "Gente chi sighis e sighiduras",
   "navigation_bar.lists": "Listas",
   "navigation_bar.logout": "Essi",
+  "navigation_bar.moderation": "Moderatzione",
   "navigation_bar.mutes": "Persones a sa muda",
+  "navigation_bar.opened_in_classic_interface": "Publicatziones, contos e àteras pàginas ispetzìficas sunt abertas in manera predefinida in s'interfache web clàssica.",
   "navigation_bar.personal": "Informatziones personales",
   "navigation_bar.pins": "Publicatziones apicadas",
   "navigation_bar.preferences": "Preferèntzias",
@@ -391,10 +448,24 @@
   "navigation_bar.search": "Chirca",
   "navigation_bar.security": "Seguresa",
   "not_signed_in_indicator.not_signed_in": "Ti depes identificare pro atzèdere a custa resursa.",
+  "notification.admin.report": "{name} at sinnaladu a {target}",
+  "notification.admin.report_account": "{name} at sinnaladu {count, plural, one {una publicatzione} other {# publicatziones}} dae {target} pro {category}",
+  "notification.admin.report_account_other": "{name} at sinnaladu {count, plural, one {una publicatzione} other {# publicatziones}} dae {target}",
+  "notification.admin.report_statuses": "{name} at sinnaladu a {target} pro {category}",
+  "notification.admin.report_statuses_other": "{name} at sinnaladu a {target}",
   "notification.admin.sign_up": "{name} at aderidu",
+  "notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {un'àtera persone} other {àteras # persones}} si sunt registradas",
   "notification.favourite": "{name} at marcadu comente a preferidu s'istadu tuo",
+  "notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {un'àtera persone} other {àteras # persones}}</a> ant marcadu sa publicatzione tua comente preferida",
   "notification.follow": "{name} ti sighit",
+  "notification.follow.name_and_others": "{name} e {count, plural, one {un'àtera persone} other {àteras # persones}} ti sighint",
   "notification.follow_request": "{name} at dimandadu de ti sighire",
+  "notification.follow_request.name_and_others": "{name} e {count, plural, one {un'àtera persone} other {àteras # persones}} ant pedidu de ti sighire",
+  "notification.label.mention": "Mèntovu",
+  "notification.label.private_mention": "Mèntovu privadu",
+  "notification.label.private_reply": "Risposta privada",
+  "notification.label.reply": "Risposta",
+  "notification.mention": "Mèntovu",
   "notification.moderation-warning.learn_more": "Àteras informatziones",
   "notification.moderation_warning": "T'ant imbiadu un'avisu de moderatzione",
   "notification.moderation_warning.action_delete_statuses": "Unas cantas de is publicatziones tuas sunt istadas cantzelladas.",
@@ -407,12 +478,30 @@
   "notification.own_poll": "Sondàgiu acabbadu",
   "notification.poll": "Unu sondàgiu in su chi as votadu est acabbadu",
   "notification.reblog": "{name} at cumpartzidu sa publicatzione tua",
+  "notification.reblog.name_and_others_with_link": "{name} e <a>{count, plural, one {un'àtera persone} other {àteras # persones}}</a> ant potentziadu sa publicatzione tua",
   "notification.relationships_severance_event": "Connessiones pèrdidas cun {name}",
+  "notification.relationships_severance_event.account_suspension": "S'amministratzione de {from} at suspèndidu a {target}; custu bolet nàrrere chi non podes prus retzire atualizatziones dae in cue o interagire cun cussos contos.",
+  "notification.relationships_severance_event.domain_block": "S'amministratzione de {from} at blocadu a {target}, incluende {followersCount} sighiduras tuas e {followingCount, plural, one {un'àteru contu} other {àteros # contos}} chi sighis.",
   "notification.relationships_severance_event.learn_more": "Àteras informatziones",
+  "notification.relationships_severance_event.user_domain_block": "As blocadu a {target}, bogadu a {followersCount} contos chi ti sighint e {followingCount, plural, one {un'àteru contu} other {àteros # contos}} chi sighis.",
   "notification.status": "{name} at publicadu cosa",
   "notification.update": "{name} at modificadu una publicatzione",
   "notification_requests.accept": "Atzeta",
+  "notification_requests.accept_multiple": "{count, plural, one {Atzeta una rechesta…} other {Atzeta # rechestas…}}",
+  "notification_requests.confirm_accept_multiple.button": "{count, plural, one {Atzeta sa rechesta} other {Atzeta is rechestas}}",
+  "notification_requests.confirm_accept_multiple.message": "Ses atzetende {count, plural, one {una rechesta de notìfica} other {# rechestas de notìfica}}. Seguru chi boles sighire?",
+  "notification_requests.confirm_accept_multiple.title": "Boles atzetare is rechestas de notìfica?",
+  "notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Iscarta sa rechesta} other {Iscarta is rechestas}}",
+  "notification_requests.confirm_dismiss_multiple.message": "Ses acanta de iscartare {count, plural, one {una rechesta de notìfica} other {# rechestas de notìfica}}. No nche {count, plural, one {} other {}} as a pòdere prus atzèdere in manera sèmplitze. Seguru chi boles sighire?",
+  "notification_requests.confirm_dismiss_multiple.title": "Boles iscartare is rechestas de notìfica?",
   "notification_requests.dismiss": "Iscarta",
+  "notification_requests.dismiss_multiple": "{count, plural, one {Iscarta una rechesta…} other {Iscarta # rechestas…}}",
+  "notification_requests.edit_selection": "Modifica",
+  "notification_requests.exit_selection": "Fatu",
+  "notification_requests.explainer_for_limited_account": "Is notìficas de custu contu sunt istadas filtradas, ca su contu est istadu limitadu dae sa moderatzione.",
+  "notification_requests.explainer_for_limited_remote_account": "Is notìficas de custu contu sunt istadas filtradas, ca su contu o su serbidore suo est istadu limitadu dae sa moderatzione.",
+  "notification_requests.maximize": "Ismànnia",
+  "notification_requests.minimize_banner": "Mìnima su bànner de notìficas filtradas",
   "notification_requests.notifications_from": "Notìficas dae {name}",
   "notification_requests.title": "Notìficas filtradas",
   "notifications.clear": "Lìmpia notìficas",
@@ -472,6 +561,11 @@
   "poll_button.add_poll": "Agiunghe unu sondàgiu",
   "poll_button.remove_poll": "Cantzella su sondàgiu",
   "privacy.change": "Modìfica s'istadu de riservadesa",
+  "privacy.direct.long": "Totu is utentes mentovados in sa publicatzione",
+  "privacy.direct.short": "Persones ispetzìficas",
+  "privacy.private.long": "Isceti chie ti sighit",
+  "privacy.private.short": "Sighiduras",
+  "privacy.public.long": "Cale si siat persone a intro o a foras de Mastodon",
   "privacy.public.short": "Pùblicu",
   "privacy_policy.last_updated": "Ùrtima atualizatzione: {date}",
   "privacy_policy.title": "Polìtica de riservadesa",
@@ -497,34 +591,64 @@
   "report.categories.legal": "Giurìdicu",
   "report.categories.other": "Àteru",
   "report.categories.spam": "Àliga",
+  "report.category.subtitle": "Sèbera sa currispondèntzia prus arta",
+  "report.category.title": "Nara·nos ite sutzedet cun custu {type}",
   "report.category.title_account": "profilu",
   "report.category.title_status": "publicatzione",
   "report.close": "Fatu",
+  "report.comment.title": "Nch'at àteru chi depamus ischire?",
   "report.forward": "Torra a imbiare a {target}",
   "report.forward_hint": "Custu contu est de un'àteru serbidore. Ddi boles imbiare puru una còpia anònima de custu informe?",
   "report.mute": "A sa muda",
+  "report.mute_explanation": "No as a bìdere is publicatziones suas. Ti podet ancora sighire e bìdere is publicatziones, ma no at a ischire chi dd'as postu a sa muda.",
   "report.next": "Imbeniente",
   "report.placeholder": "Cummentos additzionales",
   "report.reasons.dislike": "Non mi praghet",
   "report.reasons.dislike_description": "Est una cosa chi non boles bìdere",
   "report.reasons.legal": "Illegale",
   "report.reasons.other": "Un'àtera cosa",
+  "report.reasons.other_description": "Su problema non currispondet a is àteras categorias",
   "report.reasons.spam": "Est àliga",
+  "report.rules.subtitle": "Seletziona totu is chi àplichent",
+  "report.statuses.subtitle": "Seletziona totu is chi àplichent",
   "report.submit": "Imbia",
   "report.target": "Informende de {target}",
+  "report.thanks.title": "Non boles bìdere custu?",
+  "report.thanks.title_actionable": "Gràtzias de sa sinnalatzione, dd'amus a averiguare.",
   "report.unfollow": "Non sigas prus a @{name}",
+  "report.unfollow_explanation": "Ses sighende custu contu. Si non boles bìdere is publicatziones suas in sa pàgina printzipale tua, no ddu sigas prus.",
   "report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached",
   "report_notification.categories.legal": "Giurìdicu",
   "report_notification.categories.legal_sentence": "cuntenutu illegale",
   "report_notification.categories.other": "Àteru",
   "report_notification.categories.other_sentence": "àteru",
   "report_notification.categories.spam": "Àliga",
+  "report_notification.categories.spam_sentence": "àliga",
+  "report_notification.open": "Aberi una sinnalatzione",
+  "search.no_recent_searches": "Nissuna chirca reghente",
   "search.placeholder": "Chirca",
+  "search.quick_action.account_search": "Profilos chi currispondent cun {x}",
+  "search.quick_action.go_to_account": "Bae a su profilu {x}",
+  "search.quick_action.go_to_hashtag": "Bae a s'eticheta {x}",
+  "search.quick_action.open_url": "Aberi s'URL in Mastodon",
+  "search.quick_action.status_search": "Publicatziones chi currispondent cun {x}",
+  "search.search_or_paste": "Chirca o incolla un'URL",
+  "search_popout.full_text_search_disabled_message": "No a disponimentu a {domain}.",
+  "search_popout.full_text_search_logged_out_message": "Isceti a disponimentu cun sa sessione aberta.",
+  "search_popout.language_code": "Còdighe de limba ISO",
+  "search_popout.options": "Optziones de chirca",
+  "search_popout.quick_actions": "Atziones lestras",
+  "search_popout.recent": "Chircas reghentes",
+  "search_popout.specific_date": "data ispetzìfica",
   "search_popout.user": "utente",
   "search_results.accounts": "Profilos",
   "search_results.all": "Totus",
   "search_results.hashtags": "Etichetas",
+  "search_results.nothing_found": "Impossìbile agatare currispondèntzias pro custos tèrmines de chirca",
+  "search_results.see_all": "Bide totu",
   "search_results.statuses": "Publicatziones",
+  "search_results.title": "Chirca {q}",
+  "server_banner.about_active_users": "Gente chi at impreadu custu serbidore is ùrtimas 30 dies (Utentes cun Atividade a su Mese)",
   "server_banner.active_users": "utentes ativos",
   "server_banner.administered_by": "Amministradu dae:",
   "server_banner.server_stats": "Istatìsticas de su serbidore:",
diff --git a/app/javascript/mastodon/locales/tr.json b/app/javascript/mastodon/locales/tr.json
index 2d9c7321d..36c0b68f0 100644
--- a/app/javascript/mastodon/locales/tr.json
+++ b/app/javascript/mastodon/locales/tr.json
@@ -124,7 +124,7 @@
   "column.direct": "Özel değinmeler",
   "column.directory": "Profillere göz at",
   "column.domain_blocks": "Engellenen alan adları",
-  "column.favourites": "Favorilerin",
+  "column.favourites": "Gözdelerin",
   "column.firehose": "Anlık Akışlar",
   "column.follow_requests": "Takip istekleri",
   "column.home": "Anasayfa",
@@ -812,7 +812,7 @@
   "status.reblogged_by": "{name} yeniden paylaştı",
   "status.reblogs": "{count, plural, one {yeniden paylaşım} other {yeniden paylaşım}}",
   "status.reblogs.empty": "Henüz hiç kimse bu gönderiyi yeniden paylaşmadı. Herhangi bir kullanıcı yeniden paylaştığında burada görüntülenecek.",
-  "status.redraft": "Sil,Düzenle ve Yeniden paylaş",
+  "status.redraft": "Sil,Düzenle ve yeniden-paylaş",
   "status.remove_bookmark": "Yer işaretini kaldır",
   "status.replied_in_thread": "Akışta yanıtlandı",
   "status.replied_to": "{name} kullanıcısına yanıt verdi",
diff --git a/app/javascript/mastodon/locales/uk.json b/app/javascript/mastodon/locales/uk.json
index 1753d0beb..a48b071f5 100644
--- a/app/javascript/mastodon/locales/uk.json
+++ b/app/javascript/mastodon/locales/uk.json
@@ -852,6 +852,11 @@
   "upload_error.poll": "Не можна завантажувати файли до опитувань.",
   "upload_form.audio_description": "Опишіть для людей із вадами слуху",
   "upload_form.description": "Опишіть для людей з вадами зору",
+  "upload_form.drag_and_drop.instructions": "Щоб вибрати медіавкладення, натисніть пробіл або Enter. Під час перетягування, використайте клавіші зі стрілками для переміщення вкладення в будь-якому напрямку. Натисніть пробіл або Enter знову, щоб залишити медіавкладення в новому положенні, або натисніть клавішу Escape, щоб скасувати.",
+  "upload_form.drag_and_drop.on_drag_cancel": "Перетягування скасовано. Медіавкладення {item} прибрано.",
+  "upload_form.drag_and_drop.on_drag_end": "Медіавкладення {item} прибрано.",
+  "upload_form.drag_and_drop.on_drag_over": "Медіавкладення {item} переміщено.",
+  "upload_form.drag_and_drop.on_drag_start": "Медіавкладення {item} вибрано.",
   "upload_form.edit": "Змінити",
   "upload_form.thumbnail": "Змінити мініатюру",
   "upload_form.video_description": "Опишіть для людей із вадами слуху або зору",
diff --git a/config/locales/de.yml b/config/locales/de.yml
index c82e65282..7a8469df6 100644
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -79,7 +79,7 @@ de:
       invite_request_text: Begründung für das Beitreten
       invited_by: Eingeladen von
       ip: IP-Adresse
-      joined: Beigetreten
+      joined: Mitglied seit
       location:
         all: Alle
         local: Lokal
diff --git a/config/locales/sc.yml b/config/locales/sc.yml
index 306e670b7..9ab62cea7 100644
--- a/config/locales/sc.yml
+++ b/config/locales/sc.yml
@@ -30,17 +30,25 @@ sc:
       created_msg: As creadu una nota de moderatzione.
       destroyed_msg: As cantzelladu una nota de moderatzione.
     accounts:
+      add_email_domain_block: Bloca domìniu de posta eletrònica
       approve: Aprova
       approved_msg: Sa dimanda de registru de %{username} est istada aprovada
       are_you_sure: Seguru?
       avatar: Immàgine de profilu
       by_domain: Domìniu
       change_email:
+        changed_msg: Indiritzu eletrònicu de su contu modificadu!
         current_email: Indiritzu eletrònicu atuale
         label: Muda s'indiritzu eletrònicu
         new_email: Indiritzu eletrònicu nou
         submit: Muda s'indiritzu eletrònicu
         title: Muda s'indiritzu eletrònicu de %{username}
+      change_role:
+        changed_msg: Ruolu modificadu.
+        edit_roles: Gesti is ruolos de utente
+        label: Modifica su ruolu
+        no_role: Nissunu ruolu
+        title: Modifica su ruolu de %{username}
       confirm: Cunfirma
       confirmed: Cunfirmadu
       confirming: Cunfirmende
@@ -91,6 +99,7 @@ sc:
       most_recent_ip: IP prus reghente
       no_account_selected: Perunu contu est istadu mudadu, dae chi non nd'as seletzionadu
       no_limits_imposed: Sena lìmites
+      no_role_assigned: Nissunu ruolu assignadu
       not_subscribed: Sena sutiscritzione
       pending: De revisionare
       perform_full_suspension: Suspèndidu
@@ -108,12 +117,19 @@ sc:
       removed_header_msg: S'immàgine de intestatzione de %{username} est istada bogada
       resend_confirmation:
         already_confirmed: Custa persone est giai cunfirmada
+        send: Torra a imbiare su ligòngiu de cunfirmatzione
+        success: Ligòngiu de cunfirmatzione imbiadu.
       reset: Reseta
       reset_password: Reseta sa crae
       resubscribe: Torra a sutascrìere
+      role: Ruolu
       search: Chirca
+      search_same_email_domain: Àteras persones cun su pròpiu domìniu de posta
       search_same_ip: Àteras persones cun sa pròpiu IP
       security: Seguresa
+      security_measures:
+        only_password: Crae isceti
+        password_and_2fa: Crae e 2FA
       sensitive: Sensìbile
       sensitized: Marcadu comente sensìbile
       shared_inbox_url: URL de intrada cumpartzida
@@ -129,6 +145,8 @@ sc:
       suspension_irreversible: Is datos de custu contu sunt istados cantzellados in manera irreversìbile. Podes bogare sa suspensione a su contu pro chi si potzat impreare, ma no at a recuperare datu perunu de is chi teniat in antis.
       suspension_reversible_hint_html: Su contu est istadu suspèndidu, e is datos ant a èssere cantzelladu de su totu su %{date}. Finas a tando, su contu si podet ripristinare sena efetu malu perunu. Si boles cantzellare totu is datos de su contu immediatamente ddu podes fàghere inoghe in bassu.
       title: Contos
+      unblock_email: Isbloca s'indiritzu de posta eletrònica
+      unblocked_email_msg: Posta eletrònica de %{username} isblocada
       unconfirmed_email: Posta eletrònica sena cunfirmare
       undo_sensitized: Boga sa marcadura comente sensìbile
       undo_silenced: Non pòngias a sa muda
@@ -143,21 +161,31 @@ sc:
       whitelisted: Federatzione permìtida
     action_logs:
       action_types:
+        approve_user: Aprova s'utente
         assigned_to_self_report: Assigna s'informe
+        change_email_user: Muda s'indiritzu eletrònicu pro s'utente
+        change_role_user: Muda su ruolu de s'utente
         confirm_user: Cunfirma s'utente
         create_account_warning: Crea un'avisu
         create_announcement: Crea un'annùntziu
+        create_canonical_email_block: Crea unu blocu de posta eletrònica
         create_custom_emoji: Crea un'emoji personalizadu
         create_domain_allow: Crea unu domìniu permìtidu
         create_domain_block: Crea unu blocu de domìniu
+        create_email_domain_block: Crea unu blocu de domìniu de posta eletrònica
         create_ip_block: Crea una règula IP
+        create_unavailable_domain: Crea unu domìniu no a disponimentu
+        create_user_role: Crea unu ruolu
         demote_user: Degrada s'utente
         destroy_announcement: Cantzella s'annùntziu
+        destroy_canonical_email_block: Cantzella su blocu de posta eletrònica
         destroy_custom_emoji: Cantzella s'emoji personalizadu
         destroy_domain_allow: Cantzella su domìniu permìtidu
         destroy_domain_block: Cantzella su blocu de domìniu
+        destroy_email_domain_block: Cantzella su blocu de domìniu de posta eletrònica
         destroy_ip_block: Cantzella sa règula IP
         destroy_status: Cantzella s'istadu
+        destroy_unavailable_domain: Cantzella su domìniu no a disponimentu
         disable_2fa_user: Disativa 2FA
         disable_custom_emoji: Disativa s'emoji personalizadu
         disable_user: Disativa utente
@@ -165,23 +193,33 @@ sc:
         enable_user: Ativa utente
         memorialize_account: Torra in unu contu de regordu
         promote_user: Promove utente
+        reject_user: Refuda s'utente
         remove_avatar_user: Cantzella immàgine de profilu
         reopen_report: Torra a abèrrere s'informe
+        resend_user: Torra a imbiare messàgiu eletrònicu de cunfirmatzione
         reset_password_user: Reseta sa crae
         resolve_report: Isorve s'informe
         sensitive_account: Marca is cuntenutos multimediales in su contu tuo comente sensìbiles
         silence_account: Pone custu contu a sa muda
         suspend_account: Suspende custu contu
         unassigned_report: Boga s'assignatzione de custu informe
+        unblock_email_account: Isbloca s'indiritzu de posta eletrònica
         unsensitive_account: Boga sa marcadura "sensìbiles" a is elementos multimediales in su contu tuo
         unsilence_account: Boga custu contu de is contos a sa muda
         unsuspend_account: Boga custu contu de is contos suspèndidos
         update_announcement: Atualiza s'annùntziu
         update_custom_emoji: Atualiza s'emoji personalizadu
         update_domain_block: Atualiza blocu de domìniu
+        update_ip_block: Atualiza sa règula IP
+        update_report: Atualiza s'informe
         update_status: Atualiza s'istadu
+        update_user_role: Atualiza su ruolu
       actions:
+        approve_user_html: "%{name} at aprovadu su registru de %{target}"
         assigned_to_self_report_html: "%{name} s'est auto-assignadu s'informe %{target}"
+        change_email_user_html: "%{name} at mudadu s'indiritzu de posta eletrònica de s'utente %{target}"
+        change_role_user_html: "%{name} at mudadu su ruolu de %{target}"
+        confirm_user_html: "%{name} at cunfirmadu s'indiritzu de posta eletrònica de s'utente %{target}"
         create_account_warning_html: "%{name} at imbiadu un'avisu a %{target}"
         create_announcement_html: "%{name} at creadu un'annùntziu nou %{target}"
         create_custom_emoji_html: "%{name} at carrigadu un'emoji nou %{target}"
@@ -291,6 +329,8 @@ sc:
       confirm_suspension:
         cancel: Annulla
         confirm: Suspende
+        remove_all_data: Custu at a cantzellare totu su cuntenutu, elementos multimediales e datos de profilu pro is contos de custu domìniu dae su serbidore tuo.
+        stop_communication: Su serbidore tuo at a tzessare sa comunicatzione cun custos serbidores.
         title: Cunfirma su blocu de domìniu de %{domain}
       created_msg: Protzessende su blocu de domìniu
       destroyed_msg: Su blocu de domìniu est istadu iscontzadu
@@ -361,9 +401,12 @@ sc:
       dashboard:
         instance_accounts_dimension: Contos prus sighidos
         instance_accounts_measure: contos sarvados
+        instance_languages_dimension: Limbas printzipales
         instance_reports_measure: informes a subra de àtere
+        instance_statuses_measure: publicatziones sarvadas
       delivery:
         all: Totus
+        unavailable: No a disponimentu
       delivery_available: Sa cunsigna est a disponimentu
       empty: Perunu domìniu agatadu.
       known_accounts:
diff --git a/config/locales/tr.yml b/config/locales/tr.yml
index 738263191..f1e098370 100644
--- a/config/locales/tr.yml
+++ b/config/locales/tr.yml
@@ -1,7 +1,7 @@
 ---
 tr:
   about:
-    about_mastodon_html: Mastodon <em>ücretsiz ve açık kaynaklı</em> bir sosyal ağdır. <em>Merkezileştirilmemiş</em> yapısı sayesinde diğer ticari sosyal platformların aksine iletişimininizin tek bir firmada tutulmasının/yönetilmesinin önüne geçer. Güvendiğiniz bir sunucuyu seçerek oradaki kişilerle etkileşimde bulunabilirsiniz. Herkes kendi Mastodon sunucusunu kurabilir ve sorunsuz bir şekilde Mastodon <em>sosyal ağına</em> dahil edebilir.
+    about_mastodon_html: Mastodon <em>ücretsiz ve açık kaynaklı</em> bir sosyal ağdır. <em>Merkezi olmayan</em> yapısı sayesinde diğer ticari sosyal platformların aksine iletişimininizin tek bir firmada tutulmasının/yönetilmesinin önüne geçer. Güvendiğiniz bir sunucuyu seçerek oradaki kişilerle etkileşimde bulunabilirsiniz. Herkes kendi Mastodon sunucusunu kurabilir ve sorunsuz bir şekilde Mastodon <em>sosyal ağına</em> dahil edebilir!
     contact_missing: Ayarlanmadı
     contact_unavailable: Bulunamadı
     hosted_on: Mastodon %{domain} üzerinde barındırılıyor

From 1db91ab8d9b26ba0103bdc51bc9c79411a3cf6dd Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Thu, 3 Oct 2024 11:34:04 +0200
Subject: [PATCH 50/70] Update dependency typescript to v5.6.2 (#32165)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 yarn.lock | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/yarn.lock b/yarn.lock
index 0a7f89990..9aeb2b02a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -17382,22 +17382,22 @@ __metadata:
   linkType: hard
 
 "typescript@npm:5, typescript@npm:^5.0.4":
-  version: 5.5.4
-  resolution: "typescript@npm:5.5.4"
+  version: 5.6.2
+  resolution: "typescript@npm:5.6.2"
   bin:
     tsc: bin/tsc
     tsserver: bin/tsserver
-  checksum: 10c0/422be60f89e661eab29ac488c974b6cc0a660fb2228003b297c3d10c32c90f3bcffc1009b43876a082515a3c376b1eefcce823d6e78982e6878408b9a923199c
+  checksum: 10c0/3ed8297a8c7c56b7fec282532503d1ac795239d06e7c4966b42d4330c6cf433a170b53bcf93a130a7f14ccc5235de5560df4f1045eb7f3550b46ebed16d3c5e5
   languageName: node
   linkType: hard
 
 "typescript@patch:typescript@npm%3A5#optional!builtin<compat/typescript>, typescript@patch:typescript@npm%3A^5.0.4#optional!builtin<compat/typescript>":
-  version: 5.5.4
-  resolution: "typescript@patch:typescript@npm%3A5.5.4#optional!builtin<compat/typescript>::version=5.5.4&hash=379a07"
+  version: 5.6.2
+  resolution: "typescript@patch:typescript@npm%3A5.6.2#optional!builtin<compat/typescript>::version=5.6.2&hash=8c6c40"
   bin:
     tsc: bin/tsc
     tsserver: bin/tsserver
-  checksum: 10c0/73409d7b9196a5a1217b3aaad929bf76294d3ce7d6e9766dd880ece296ee91cf7d7db6b16c6c6c630ee5096eccde726c0ef17c7dfa52b01a243e57ae1f09ef07
+  checksum: 10c0/94eb47e130d3edd964b76da85975601dcb3604b0c848a36f63ac448d0104e93819d94c8bdf6b07c00120f2ce9c05256b8b6092d23cf5cf1c6fa911159e4d572f
   languageName: node
   linkType: hard
 

From 52afa94f1c277f7387c5bb3d1eb85fb555126a2a Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Thu, 3 Oct 2024 08:13:54 -0400
Subject: [PATCH 51/70] Use `print_table` to wrap storage output CLI (#32230)

---
 lib/mastodon/cli/media.rb | 39 +++++++++++++++++++--------------------
 1 file changed, 19 insertions(+), 20 deletions(-)

diff --git a/lib/mastodon/cli/media.rb b/lib/mastodon/cli/media.rb
index 70c11ee1a..37f110d89 100644
--- a/lib/mastodon/cli/media.rb
+++ b/lib/mastodon/cli/media.rb
@@ -278,14 +278,10 @@ module Mastodon::CLI
 
     desc 'usage', 'Calculate disk space consumed by Mastodon'
     def usage
-      say("Attachments:\t#{number_to_human_size(media_attachment_storage_size)} (#{number_to_human_size(local_media_attachment_storage_size)} local)")
-      say("Custom emoji:\t#{number_to_human_size(CustomEmoji.sum(:image_file_size))} (#{number_to_human_size(CustomEmoji.local.sum(:image_file_size))} local)")
-      say("Preview cards:\t#{number_to_human_size(PreviewCard.sum(:image_file_size))}")
-      say("Avatars:\t#{number_to_human_size(Account.sum(:avatar_file_size))} (#{number_to_human_size(Account.local.sum(:avatar_file_size))} local)")
-      say("Headers:\t#{number_to_human_size(Account.sum(:header_file_size))} (#{number_to_human_size(Account.local.sum(:header_file_size))} local)")
-      say("Backups:\t#{number_to_human_size(Backup.sum(:dump_file_size))}")
-      say("Imports:\t#{number_to_human_size(Import.sum(:data_file_size))}")
-      say("Settings:\t#{number_to_human_size(SiteUpload.sum(:file_file_size))}")
+      print_table [
+        %w(Object Total Local),
+        *object_storage_summary,
+      ]
     end
 
     desc 'lookup URL', 'Lookup where media is displayed by passing a media URL'
@@ -318,20 +314,23 @@ module Mastodon::CLI
 
     private
 
-    def media_attachment_storage_size
-      MediaAttachment.sum(file_and_thumbnail_size_sql)
+    def object_storage_summary
+      [
+        [:attachments, MediaAttachment.sum(combined_media_sum), MediaAttachment.where(account: Account.local).sum(combined_media_sum)],
+        [:custom_emoji, CustomEmoji.sum(:image_file_size), CustomEmoji.local.sum(:image_file_size)],
+        [:avatars, Account.sum(:avatar_file_size), Account.local.sum(:avatar_file_size)],
+        [:headers, Account.sum(:header_file_size), Account.local.sum(:header_file_size)],
+        [:preview_cards, PreviewCard.sum(:image_file_size), nil],
+        [:backups, Backup.sum(:dump_file_size), nil],
+        [:imports, Import.sum(:data_file_size), nil],
+        [:settings, SiteUpload.sum(:file_file_size), nil],
+      ].map { |label, total, local| [label.to_s.titleize, number_to_human_size(total), local.present? ? number_to_human_size(local) : nil] }
     end
 
-    def local_media_attachment_storage_size
-      MediaAttachment.where(account: Account.local).sum(file_and_thumbnail_size_sql)
-    end
-
-    def file_and_thumbnail_size_sql
-      Arel.sql(
-        <<~SQL.squish
-          COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0)
-        SQL
-      )
+    def combined_media_sum
+      Arel.sql(<<~SQL.squish)
+        COALESCE(file_file_size, 0) + COALESCE(thumbnail_file_size, 0)
+      SQL
     end
 
     PRELOAD_MODEL_WHITELIST = %w(

From d95f6f4410c28b2f7b9f736c9477936587bd700b Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Thu, 3 Oct 2024 09:09:58 -0400
Subject: [PATCH 52/70] Extract `ExportSummary` class for account object counts
 (#32227)

---
 .../settings/exports_controller.rb            | 13 ++-
 app/models/export.rb                          | 36 --------
 app/presenters/export_summary.rb              | 70 +++++++++++++++
 app/views/settings/exports/show.html.haml     | 18 ++--
 spec/models/export_spec.rb                    | 71 ---------------
 spec/presenters/export_summary_spec.rb        | 86 +++++++++++++++++++
 6 files changed, 177 insertions(+), 117 deletions(-)
 create mode 100644 app/presenters/export_summary.rb
 create mode 100644 spec/presenters/export_summary_spec.rb

diff --git a/app/controllers/settings/exports_controller.rb b/app/controllers/settings/exports_controller.rb
index 076ed5dad..263d20eae 100644
--- a/app/controllers/settings/exports_controller.rb
+++ b/app/controllers/settings/exports_controller.rb
@@ -9,7 +9,7 @@ class Settings::ExportsController < Settings::BaseController
   skip_before_action :require_functional!
 
   def show
-    @export  = Export.new(current_account)
+    @export_summary = ExportSummary.new(preloaded_account)
     @backups = current_user.backups
   end
 
@@ -25,4 +25,15 @@ class Settings::ExportsController < Settings::BaseController
 
     redirect_to settings_export_path
   end
+
+  private
+
+  def preloaded_account
+    current_account.tap do |account|
+      ActiveRecord::Associations::Preloader.new(
+        records: [account],
+        associations: :account_stat
+      ).call
+    end
+  end
 end
diff --git a/app/models/export.rb b/app/models/export.rb
index 2457dcc15..6ed9f60c7 100644
--- a/app/models/export.rb
+++ b/app/models/export.rb
@@ -55,42 +55,6 @@ class Export
     end
   end
 
-  def total_storage
-    account.media_attachments.sum(:file_file_size)
-  end
-
-  def total_statuses
-    account.statuses_count
-  end
-
-  def total_bookmarks
-    account.bookmarks.count
-  end
-
-  def total_follows
-    account.following_count
-  end
-
-  def total_lists
-    account.owned_lists.count
-  end
-
-  def total_followers
-    account.followers_count
-  end
-
-  def total_blocks
-    account.blocking.count
-  end
-
-  def total_mutes
-    account.muting.count
-  end
-
-  def total_domain_blocks
-    account.domain_blocks.count
-  end
-
   private
 
   def to_csv(accounts)
diff --git a/app/presenters/export_summary.rb b/app/presenters/export_summary.rb
new file mode 100644
index 000000000..8e45aadf6
--- /dev/null
+++ b/app/presenters/export_summary.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+class ExportSummary
+  attr_reader :account, :counts
+
+  delegate(
+    :blocking,
+    :bookmarks,
+    :domain_blocks,
+    :owned_lists,
+    :media_attachments,
+    :muting,
+    to: :account,
+    prefix: true
+  )
+
+  def initialize(account)
+    @account = account
+    @counts = populate_counts
+  end
+
+  def total_blocks
+    counts[:blocks].value
+  end
+
+  def total_bookmarks
+    counts[:bookmarks].value
+  end
+
+  def total_domain_blocks
+    counts[:domain_blocks].value
+  end
+
+  def total_followers
+    account.followers_count
+  end
+
+  def total_follows
+    account.following_count
+  end
+
+  def total_lists
+    counts[:owned_lists].value
+  end
+
+  def total_mutes
+    counts[:muting].value
+  end
+
+  def total_statuses
+    account.statuses_count
+  end
+
+  def total_storage
+    counts[:storage].value
+  end
+
+  private
+
+  def populate_counts
+    {
+      blocks: account_blocking.async_count,
+      bookmarks: account_bookmarks.async_count,
+      domain_blocks: account_domain_blocks.async_count,
+      owned_lists: account_owned_lists.async_count,
+      muting: account_muting.async_count,
+      storage: account_media_attachments.async_sum(:file_file_size),
+    }
+  end
+end
diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml
index 61d55350c..b36e395e3 100644
--- a/app/views/settings/exports/show.html.haml
+++ b/app/views/settings/exports/show.html.haml
@@ -6,39 +6,39 @@
     %tbody
       %tr
         %th= t('exports.storage')
-        %td= number_to_human_size @export.total_storage
+        %td= number_to_human_size @export_summary.total_storage
         %td
       %tr
         %th= t('accounts.posts_tab_heading')
-        %td= number_with_delimiter @export.total_statuses
+        %td= number_with_delimiter @export_summary.total_statuses
         %td
       %tr
         %th= t('admin.accounts.follows')
-        %td= number_with_delimiter @export.total_follows
+        %td= number_with_delimiter @export_summary.total_follows
         %td= table_link_to 'download', t('exports.csv'), settings_exports_follows_path(format: :csv)
       %tr
         %th= t('exports.lists')
-        %td= number_with_delimiter @export.total_lists
+        %td= number_with_delimiter @export_summary.total_lists
         %td= table_link_to 'download', t('exports.csv'), settings_exports_lists_path(format: :csv)
       %tr
         %th= t('admin.accounts.followers')
-        %td= number_with_delimiter @export.total_followers
+        %td= number_with_delimiter @export_summary.total_followers
         %td
       %tr
         %th= t('exports.mutes')
-        %td= number_with_delimiter @export.total_mutes
+        %td= number_with_delimiter @export_summary.total_mutes
         %td= table_link_to 'download', t('exports.csv'), settings_exports_mutes_path(format: :csv)
       %tr
         %th= t('exports.blocks')
-        %td= number_with_delimiter @export.total_blocks
+        %td= number_with_delimiter @export_summary.total_blocks
         %td= table_link_to 'download', t('exports.csv'), settings_exports_blocks_path(format: :csv)
       %tr
         %th= t('exports.domain_blocks')
-        %td= number_with_delimiter @export.total_domain_blocks
+        %td= number_with_delimiter @export_summary.total_domain_blocks
         %td= table_link_to 'download', t('exports.csv'), settings_exports_domain_blocks_path(format: :csv)
       %tr
         %th= t('exports.bookmarks')
-        %td= number_with_delimiter @export.total_bookmarks
+        %td= number_with_delimiter @export_summary.total_bookmarks
         %td= table_link_to 'download', t('exports.csv'), settings_exports_bookmarks_path(format: :csv)
 
 %hr.spacer/
diff --git a/spec/models/export_spec.rb b/spec/models/export_spec.rb
index 48e78830d..81aaf8858 100644
--- a/spec/models/export_spec.rb
+++ b/spec/models/export_spec.rb
@@ -103,75 +103,4 @@ RSpec.describe Export do
         )
     end
   end
-
-  describe '#total_storage' do
-    it 'returns the total size of the media attachments' do
-      media_attachment = Fabricate(:media_attachment, account: account)
-      expect(subject.total_storage).to eq media_attachment.file_file_size || 0
-    end
-  end
-
-  describe '#total_statuses' do
-    before { Fabricate.times(2, :status, account: account) }
-
-    it 'returns the total number of statuses' do
-      expect(subject.total_statuses).to eq(2)
-    end
-  end
-
-  describe '#total_bookmarks' do
-    before { Fabricate.times(2, :bookmark, account: account) }
-
-    it 'returns the total number of bookmarks' do
-      expect(subject.total_bookmarks).to eq(2)
-    end
-  end
-
-  describe '#total_follows' do
-    before { target_accounts.each { |target_account| account.follow!(target_account) } }
-
-    it 'returns the total number of the followed accounts' do
-      expect(subject.total_follows).to eq(2)
-    end
-  end
-
-  describe '#total_lists' do
-    before { Fabricate.times(2, :list, account: account) }
-
-    it 'returns the total number of lists' do
-      expect(subject.total_lists).to eq(2)
-    end
-  end
-
-  describe '#total_followers' do
-    before { target_accounts.each { |target_account| target_account.follow!(account) } }
-
-    it 'returns the total number of the follower accounts' do
-      expect(subject.total_followers).to eq(2)
-    end
-  end
-
-  describe '#total_blocks' do
-    before { target_accounts.each { |target_account| account.block!(target_account) } }
-
-    it 'returns the total number of the blocked accounts' do
-      expect(subject.total_blocks).to eq(2)
-    end
-  end
-
-  describe '#total_mutes' do
-    before { target_accounts.each { |target_account| account.mute!(target_account) } }
-
-    it 'returns the total number of the muted accounts' do
-      expect(subject.total_mutes).to eq(2)
-    end
-  end
-
-  describe '#total_domain_blocks' do
-    before { Fabricate.times(2, :account_domain_block, account: account) }
-
-    it 'returns the total number of account domain blocks' do
-      expect(subject.total_domain_blocks).to eq(2)
-    end
-  end
 end
diff --git a/spec/presenters/export_summary_spec.rb b/spec/presenters/export_summary_spec.rb
new file mode 100644
index 000000000..0ed46c857
--- /dev/null
+++ b/spec/presenters/export_summary_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+RSpec.describe ExportSummary do
+  subject { described_class.new(account) }
+
+  let(:account) { Fabricate(:account) }
+  let(:target_accounts) do
+    [
+      Fabricate(:account),
+      Fabricate(:account, username: 'one', domain: 'local.host'),
+    ]
+  end
+
+  describe '#total_storage' do
+    it 'returns the total size of the media attachments' do
+      media_attachment = Fabricate(:media_attachment, account: account)
+      expect(subject.total_storage).to eq media_attachment.file_file_size || 0
+    end
+  end
+
+  describe '#total_statuses' do
+    before { Fabricate.times(2, :status, account: account) }
+
+    it 'returns the total number of statuses' do
+      expect(subject.total_statuses).to eq(2)
+    end
+  end
+
+  describe '#total_bookmarks' do
+    before { Fabricate.times(2, :bookmark, account: account) }
+
+    it 'returns the total number of bookmarks' do
+      expect(subject.total_bookmarks).to eq(2)
+    end
+  end
+
+  describe '#total_follows' do
+    before { target_accounts.each { |target_account| account.follow!(target_account) } }
+
+    it 'returns the total number of the followed accounts' do
+      expect(subject.total_follows).to eq(2)
+    end
+  end
+
+  describe '#total_lists' do
+    before { Fabricate.times(2, :list, account: account) }
+
+    it 'returns the total number of lists' do
+      expect(subject.total_lists).to eq(2)
+    end
+  end
+
+  describe '#total_followers' do
+    before { target_accounts.each { |target_account| target_account.follow!(account) } }
+
+    it 'returns the total number of the follower accounts' do
+      expect(subject.total_followers).to eq(2)
+    end
+  end
+
+  describe '#total_blocks' do
+    before { target_accounts.each { |target_account| account.block!(target_account) } }
+
+    it 'returns the total number of the blocked accounts' do
+      expect(subject.total_blocks).to eq(2)
+    end
+  end
+
+  describe '#total_mutes' do
+    before { target_accounts.each { |target_account| account.mute!(target_account) } }
+
+    it 'returns the total number of the muted accounts' do
+      expect(subject.total_mutes).to eq(2)
+    end
+  end
+
+  describe '#total_domain_blocks' do
+    before { Fabricate.times(2, :account_domain_block, account: account) }
+
+    it 'returns the total number of account domain blocks' do
+      expect(subject.total_domain_blocks).to eq(2)
+    end
+  end
+end

From cc8d723e7111b7e2e3d6685e0632c38813d27df0 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Thu, 3 Oct 2024 09:10:27 -0400
Subject: [PATCH 53/70] Register an XML encoder for response tests (#32220)

---
 spec/requests/well_known/host_meta_spec.rb | 81 +++++++++++++---------
 spec/support/response_encoders.rb          |  4 ++
 2 files changed, 53 insertions(+), 32 deletions(-)
 create mode 100644 spec/support/response_encoders.rb

diff --git a/spec/requests/well_known/host_meta_spec.rb b/spec/requests/well_known/host_meta_spec.rb
index 726911dda..8d8e38f52 100644
--- a/spec/requests/well_known/host_meta_spec.rb
+++ b/spec/requests/well_known/host_meta_spec.rb
@@ -3,45 +3,62 @@
 require 'rails_helper'
 
 RSpec.describe 'The /.well-known/host-meta request' do
-  it 'returns http success with valid XML response' do
-    get '/.well-known/host-meta'
+  context 'without extension format or accept header' do
+    it 'returns http success with expected XML' do
+      get '/.well-known/host-meta'
 
-    expect(response)
-      .to have_http_status(200)
-      .and have_attributes(
-        media_type: 'application/xrd+xml'
-      )
+      expect(response)
+        .to have_http_status(200)
+        .and have_attributes(
+          media_type: 'application/xrd+xml'
+        )
 
-    doc = Nokogiri::XML(response.parsed_body)
-    expect(doc.at_xpath('/xrd:XRD/xrd:Link[@rel="lrdd"]/@template', 'xrd' => 'http://docs.oasis-open.org/ns/xri/xrd-1.0').value)
-      .to eq 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}'
+      expect(xrd_link_template_value)
+        .to eq 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}'
+    end
+
+    def xrd_link_template_value
+      response
+        .parsed_body
+        .at_xpath('/xrd:XRD/xrd:Link[@rel="lrdd"]/@template', 'xrd' => 'http://docs.oasis-open.org/ns/xri/xrd-1.0')
+        .value
+    end
   end
 
-  it 'returns http success with valid JSON response with .json extension' do
-    get '/.well-known/host-meta.json'
+  context 'with a .json format extension' do
+    it 'returns http success with expected JSON' do
+      get '/.well-known/host-meta.json'
 
-    expect(response)
-      .to have_http_status(200)
-      .and have_attributes(
-        media_type: 'application/json'
-      )
-
-    expect(response.parsed_body)
-      .to include(
-        links: [
-          'rel' => 'lrdd',
-          'template' => 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}',
-        ]
-      )
+      expect(response)
+        .to have_http_status(200)
+        .and have_attributes(
+          media_type: 'application/json'
+        )
+      expect(response.parsed_body)
+        .to include(expected_json_template)
+    end
   end
 
-  it 'returns http success with valid JSON response with Accept header' do
-    get '/.well-known/host-meta', headers: { 'Accept' => 'application/json' }
+  context 'with a JSON `Accept` header' do
+    it 'returns http success with expected JSON' do
+      get '/.well-known/host-meta', headers: { 'Accept' => 'application/json' }
 
-    expect(response)
-      .to have_http_status(200)
-      .and have_attributes(
-        media_type: 'application/json'
-      )
+      expect(response)
+        .to have_http_status(200)
+        .and have_attributes(
+          media_type: 'application/json'
+        )
+      expect(response.parsed_body)
+        .to include(expected_json_template)
+    end
+  end
+
+  def expected_json_template
+    {
+      links: [
+        'rel' => 'lrdd',
+        'template' => 'https://cb6e6126.ngrok.io/.well-known/webfinger?resource={uri}',
+      ],
+    }
   end
 end
diff --git a/spec/support/response_encoders.rb b/spec/support/response_encoders.rb
new file mode 100644
index 000000000..548dba713
--- /dev/null
+++ b/spec/support/response_encoders.rb
@@ -0,0 +1,4 @@
+# frozen_string_literal: true
+
+ActionDispatch::IntegrationTest
+  .register_encoder :xml, response_parser: ->(body) { Nokogiri::XML(body) }

From 4c0e44ebbe244b32117ef0e2624ca3e7087c34ad Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Thu, 3 Oct 2024 18:12:15 +0200
Subject: [PATCH 54/70] Fix recently-broken admin interface buttons (#32240)

---
 app/javascript/styles/mastodon/forms.scss     |  4 ----
 app/views/admin/accounts/_buttons.html.haml   | 20 +++++++++----------
 app/views/admin/accounts/show.html.haml       |  2 +-
 app/views/admin/instances/show.html.haml      | 12 +++++------
 app/views/admin/invites/index.html.haml       |  2 +-
 app/views/admin/reports/_actions.html.haml    |  2 +-
 app/views/admin/reports/show.html.haml        |  4 ++--
 app/views/disputes/strikes/show.html.haml     |  4 ++--
 app/views/settings/exports/show.html.haml     |  2 +-
 .../otp_authentication/show.html.haml         |  2 +-
 .../index.html.haml                           |  4 ++--
 11 files changed, 27 insertions(+), 31 deletions(-)

diff --git a/app/javascript/styles/mastodon/forms.scss b/app/javascript/styles/mastodon/forms.scss
index 4f974ea58..957a28352 100644
--- a/app/javascript/styles/mastodon/forms.scss
+++ b/app/javascript/styles/mastodon/forms.scss
@@ -19,10 +19,6 @@ code {
   margin-bottom: 24px;
 }
 
-form.button_to {
-  display: inline-block;
-}
-
 .fade-out-top {
   position: relative;
   overflow: hidden;
diff --git a/app/views/admin/accounts/_buttons.html.haml b/app/views/admin/accounts/_buttons.html.haml
index eb1a7c3a4..2aaca8962 100644
--- a/app/views/admin/accounts/_buttons.html.haml
+++ b/app/views/admin/accounts/_buttons.html.haml
@@ -4,8 +4,8 @@
     %p.muted-hint= deletion_request.present? ? t('admin.accounts.remote_suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.remote_suspension_irreversible')
   - else
     %p.muted-hint= deletion_request.present? ? t('admin.accounts.suspension_reversible_hint_html', date: content_tag(:strong, l(deletion_request.due_at.to_date))) : t('admin.accounts.suspension_irreversible')
-  = button_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(account.id), class: :button if can?(:unsuspend, account)
-  = button_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), class: :button if can?(:redownload, account) && account.suspension_origin_remote?
+  = link_to t('admin.accounts.undo_suspension'), unsuspend_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsuspend, account)
+  = link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button' if can?(:redownload, account) && account.suspension_origin_remote?
   - if deletion_request.present? && can?(:destroy, account)
     = link_to t('admin.accounts.delete'), admin_account_path(account.id), method: :delete, class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure') }
 - else
@@ -14,28 +14,28 @@
       - if account.local? && account.user_approved?
         = link_to t('admin.accounts.warn'), new_admin_account_action_path(account.id, type: 'none'), class: 'button' if can?(:warn, account)
         - if account.user_disabled?
-          = button_to t('admin.accounts.enable'), enable_admin_account_path(account.id), class: :button if can?(:enable, account.user)
+          = link_to t('admin.accounts.enable'), enable_admin_account_path(account.id), method: :post, class: 'button' if can?(:enable, account.user)
         - elsif can?(:disable, account.user)
           = link_to t('admin.accounts.disable'), new_admin_account_action_path(account.id, type: 'disable'), class: 'button'
       - if account.sensitized?
-        = button_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(account.id), class: :button if can?(:unsensitive, account)
+        = link_to t('admin.accounts.undo_sensitized'), unsensitive_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsensitive, account)
       - elsif !account.local? || account.user_approved?
         = link_to t('admin.accounts.sensitive'), new_admin_account_action_path(account.id, type: 'sensitive'), class: 'button' if can?(:sensitive, account)
       - if account.silenced?
-        = button_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(account.id), class: :button if can?(:unsilence, account)
+        = link_to t('admin.accounts.undo_silenced'), unsilence_admin_account_path(account.id), method: :post, class: 'button' if can?(:unsilence, account)
       - elsif !account.local? || account.user_approved?
         = link_to t('admin.accounts.silence'), new_admin_account_action_path(account.id, type: 'silence'), class: 'button' if can?(:silence, account)
       - if account.local?
         - if account.user_pending?
-          = button_to t('admin.accounts.approve'), approve_admin_account_path(account.id), data: { confirm: t('admin.accounts.are_you_sure') }, class: :button if can?(:approve, account.user)
-          = button_to t('admin.accounts.reject'), reject_admin_account_path(account.id), data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, account.user)
+          = link_to t('admin.accounts.approve'), approve_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' if can?(:approve, account.user)
+          = link_to t('admin.accounts.reject'), reject_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:reject, account.user)
         - if !account.user_confirmed? && can?(:confirm, account.user)
-          = button_to t('admin.accounts.confirm'), admin_account_confirmation_path(account.id), class: :button
+          = link_to t('admin.accounts.confirm'), admin_account_confirmation_path(account.id), method: :post, class: 'button'
       - if (!account.local? || account.user_approved?) && can?(:suspend, account)
         = link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(account.id, type: 'suspend'), class: 'button'
     %div
       - if account.local?
         - if !account.memorial? && account.user_approved? && can?(:memorialize, account)
-          = button_to t('admin.accounts.memorialize'), memorialize_admin_account_path(account.id), data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
+          = link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
       - elsif can?(:redownload, account)
-        = button_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), class: :button
+        = link_to t('admin.accounts.redownload'), redownload_admin_account_path(account.id), method: :post, class: 'button'
diff --git a/app/views/admin/accounts/show.html.haml b/app/views/admin/accounts/show.html.haml
index 2d9e30e36..f148b9a08 100644
--- a/app/views/admin/accounts/show.html.haml
+++ b/app/views/admin/accounts/show.html.haml
@@ -30,7 +30,7 @@
 = render 'admin/accounts/counters', account: @account
 
 - if @account.local? && @account.user.nil?
-  = button_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), class: :button if can?(:unblock_email, @account) && CanonicalEmailBlock.exists?(reference_account_id: @account.id)
+  = link_to t('admin.accounts.unblock_email'), unblock_email_admin_account_path(@account.id), method: :post, class: 'button' if can?(:unblock_email, @account) && CanonicalEmailBlock.exists?(reference_account_id: @account.id)
 - else
   .table-wrapper
     %table.table.inline-table
diff --git a/app/views/admin/instances/show.html.haml b/app/views/admin/instances/show.html.haml
index dd4c50549..812a9c887 100644
--- a/app/views/admin/instances/show.html.haml
+++ b/app/views/admin/instances/show.html.haml
@@ -21,7 +21,7 @@
   - if @instance.domain_allow
     = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@instance.domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
   - else
-    = button_to t('admin.domain_allows.add_new'), admin_domain_allows_path(domain_allow: { domain: @instance.domain }), class: :button
+    = link_to t('admin.domain_allows.add_new'), admin_domain_allows_path(domain_allow: { domain: @instance.domain }), class: 'button', method: :post
 - else
   %p= t('admin.instances.content_policies.description_html')
 
@@ -40,7 +40,7 @@
             %td= @instance.domain_block.policies.map { |policy| t(policy, scope: 'admin.instances.content_policies.policies') }.join(' · ')
 
     = link_to t('admin.domain_blocks.edit'), edit_admin_domain_block_path(@instance.domain_block), class: 'button'
-    = button_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: :button, data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
+    = link_to t('admin.domain_blocks.undo'), admin_domain_block_path(@instance.domain_block), class: 'button', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
   - else
     = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @instance.domain), class: 'button'
 
@@ -70,16 +70,16 @@
       - if @instance.unavailable?
         %span.negative-hint
           = t('admin.instances.availability.failure_threshold_reached', date: l(@instance.unavailable_domain.created_at.to_date))
-          = button_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') }
+          = link_to t('admin.instances.delivery.restart'), restart_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
       - elsif @instance.exhausted_deliveries_days.empty?
         %span.positive-hint
           = t('admin.instances.availability.no_failures_recorded')
-          = button_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') }
+          = link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
       - else
         %span.negative-hint
           = t('admin.instances.availability.failures_recorded', count: @instance.delivery_failure_tracker.days)
-          %span= button_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') } unless @instance.exhausted_deliveries_days.empty?
-          %span= button_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure') }
+          %span= link_to t('admin.instances.delivery.clear'), clear_delivery_errors_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post } unless @instance.exhausted_deliveries_days.empty?
+          %span= link_to t('admin.instances.delivery.stop'), stop_delivery_admin_instance_path(@instance), data: { confirm: t('admin.accounts.are_you_sure'), method: :post }
 
   - if @instance.purgeable?
     %p= t('admin.instances.purge_description_html')
diff --git a/app/views/admin/invites/index.html.haml b/app/views/admin/invites/index.html.haml
index bbccca314..964deaba8 100644
--- a/app/views/admin/invites/index.html.haml
+++ b/app/views/admin/invites/index.html.haml
@@ -34,4 +34,4 @@
 = paginate @invites
 
 - if policy(:invite).deactivate_all?
-  = button_to t('admin.invites.deactivate_all'), deactivate_all_admin_invites_path, data: { confirm: t('admin.accounts.are_you_sure') }, class: :button
+  = link_to t('admin.invites.deactivate_all'), deactivate_all_admin_invites_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
diff --git a/app/views/admin/reports/_actions.html.haml b/app/views/admin/reports/_actions.html.haml
index c25f45e16..ef016e949 100644
--- a/app/views/admin/reports/_actions.html.haml
+++ b/app/views/admin/reports/_actions.html.haml
@@ -2,7 +2,7 @@
   .report-actions
     .report-actions__item
       .report-actions__item__button
-        = button_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(report), class: :button
+        = link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(report), method: :post, class: 'button'
       .report-actions__item__description
         = t('admin.reports.actions.resolve_description_html')
     - if statuses.any? { |status| (status.with_media? || status.with_preview_card?) && !status.discarded? }
diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml
index 32b6b3be8..69e9c0292 100644
--- a/app/views/admin/reports/show.html.haml
+++ b/app/views/admin/reports/show.html.haml
@@ -3,9 +3,9 @@
 
 - content_for :heading_actions do
   - if @report.unresolved?
-    = button_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), class: :button
+    = link_to t('admin.reports.mark_as_resolved'), resolve_admin_report_path(@report), method: :post, class: 'button'
   - else
-    = button_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), class: :button
+    = link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button'
 
 - unless @report.account.local? || @report.target_account.local?
   .flash-message= t('admin.reports.forwarded_replies_explanation')
diff --git a/app/views/disputes/strikes/show.html.haml b/app/views/disputes/strikes/show.html.haml
index 1d71e0ddd..150dc0675 100644
--- a/app/views/disputes/strikes/show.html.haml
+++ b/app/views/disputes/strikes/show.html.haml
@@ -3,8 +3,8 @@
 
 - content_for :heading_actions do
   - if @appeal.persisted?
-    = button_to t('disputes.strikes.approve_appeal'), approve_admin_disputes_appeal_path(@appeal), class: :button if can?(:approve, @appeal)
-    = button_to t('disputes.strikes.reject_appeal'), reject_admin_disputes_appeal_path(@appeal), class: 'button button--destructive' if can?(:reject, @appeal)
+    = link_to t('disputes.strikes.approve_appeal'), approve_admin_disputes_appeal_path(@appeal), method: :post, class: 'button' if can?(:approve, @appeal)
+    = link_to t('disputes.strikes.reject_appeal'), reject_admin_disputes_appeal_path(@appeal), method: :post, class: 'button button--destructive' if can?(:reject, @appeal)
 
 - if @strike.overruled?
   %p.hint
diff --git a/app/views/settings/exports/show.html.haml b/app/views/settings/exports/show.html.haml
index b36e395e3..5a151be73 100644
--- a/app/views/settings/exports/show.html.haml
+++ b/app/views/settings/exports/show.html.haml
@@ -46,7 +46,7 @@
 %p.muted-hint= t('exports.archive_takeout.hint_html')
 
 - if policy(:backup).create?
-  %p= button_to t('exports.archive_takeout.request'), settings_export_path, class: :button
+  %p= link_to t('exports.archive_takeout.request'), settings_export_path, class: 'button', method: :post
 
 - unless @backups.empty?
   %hr.spacer/
diff --git a/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml b/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml
index 01f385dbe..d069ba12a 100644
--- a/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml
+++ b/app/views/settings/two_factor_authentication/otp_authentication/show.html.haml
@@ -6,4 +6,4 @@
 
   %hr.spacer/
 
-  = button_to t('otp_authentication.setup'), settings_otp_authentication_path, class: 'block-button'
+  = link_to t('otp_authentication.setup'), settings_otp_authentication_path, data: { method: :post }, class: 'block-button'
diff --git a/app/views/settings/two_factor_authentication_methods/index.html.haml b/app/views/settings/two_factor_authentication_methods/index.html.haml
index e3a211a3d..8b670283b 100644
--- a/app/views/settings/two_factor_authentication_methods/index.html.haml
+++ b/app/views/settings/two_factor_authentication_methods/index.html.haml
@@ -2,7 +2,7 @@
   = t('settings.two_factor_authentication')
 
 - content_for :heading_actions do
-  = button_to t('two_factor_authentication.disable'), disable_settings_two_factor_authentication_methods_path, class: 'button button--destructive'
+  = link_to t('two_factor_authentication.disable'), disable_settings_two_factor_authentication_methods_path, class: 'button button--destructive', method: :post
 
 %p.hint
   %span.positive-hint
@@ -38,4 +38,4 @@
 %hr.spacer/
 
 .simple_form
-  = button_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, class: 'block-button'
+  = link_to t('two_factor_authentication.generate_recovery_codes'), settings_two_factor_authentication_recovery_codes_path, data: { method: :post }, class: 'block-button'

From aba888c4a6c3be008bb708c0871f145cb0356052 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 4 Oct 2024 09:18:38 +0200
Subject: [PATCH 55/70] Update dependency ffmpeg to v7.1 (#32239)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 Dockerfile | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Dockerfile b/Dockerfile
index 0452e5d06..a5e35025a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -214,7 +214,7 @@ FROM build AS ffmpeg
 
 # ffmpeg version to compile, change with [--build-arg FFMPEG_VERSION="7.0.x"]
 # renovate: datasource=repology depName=ffmpeg packageName=openpkg_current/ffmpeg
-ARG FFMPEG_VERSION=7.0.2
+ARG FFMPEG_VERSION=7.1
 # ffmpeg download URL, change with [--build-arg FFMPEG_URL="https://ffmpeg.org/releases"]
 ARG FFMPEG_URL=https://ffmpeg.org/releases
 

From 1b247a1dc21bbaf20bd03be073f65e8dfc286dba Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Fri, 4 Oct 2024 09:29:53 +0200
Subject: [PATCH 56/70] New Crowdin Translations (automated) (#32249)

Co-authored-by: GitHub Actions <noreply@github.com>
---
 app/javascript/mastodon/locales/kab.json | 8 +++++---
 app/javascript/mastodon/locales/ko.json  | 2 +-
 app/javascript/mastodon/locales/sk.json  | 3 ++-
 app/javascript/mastodon/locales/sq.json  | 5 +++++
 config/locales/ca.yml                    | 6 ++++++
 config/locales/lv.yml                    | 7 +++++++
 config/locales/simple_form.lv.yml        | 1 +
 config/locales/sk.yml                    | 6 +++---
 8 files changed, 30 insertions(+), 8 deletions(-)

diff --git a/app/javascript/mastodon/locales/kab.json b/app/javascript/mastodon/locales/kab.json
index 07236bb73..35e9be816 100644
--- a/app/javascript/mastodon/locales/kab.json
+++ b/app/javascript/mastodon/locales/kab.json
@@ -278,6 +278,8 @@
   "hashtag.counter_by_uses_today": "{count, plural, one {{counter} n tsuffeɣt} other {{counter} n tsuffaɣ}} assa",
   "hashtag.follow": "Ḍfeṛ ahacṭag",
   "hashtags.and_other": "…d {count, plural, one {}other {# nniḍen}}",
+  "hints.threads.replies_may_be_missing": "Tiririyin d-yusan deg iqeddacen nniḍen, yezmer ur d-ddant ara.",
+  "hints.threads.see_more": "Wali ugar n tririt deg {domain}",
   "home.column_settings.show_reblogs": "Ssken-d beṭṭu",
   "home.column_settings.show_replies": "Ssken-d tiririyin",
   "home.hide_announcements": "Ffer ulɣuyen",
@@ -585,7 +587,7 @@
   "status.bookmark": "Creḍ",
   "status.cancel_reblog_private": "Sefsex beṭṭu",
   "status.cannot_reblog": "Tasuffeɣt-a ur tezmir ara ad tettwabḍu tikelt-nniḍen",
-  "status.continued_thread": "Asentel yettkemmil",
+  "status.continued_thread": "Asqerdec yettkemmil",
   "status.copy": "Nɣel assaɣ ɣer tasuffeɣt",
   "status.delete": "Kkes",
   "status.direct": "Bder-d @{name} weḥd-s",
@@ -617,10 +619,10 @@
   "status.reblogs.empty": "Ula yiwen ur yebḍi tajewwiqt-agi ar tura. Ticki yebḍa-tt yiwen, ad d-iban da.",
   "status.redraft": "Kkes tɛiwdeḍ tira",
   "status.remove_bookmark": "Kkes tacreḍt",
-  "status.replied_in_thread": "Y·t·erra-d deg usentel",
+  "status.replied_in_thread": "Y·t·erra-d deg usqerdec",
   "status.replied_to": "Y·terra-yas i {name}",
   "status.reply": "Err",
-  "status.replyAll": "Err i lxiḍ",
+  "status.replyAll": "Err i wesqerdec",
   "status.report": "Cetki ɣef @{name}",
   "status.sensitive_warning": "Agbur amḥulfu",
   "status.share": "Bḍu",
diff --git a/app/javascript/mastodon/locales/ko.json b/app/javascript/mastodon/locales/ko.json
index 2054dfc0f..e5e7dc2c1 100644
--- a/app/javascript/mastodon/locales/ko.json
+++ b/app/javascript/mastodon/locales/ko.json
@@ -317,7 +317,7 @@
   "follow_suggestions.curated_suggestion": "스태프의 추천",
   "follow_suggestions.dismiss": "다시 보지 않기",
   "follow_suggestions.featured_longer": "{domain} 팀이 손수 고름",
-  "follow_suggestions.friends_of_friends_longer": "내가 팔로우 하는 사람들 사이에서 인기",
+  "follow_suggestions.friends_of_friends_longer": "내가 팔로우한 사람들 사이에서 인기",
   "follow_suggestions.hints.featured": "이 프로필은 {domain} 팀이 손수 선택했습니다.",
   "follow_suggestions.hints.friends_of_friends": "이 프로필은 내가 팔로우 하는 사람들에게서 유명합니다.",
   "follow_suggestions.hints.most_followed": "이 프로필은 {domain}에서 가장 많이 팔로우 된 사람들 중 하나입니다.",
diff --git a/app/javascript/mastodon/locales/sk.json b/app/javascript/mastodon/locales/sk.json
index ddf341584..da3b1eaef 100644
--- a/app/javascript/mastodon/locales/sk.json
+++ b/app/javascript/mastodon/locales/sk.json
@@ -33,7 +33,7 @@
   "account.follow": "Sledovať",
   "account.follow_back": "Sledovať späť",
   "account.followers": "Sledovatelia",
-  "account.followers.empty": "Tento účet ešte nikto nesleduje.",
+  "account.followers.empty": "Ešte nikto nesleduje tohto užívateľa.",
   "account.followers_counter": "{count, plural, one {{counter} sledujúci} other {{counter} sledujúci}}",
   "account.following": "Sledovaný účet",
   "account.following_counter": "{count, plural, one {{counter} sledovaných} other {{counter} sledovaných}}",
@@ -85,6 +85,7 @@
   "alert.rate_limited.title": "Priveľa žiadostí",
   "alert.unexpected.message": "Vyskytla sa nečakaná chyba.",
   "alert.unexpected.title": "Ups!",
+  "alt_text_badge.title": "Alternatívny popis",
   "announcement.announcement": "Oznámenie",
   "attachments_list.unprocessed": "(nespracované)",
   "audio.hide": "Skryť zvuk",
diff --git a/app/javascript/mastodon/locales/sq.json b/app/javascript/mastodon/locales/sq.json
index 084ac9945..15101be47 100644
--- a/app/javascript/mastodon/locales/sq.json
+++ b/app/javascript/mastodon/locales/sq.json
@@ -852,6 +852,11 @@
   "upload_error.poll": "Me pyetësorët s’lejohet ngarkim kartelash.",
   "upload_form.audio_description": "Përshkruajeni për persona me dëgjim të kufizuar",
   "upload_form.description": "Përshkruajeni për persona me probleme shikimi",
+  "upload_form.drag_and_drop.instructions": "Që të merrni një bashkëngjitje media, shtypni tastin Space ose Enter. Teksa bëhet tërheqje, përdorni tastet shigjetë për ta shpënë bashkëngjitjen media në cilëndo drejtori që doni. Shtypni sërish Space ose Enter që të lihet bashkëngjitja media në pozicionin e vet të ri, ose shtypni Esc, që të anulohet veprimi.",
+  "upload_form.drag_and_drop.on_drag_cancel": "Tërheqja u anulua. Bashkëngjitja media {item} u la.",
+  "upload_form.drag_and_drop.on_drag_end": "Bashkëngjitja media {item} u la.",
+  "upload_form.drag_and_drop.on_drag_over": "Bashkëngjitja media {item} u lëviz.",
+  "upload_form.drag_and_drop.on_drag_start": "U mor bashkëngjitja media {item}.",
   "upload_form.edit": "Përpunoni",
   "upload_form.thumbnail": "Ndryshoni miniaturën",
   "upload_form.video_description": "Përshkruajeni për persona me dëgjim të kufizuar ose probleme shikimi",
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index 965f354a3..ced8de4ef 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -446,6 +446,7 @@ ca:
         title: Blocar el nou domini de correu-e
       no_email_domain_block_selected: No s'han canviat els bloqueigs de domini perquè no se n'ha seleccionat cap
       not_permitted: No permés
+      resolved_dns_records_hint_html: El nom del domini resol als següents dominis MX, que són els responsables finals per a acceptar els correus. Blocar un domini MX blocarà els registres des de qualsevol adreça de correu que utilitzi el mateix domini MX, encara que el nom visible sigui diferent. <strong>Vigileu de no blocar els grans proveïdors de correu.</strong>
       resolved_through_html: Resolt mitjançant %{domain}
       title: Dominis de correu-e blocats
     export_domain_allows:
@@ -800,6 +801,7 @@ ca:
       destroyed_msg: La càrrega al lloc s'ha suprimit correctament!
     software_updates:
       critical_update: Crítica - si us plau, actualitza ràpidament
+      description: Es recomana de mantenir actualizada la instal·lació de Mastodon per a beneficiar-se de les darreres correccions i característiques. A més, de vegades és fonamental actualitzar-la per a evitar problemes de seguretat. Per això Mastodon comprova si hi ha actualitzacions cada 30 minuts i us notificarà d'acord amb les preferències de notificacions de correu electrònic.
       documentation_link: Més informació
       release_notes: Notes de llançament
       title: Actualitzacions disponibles
@@ -1155,6 +1157,7 @@ ca:
       account_status: Estat del compte
       confirming: Esperant que es completi la confirmació del correu-e.
       functional: El teu compte està completament operatiu.
+      pending: La vostra sol·licitud està pendent de revisió pel nostre personal. Això pot trigar una mica. Rebreu un correu electrònic quan s'aprovi.
       redirecting_to: El teu compte és inactiu perquè actualment està redirigint a %{acct}.
       self_destruct: Com que %{domain} tanca, només tindreu accés limitat al vostre compte.
       view_strikes: Veure accions del passat contra el teu compte
@@ -1202,6 +1205,9 @@ ca:
       before: 'Abans de procedir, llegiu amb cura aquestes notes:'
       caches: El contingut que ha estat memoritzat en la memòria cau per altres servidors pot persistir
       data_removal: Els teus tuts i altres dades seran permanentment eliminades
+      email_change_html: Podeu <a href="%{path}">canviar l'adreça de correu </a> sense eliminar el vostre compte
+      email_contact_html: Si encara no arriba, podeu enviar un correu-e a <a href="mailto:%{email}">%{email}</a> per a demanar ajuda
+      email_reconfirmation_html: Si no rebeu el correu electrònic de confirmació <a href="%{path}">, podeu tornar-lo a demanar</a>
       irreversible: No seràs capaç de restaurar o reactivar el teu compte
       more_details_html: Per a més detalls, llegeix la <a href="%{terms_path}">política de privadesa</a>.
       username_available: El teu nom d'usuari esdevindrà altre cop disponible
diff --git a/config/locales/lv.yml b/config/locales/lv.yml
index 16844a95c..2cc8ff6a4 100644
--- a/config/locales/lv.yml
+++ b/config/locales/lv.yml
@@ -34,6 +34,7 @@ lv:
       created_msg: Moderācijas piezīme ir veiksmīgi izveidota!
       destroyed_msg: Moderācijas piezīme ir veiksmīgi iznīcināta!
     accounts:
+      add_email_domain_block: Liegt e-pasta domēnu
       approve: Apstiprināt
       approved_msg: Veiksmīgi apstiprināts %{username} reģistrēšanās pieteikums
       are_you_sure: Vai esi pārliecināts?
@@ -61,6 +62,7 @@ lv:
       demote: Pazemināt
       destroyed_msg: Lietotāja %{username} dati tagad ievietoti rindā, lai tos nekavējoties izdzēstu
       disable: Iesaldēt
+      disable_sign_in_token_auth: Atspējot autentificēšanos ar e-pasta pilnvaru
       disable_two_factor_authentication: Atspējot 2FA
       disabled: Iesaldēts
       display_name: Parādāmais vārds
@@ -69,6 +71,7 @@ lv:
       email: E-pasts
       email_status: E-pasta statuss
       enable: Atsaldēt
+      enable_sign_in_token_auth: Iespējot autentificēšanos ar e-pasta pilnvaru
       enabled: Iespējots
       enabled_msg: Veiksmīgi atsaldēts %{username} konts
       followers: Sekotāji
@@ -180,17 +183,21 @@ lv:
         confirm_user: Apstiprināt lietotāju
         create_account_warning: Izveidot Brīdinājumu
         create_announcement: Izveidot Paziņojumu
+        create_canonical_email_block: Izveidot e-pasta liegumu
         create_custom_emoji: Izveidot pielāgotu emocijzīmi
         create_domain_allow: Izveidot Domēna Atļauju
         create_domain_block: Izveidot Domēna Bloku
+        create_email_domain_block: Izveidot e-pasta domēna liegumu
         create_ip_block: Izveidot IP noteikumu
         create_unavailable_domain: Izveidot Nepieejamu Domēnu
         create_user_role: Izveidot lomu
         demote_user: Pazemināt Lietotāju
         destroy_announcement: Dzēst Paziņojumu
+        destroy_canonical_email_block: Izdzēst e-pasta liegumu
         destroy_custom_emoji: Dzēst pielāgoto emocijzīmi
         destroy_domain_allow: Dzēst Domēna Atļauju
         destroy_domain_block: Dzēst Domēna Bloku
+        destroy_email_domain_block: Izdzēst e-pasta domēna liegumu
         destroy_instance: Attīrīt domēnu
         destroy_ip_block: Dzēst IP noteikumu
         destroy_status: Izdzēst Rakstu
diff --git a/config/locales/simple_form.lv.yml b/config/locales/simple_form.lv.yml
index 9cc32457f..523e9a5fc 100644
--- a/config/locales/simple_form.lv.yml
+++ b/config/locales/simple_form.lv.yml
@@ -86,6 +86,7 @@ lv:
         custom_css: Vari lietot pielāgotus stilus Mastodon tīmekļa versijā.
         favicon: WEBP, PNG, GIF vai JPG. Aizstāj noklusējuma Mastodon favikonu ar pielāgotu.
         mascot: Ignorē ilustrāciju uzlabotajā tīmekļa saskarnē.
+        media_cache_retention_period: Informācijas nesēju datnes no ierakstiem, kurus ir veikuši attālie lietotāji, tiek kešoti šajā serverī. Kad ir iestatīta apstiprinoša vērtība, informācijas nesēji tiks izdzēsti pēc norādītā dienu skaita. Ja informācijas nesēju dati tiks pieprasīti pēc tam, kad tie tika izdzēsti, tie tiks atkārtoti lejupielādēti, ja avota saturs joprojām būs pieejams. Saišu priekšskatījuma karšu vaicājumu biežuma ierobežojumu dēļ ir ieteicams iestatīt šo vērtību vismaz 14 dienas vai saišu priekšskatījuma kartes netiks atjauninātas pēc pieprasījuma pirms tā laika.
         peers_api_enabled: Domēna vārdu saraksts, ar kuriem šis serveris ir saskāries fediversā. Šeit nav iekļauti dati par to, vai tu veic federāciju ar noteiktu serveri, tikai tavs serveris par to zina. To izmanto dienesti, kas apkopo statistiku par federāciju vispārīgā nozīmē.
         profile_directory: Profilu direktorijā ir uzskaitīti visi lietotāji, kuri ir izvēlējušies būt atklājami.
         require_invite_text: 'Ja pierakstīšanai nepieciešama manuāla apstiprināšana, izdari tā, lai teksta: “Kāpēc vēlaties pievienoties?” ievade ir obligāta, nevis opcionāla'
diff --git a/config/locales/sk.yml b/config/locales/sk.yml
index 8076682ed..399ecc061 100644
--- a/config/locales/sk.yml
+++ b/config/locales/sk.yml
@@ -8,10 +8,10 @@ sk:
     title: Ohľadom
   accounts:
     followers:
-      few: Sledovateľov
+      few: Sledujúcich
       many: Sledovateľov
       one: Sledujúci
-      other: Sledovatelia
+      other: Sledujúci
     following: Nasledujem
     instance_actor_flash: Toto konto je virtuálny aktér, ktorý predstavuje samotný server, a nie konkrétneho používateľa. Používa sa na účely federácie a nemal by byť pozastavený.
     last_active: naposledy aktívny
@@ -1167,7 +1167,7 @@ sk:
     dormant: Spiace
     follow_failure: Nemožno nasledovať niektoré z vybraných účtov.
     follow_selected_followers: Následuj označených sledovatelov
-    followers: Následovatelia
+    followers: Sledovatelia
     following: Nasledovaní
     invited: Pozvaný/á
     last_active: Naposledy aktívny

From 12a8ac1f8514f002a9d2c830e8981fde80b1cba9 Mon Sep 17 00:00:00 2001
From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com>
Date: Fri, 4 Oct 2024 09:57:41 +0200
Subject: [PATCH 57/70] Update dependency node to 20.18 (#32244)

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
---
 .nvmrc | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.nvmrc b/.nvmrc
index 65da8ce39..10fef252a 100644
--- a/.nvmrc
+++ b/.nvmrc
@@ -1 +1 @@
-20.17
+20.18

From 63a959099b854af62b88d6097895d5a7c1443cb3 Mon Sep 17 00:00:00 2001
From: forsamori <forsamori@yahoo.co.uk>
Date: Fri, 4 Oct 2024 09:02:14 +0100
Subject: [PATCH 58/70] Add margin-bottom to error.dialog to reduce whitespace
 (#25708)

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
---
 app/javascript/styles/mastodon/basics.scss | 1 +
 1 file changed, 1 insertion(+)

diff --git a/app/javascript/styles/mastodon/basics.scss b/app/javascript/styles/mastodon/basics.scss
index 1f961cb9e..1a74d8d52 100644
--- a/app/javascript/styles/mastodon/basics.scss
+++ b/app/javascript/styles/mastodon/basics.scss
@@ -141,6 +141,7 @@ body {
           width: 100%;
           height: auto;
           margin-top: -120px;
+          margin-bottom: -45px;
         }
       }
 

From 160917e71814168ce3aac776907dbe23a8c1c178 Mon Sep 17 00:00:00 2001
From: gunchleoc <fios@foramnagaidhlig.net>
Date: Fri, 4 Oct 2024 09:19:01 +0100
Subject: [PATCH 59/70] Pluralize csv imports (#27094)

Co-authored-by: Claire <claire.github-309c@sitedethib.com>
---
 app/views/settings/imports/show.html.haml |  4 +-
 config/i18n-tasks.yml                     |  4 +-
 config/locales/en.yml                     | 48 +++++++++++++++++------
 3 files changed, 40 insertions(+), 16 deletions(-)

diff --git a/app/views/settings/imports/show.html.haml b/app/views/settings/imports/show.html.haml
index 4d50049d3..dd18ac216 100644
--- a/app/views/settings/imports/show.html.haml
+++ b/app/views/settings/imports/show.html.haml
@@ -5,9 +5,9 @@
   .flash-message.warning= t('imports.mismatched_types_warning')
 
 - if @bulk_import.overwrite?
-  %p.hint= t("imports.overwrite_preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, total_items: @bulk_import.total_items)
+  %p.hint= t("imports.overwrite_preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, count: @bulk_import.total_items)
 - else
-  %p.hint= t("imports.preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, total_items: @bulk_import.total_items)
+  %p.hint= t("imports.preambles.#{@bulk_import.type}_html", filename: @bulk_import.original_filename, count: @bulk_import.total_items)
 
 .simple_form
   .actions
diff --git a/config/i18n-tasks.yml b/config/i18n-tasks.yml
index 8463d4297..fc043c422 100644
--- a/config/i18n-tasks.yml
+++ b/config/i18n-tasks.yml
@@ -66,8 +66,8 @@ ignore_unused:
   - 'admin_mailer.*.subject'
   - 'user_mailer.*.subject'
   - 'notification_mailer.*'
-  - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html'
-  - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html'
+  - 'imports.overwrite_preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*'
+  - 'imports.preambles.{following,blocking,muting,domain_blocking,bookmarks,lists}_html.*'
   - 'mail_subscriptions.unsubscribe.emails.*'
   - 'preferences.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use
   - 'edit_profile.other' # some locales are missing other keys, therefore leading i18n-tasks to detect `preferences` as plural and not finding use
diff --git a/config/locales/en.yml b/config/locales/en.yml
index bf45dff33..42327ae18 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -1366,19 +1366,43 @@ en:
       overwrite: Overwrite
       overwrite_long: Replace current records with the new ones
     overwrite_preambles:
-      blocking_html: You are about to <strong>replace your block list</strong> with up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
-      bookmarks_html: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{total_items} posts</strong> from <strong>%{filename}</strong>.
-      domain_blocking_html: You are about to <strong>replace your domain block list</strong> with up to <strong>%{total_items} domains</strong> from <strong>%{filename}</strong>.
-      following_html: You are about to <strong>follow</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
-      lists_html: You are about to <strong>replace your lists</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{total_items} accounts</strong> will be added to new lists.
-      muting_html: You are about to <strong>replace your list of muted accounts</strong> with up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
+      blocking_html:
+        one: You are about to <strong>replace your block list</strong> with up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
+        other: You are about to <strong>replace your block list</strong> with up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
+      bookmarks_html:
+        one: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{count} post</strong> from <strong>%{filename}</strong>.
+        other: You are about to <strong>replace your bookmarks</strong> with up to <strong>%{count} posts</strong> from <strong>%{filename}</strong>.
+      domain_blocking_html:
+        one: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>.
+        other: You are about to <strong>replace your domain block list</strong> with up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>.
+      following_html:
+        one: You are about to <strong>follow</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
+        other: You are about to <strong>follow</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong> and <strong>stop following anyone else</strong>.
+      lists_html:
+        one: You are about to <strong>replace your lists</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{count} account</strong> will be added to new lists.
+        other: You are about to <strong>replace your lists</strong> with contents of <strong>%{filename}</strong>. Up to <strong>%{count} accounts</strong> will be added to new lists.
+      muting_html:
+        one: You are about to <strong>replace your list of muted account</strong> with up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
+        other: You are about to <strong>replace your list of muted accounts</strong> with up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
     preambles:
-      blocking_html: You are about to <strong>block</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
-      bookmarks_html: You are about to add up to <strong>%{total_items} posts</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
-      domain_blocking_html: You are about to <strong>block</strong> up to <strong>%{total_items} domains</strong> from <strong>%{filename}</strong>.
-      following_html: You are about to <strong>follow</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
-      lists_html: You are about to add up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong> to your <strong>lists</strong>. New lists will be created if there is no list to add to.
-      muting_html: You are about to <strong>mute</strong> up to <strong>%{total_items} accounts</strong> from <strong>%{filename}</strong>.
+      blocking_html:
+        one: You are about to <strong>block</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
+        other: You are about to <strong>block</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
+      bookmarks_html:
+        one: You are about to add up to <strong>%{count} post</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
+        other: You are about to add up to <strong>%{count} posts</strong> from <strong>%{filename}</strong> to your <strong>bookmarks</strong>.
+      domain_blocking_html:
+        one: You are about to <strong>block</strong> up to <strong>%{count} domain</strong> from <strong>%{filename}</strong>.
+        other: You are about to <strong>block</strong> up to <strong>%{count} domains</strong> from <strong>%{filename}</strong>.
+      following_html:
+        one: You are about to <strong>follow</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
+        other: You are about to <strong>follow</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
+      lists_html:
+        one: You are about to add up to <strong>%{count} account</strong> from <strong>%{filename}</strong> to your <strong>lists</strong>. New lists will be created if there is no list to add to.
+        other: You are about to add up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong> to your <strong>lists</strong>. New lists will be created if there is no list to add to.
+      muting_html:
+        one: You are about to <strong>mute</strong> up to <strong>%{count} account</strong> from <strong>%{filename}</strong>.
+        other: You are about to <strong>mute</strong> up to <strong>%{count} accounts</strong> from <strong>%{filename}</strong>.
     preface: You can import data that you have exported from another server, such as a list of the people you are following or blocking.
     recent_imports: Recent imports
     states:

From 82e7d53d54143ca69c7b0a0cebacee3f3ddd7b3c Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Fri, 4 Oct 2024 04:29:23 -0400
Subject: [PATCH 60/70] Rename transformer constant in sanitizer (#30532)

---
 lib/sanitize_ext/sanitize_config.rb | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lib/sanitize_ext/sanitize_config.rb b/lib/sanitize_ext/sanitize_config.rb
index f0a7b6578..7387807e4 100644
--- a/lib/sanitize_ext/sanitize_config.rb
+++ b/lib/sanitize_ext/sanitize_config.rb
@@ -21,7 +21,7 @@ class Sanitize
       gemini
     ).freeze
 
-    CLASS_WHITELIST_TRANSFORMER = lambda do |env|
+    ALLOWED_CLASS_TRANSFORMER = lambda do |env|
       node = env[:node]
       class_list = node['class']&.split(/[\t\n\f\r ]/)
 
@@ -84,7 +84,7 @@ class Sanitize
       protocols: {},
 
       transformers: [
-        CLASS_WHITELIST_TRANSFORMER,
+        ALLOWED_CLASS_TRANSFORMER,
         TRANSLATE_TRANSFORMER,
         UNSUPPORTED_ELEMENTS_TRANSFORMER,
         UNSUPPORTED_HREF_TRANSFORMER,

From cf28104317642d647d77efe872bdabe3ea0c0624 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Fri, 4 Oct 2024 04:30:08 -0400
Subject: [PATCH 61/70] Rename preloaded models constant in cli media (#30531)

---
 lib/mastodon/cli/media.rb | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/lib/mastodon/cli/media.rb b/lib/mastodon/cli/media.rb
index 37f110d89..996b7fd92 100644
--- a/lib/mastodon/cli/media.rb
+++ b/lib/mastodon/cli/media.rb
@@ -177,7 +177,7 @@ module Mastodon::CLI
           attachment_name = path_segments[1].singularize
           file_name       = path_segments.last
 
-          next unless PRELOAD_MODEL_WHITELIST.include?(model_name)
+          next unless PRELOADED_MODELS.include?(model_name)
 
           record     = model_name.constantize.find_by(id: record_id)
           attachment = record&.public_send(attachment_name)
@@ -296,7 +296,7 @@ module Mastodon::CLI
       model_name = path_segments.first.classify
       record_id  = path_segments[2...-2].join.to_i
 
-      fail_with_message "Cannot find corresponding model: #{model_name}" unless PRELOAD_MODEL_WHITELIST.include?(model_name)
+      fail_with_message "Cannot find corresponding model: #{model_name}" unless PRELOADED_MODELS.include?(model_name)
 
       record = model_name.constantize.find_by(id: record_id)
       record = record.status if record.respond_to?(:status)
@@ -333,7 +333,7 @@ module Mastodon::CLI
       SQL
     end
 
-    PRELOAD_MODEL_WHITELIST = %w(
+    PRELOADED_MODELS = %w(
       Account
       Backup
       CustomEmoji
@@ -355,7 +355,7 @@ module Mastodon::CLI
         model_name = segments.first.classify
         record_id  = segments[2...-2].join.to_i
 
-        next unless PRELOAD_MODEL_WHITELIST.include?(model_name)
+        next unless PRELOADED_MODELS.include?(model_name)
 
         preload_map[model_name] << record_id
       end

From 49407e7623c52dd9e6e21bcb7a014cb116e081d3 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Fri, 4 Oct 2024 10:50:36 +0200
Subject: [PATCH 62/70] Fix Content-Security-Policy when using sso-redirect
 (#32241)

---
 app/controllers/concerns/web_app_controller_concern.rb | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/app/controllers/concerns/web_app_controller_concern.rb b/app/controllers/concerns/web_app_controller_concern.rb
index ebbdba59a..9485ecda4 100644
--- a/app/controllers/concerns/web_app_controller_concern.rb
+++ b/app/controllers/concerns/web_app_controller_concern.rb
@@ -13,7 +13,7 @@ module WebAppControllerConcern
       policy = ContentSecurityPolicy.new
 
       if policy.sso_host.present?
-        p.form_action policy.sso_host
+        p.form_action policy.sso_host, -> { "https://#{request.host}/auth/auth/" }
       else
         p.form_action :none
       end

From 463f9197d822e3b040834b4dab1c5334c7c8b968 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Fri, 4 Oct 2024 08:21:55 -0400
Subject: [PATCH 63/70] Add regression coverage for admin reports resolution
 button within form (#32248)

---
 spec/system/report_interface_spec.rb | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/spec/system/report_interface_spec.rb b/spec/system/report_interface_spec.rb
index 257a1cd6f..6a90aa5bc 100644
--- a/spec/system/report_interface_spec.rb
+++ b/spec/system/report_interface_spec.rb
@@ -28,4 +28,17 @@ RSpec.describe 'report interface', :attachment_processing, :js, :streaming do
     page.scroll_to(page.find('.batch-table__row'))
     expect(page).to have_css('.spoiler-button__overlay__label')
   end
+
+  it 'marks a report resolved from the show page actions area' do
+    visit admin_report_path(report)
+
+    expect { resolve_report }
+      .to change { report.reload.action_taken_at }.to(be_present).from(nil)
+  end
+
+  def resolve_report
+    within '.report-actions' do
+      click_on I18n.t('admin.reports.mark_as_resolved')
+    end
+  end
 end

From 77f5b127fadc02ba84fd1d5deccc7b70e68aba59 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Fri, 4 Oct 2024 14:23:30 +0200
Subject: [PATCH 64/70] Fix unsupported grouped notifications from streaming
 causing duplicate IDs (#32243)

---
 .../mastodon/reducers/notification_groups.ts  | 87 ++++++++++---------
 1 file changed, 45 insertions(+), 42 deletions(-)

diff --git a/app/javascript/mastodon/reducers/notification_groups.ts b/app/javascript/mastodon/reducers/notification_groups.ts
index 375e64387..8b033f0fc 100644
--- a/app/javascript/mastodon/reducers/notification_groups.ts
+++ b/app/javascript/mastodon/reducers/notification_groups.ts
@@ -206,50 +206,53 @@ function processNewNotification(
   groups: NotificationGroupsState['groups'],
   notification: ApiNotificationJSON,
 ) {
-  if (shouldGroupNotificationType(notification.type)) {
-    const existingGroupIndex = groups.findIndex(
-      (group) =>
-        group.type !== 'gap' && group.group_key === notification.group_key,
-    );
-
-    // In any case, we are going to add a group at the top
-    // If there is currently a gap at the top, now is the time to update it
-    if (groups.length > 0 && groups[0]?.type === 'gap') {
-      groups[0].maxId = notification.id;
-    }
-
-    if (existingGroupIndex > -1) {
-      const existingGroup = groups[existingGroupIndex];
-
-      if (
-        existingGroup &&
-        existingGroup.type !== 'gap' &&
-        !existingGroup.sampleAccountIds.includes(notification.account.id) // This can happen for example if you like, then unlike, then like again the same post
-      ) {
-        // Update the existing group
-        if (
-          existingGroup.sampleAccountIds.unshift(notification.account.id) >
-          NOTIFICATIONS_GROUP_MAX_AVATARS
-        )
-          existingGroup.sampleAccountIds.pop();
-
-        existingGroup.most_recent_notification_id = notification.id;
-        existingGroup.page_max_id = notification.id;
-        existingGroup.latest_page_notification_at = notification.created_at;
-        existingGroup.notifications_count += 1;
-
-        groups.splice(existingGroupIndex, 1);
-        mergeGapsAround(groups, existingGroupIndex);
-
-        groups.unshift(existingGroup);
-
-        return;
-      }
-    }
+  if (!shouldGroupNotificationType(notification.type)) {
+    notification = {
+      ...notification,
+      group_key: `ungrouped-${notification.id}`,
+    };
   }
 
-  // We have not found an existing group, create a new one
-  groups.unshift(createNotificationGroupFromNotificationJSON(notification));
+  const existingGroupIndex = groups.findIndex(
+    (group) =>
+      group.type !== 'gap' && group.group_key === notification.group_key,
+  );
+
+  // In any case, we are going to add a group at the top
+  // If there is currently a gap at the top, now is the time to update it
+  if (groups.length > 0 && groups[0]?.type === 'gap') {
+    groups[0].maxId = notification.id;
+  }
+
+  if (existingGroupIndex > -1) {
+    const existingGroup = groups[existingGroupIndex];
+
+    if (
+      existingGroup &&
+      existingGroup.type !== 'gap' &&
+      !existingGroup.sampleAccountIds.includes(notification.account.id) // This can happen for example if you like, then unlike, then like again the same post
+    ) {
+      // Update the existing group
+      if (
+        existingGroup.sampleAccountIds.unshift(notification.account.id) >
+        NOTIFICATIONS_GROUP_MAX_AVATARS
+      )
+        existingGroup.sampleAccountIds.pop();
+
+      existingGroup.most_recent_notification_id = notification.id;
+      existingGroup.page_max_id = notification.id;
+      existingGroup.latest_page_notification_at = notification.created_at;
+      existingGroup.notifications_count += 1;
+
+      groups.splice(existingGroupIndex, 1);
+      mergeGapsAround(groups, existingGroupIndex);
+
+      groups.unshift(existingGroup);
+    }
+  } else {
+    // We have not found an existing group, create a new one
+    groups.unshift(createNotificationGroupFromNotificationJSON(notification));
+  }
 }
 
 function trimNotifications(state: NotificationGroupsState) {

From 4fe7f213a64e22a2912f13a8e5d6ac415125b0a1 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Fri, 4 Oct 2024 08:29:43 -0400
Subject: [PATCH 65/70] Use `async_count` in more view locations (#32086)

---
 app/controllers/admin/announcements_controller.rb               | 1 +
 app/controllers/admin/disputes/appeals_controller.rb            | 1 +
 .../admin/trends/links/preview_card_providers_controller.rb     | 1 +
 app/controllers/admin/trends/tags_controller.rb                 | 1 +
 app/views/admin/announcements/index.html.haml                   | 2 +-
 app/views/admin/disputes/appeals/index.html.haml                | 2 +-
 .../admin/trends/links/preview_card_providers/index.html.haml   | 2 +-
 app/views/admin/trends/tags/index.html.haml                     | 2 +-
 8 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/app/controllers/admin/announcements_controller.rb b/app/controllers/admin/announcements_controller.rb
index 8f9708183..12230a650 100644
--- a/app/controllers/admin/announcements_controller.rb
+++ b/app/controllers/admin/announcements_controller.rb
@@ -6,6 +6,7 @@ class Admin::AnnouncementsController < Admin::BaseController
 
   def index
     authorize :announcement, :index?
+    @published_announcements_count = Announcement.published.async_count
   end
 
   def new
diff --git a/app/controllers/admin/disputes/appeals_controller.rb b/app/controllers/admin/disputes/appeals_controller.rb
index 5e342409b..0c4155367 100644
--- a/app/controllers/admin/disputes/appeals_controller.rb
+++ b/app/controllers/admin/disputes/appeals_controller.rb
@@ -6,6 +6,7 @@ class Admin::Disputes::AppealsController < Admin::BaseController
   def index
     authorize :appeal, :index?
 
+    @pending_appeals_count = Appeal.pending.async_count
     @appeals = filtered_appeals.page(params[:page])
   end
 
diff --git a/app/controllers/admin/trends/links/preview_card_providers_controller.rb b/app/controllers/admin/trends/links/preview_card_providers_controller.rb
index 768b79f8d..5e4b4084f 100644
--- a/app/controllers/admin/trends/links/preview_card_providers_controller.rb
+++ b/app/controllers/admin/trends/links/preview_card_providers_controller.rb
@@ -4,6 +4,7 @@ class Admin::Trends::Links::PreviewCardProvidersController < Admin::BaseControll
   def index
     authorize :preview_card_provider, :review?
 
+    @pending_preview_card_providers_count = PreviewCardProvider.unreviewed.async_count
     @preview_card_providers = filtered_preview_card_providers.page(params[:page])
     @form = Trends::PreviewCardProviderBatch.new
   end
diff --git a/app/controllers/admin/trends/tags_controller.rb b/app/controllers/admin/trends/tags_controller.rb
index f5946448a..fcd23fbf6 100644
--- a/app/controllers/admin/trends/tags_controller.rb
+++ b/app/controllers/admin/trends/tags_controller.rb
@@ -4,6 +4,7 @@ class Admin::Trends::TagsController < Admin::BaseController
   def index
     authorize :tag, :review?
 
+    @pending_tags_count = Tag.pending_review.async_count
     @tags = filtered_tags.page(params[:page])
     @form = Trends::TagBatch.new
   end
diff --git a/app/views/admin/announcements/index.html.haml b/app/views/admin/announcements/index.html.haml
index 72227b045..6a76c1877 100644
--- a/app/views/admin/announcements/index.html.haml
+++ b/app/views/admin/announcements/index.html.haml
@@ -9,7 +9,7 @@
     %strong= t('admin.relays.status')
     %ul
       %li= filter_link_to t('generic.all'), published: nil, unpublished: nil
-      %li= filter_link_to safe_join([t('admin.announcements.live'), "(#{number_with_delimiter(Announcement.published.count)})"], ' '), published: '1', unpublished: nil
+      %li= filter_link_to safe_join([t('admin.announcements.live'), "(#{number_with_delimiter(@published_announcements_count.value)})"], ' '), published: '1', unpublished: nil
 
 - if @announcements.empty?
   .muted-hint.center-text
diff --git a/app/views/admin/disputes/appeals/index.html.haml b/app/views/admin/disputes/appeals/index.html.haml
index 7f04dd40f..e09e275e5 100644
--- a/app/views/admin/disputes/appeals/index.html.haml
+++ b/app/views/admin/disputes/appeals/index.html.haml
@@ -5,7 +5,7 @@
   .filter-subset
     %strong= t('admin.tags.review')
     %ul
-      %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{Appeal.pending.count})"], ' '), status: 'pending'
+      %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{@pending_appeals_count.value})"], ' '), status: 'pending'
       %li= filter_link_to t('admin.trends.approved'), status: 'approved'
       %li= filter_link_to t('admin.trends.rejected'), status: 'rejected'
 
diff --git a/app/views/admin/trends/links/preview_card_providers/index.html.haml b/app/views/admin/trends/links/preview_card_providers/index.html.haml
index 93daf25f3..0770ac4b8 100644
--- a/app/views/admin/trends/links/preview_card_providers/index.html.haml
+++ b/app/views/admin/trends/links/preview_card_providers/index.html.haml
@@ -12,7 +12,7 @@
       %li= filter_link_to t('generic.all'), status: nil
       %li= filter_link_to t('admin.trends.approved'), status: 'approved'
       %li= filter_link_to t('admin.trends.rejected'), status: 'rejected'
-      %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{PreviewCardProvider.unreviewed.count})"], ' '), status: 'pending_review'
+      %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{@pending_preview_card_providers_count.value})"], ' '), status: 'pending_review'
   .back-link
     = link_to admin_trends_links_path do
       = material_symbol 'chevron_left'
diff --git a/app/views/admin/trends/tags/index.html.haml b/app/views/admin/trends/tags/index.html.haml
index 480877456..21f6c2947 100644
--- a/app/views/admin/trends/tags/index.html.haml
+++ b/app/views/admin/trends/tags/index.html.haml
@@ -12,7 +12,7 @@
       %li= filter_link_to t('generic.all'), status: nil
       %li= filter_link_to t('admin.trends.approved'), status: 'approved'
       %li= filter_link_to t('admin.trends.rejected'), status: 'rejected'
-      %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{Tag.pending_review.count})"], ' '), status: 'pending_review'
+      %li= filter_link_to safe_join([t('admin.accounts.moderation.pending'), "(#{@pending_tags_count.value})"], ' '), status: 'pending_review'
 
 = form_with model: @form, url: batch_admin_trends_tags_path do |f|
   = hidden_field_tag :page, params[:page] || 1

From e4e07b1c34858dc6b7070f1b5d81f40ec63ebf4c Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Fri, 4 Oct 2024 10:11:15 -0400
Subject: [PATCH 66/70] Reduce factory usage across `spec/services` area
 (#32098)

---
 .../account_statuses_cleanup_service_spec.rb  |  75 +++---
 .../process_status_update_service_spec.rb     |  74 +++--
 .../services/authorize_follow_service_spec.rb |  34 ++-
 .../batched_remove_status_service_spec.rb     |  52 ++--
 spec/services/block_service_spec.rb           |  13 +-
 spec/services/bulk_import_service_spec.rb     | 255 +++++++-----------
 spec/services/favourite_service_spec.rb       |  19 +-
 spec/services/follow_service_spec.rb          |  13 +-
 .../services/process_mentions_service_spec.rb |  48 ++--
 spec/services/reject_follow_service_spec.rb   |  36 ++-
 .../remove_from_followers_service_spec.rb     |  23 +-
 spec/services/remove_status_service_spec.rb   |  44 ++-
 spec/services/report_service_spec.rb          |  35 ++-
 spec/services/resolve_account_service_spec.rb | 128 +++++----
 spec/services/resolve_url_service_spec.rb     |  44 ++-
 .../services/translate_status_service_spec.rb |  52 ++--
 spec/services/unblock_domain_service_spec.rb  |  38 +--
 spec/services/unblock_service_spec.rb         |  24 +-
 spec/services/unfollow_service_spec.rb        |  38 +--
 spec/services/update_account_service_spec.rb  |  20 +-
 spec/services/update_status_service_spec.rb   | 143 +++++-----
 21 files changed, 567 insertions(+), 641 deletions(-)

diff --git a/spec/services/account_statuses_cleanup_service_spec.rb b/spec/services/account_statuses_cleanup_service_spec.rb
index 857bd4fda..553d20029 100644
--- a/spec/services/account_statuses_cleanup_service_spec.rb
+++ b/spec/services/account_statuses_cleanup_service_spec.rb
@@ -27,39 +27,35 @@ RSpec.describe AccountStatusesCleanupService do
       end
 
       context 'when given a normal budget of 10' do
-        it 'reports 3 deleted statuses' do
-          expect(subject.call(account_policy, 10)).to eq 3
-        end
+        it 'reports 3 deleted statuses and records last deleted id, deletes statuses, preserves recent unrelated statuses' do
+          expect(subject.call(account_policy, 10))
+            .to eq(3)
 
-        it 'records the last deleted id' do
-          subject.call(account_policy, 10)
-          expect(account_policy.last_inspected).to eq [old_status.id, another_old_status.id].max
-        end
+          expect(account_policy.last_inspected)
+            .to eq [old_status.id, another_old_status.id].max
 
-        it 'actually deletes the statuses' do
-          subject.call(account_policy, 10)
-          expect(Status.find_by(id: [very_old_status.id, old_status.id, another_old_status.id])).to be_nil
-          expect { recent_status.reload }.to_not raise_error
-        end
-
-        it 'preserves recent and unrelated statuses' do
-          subject.call(account_policy, 10)
-          expect { unrelated_status.reload }.to_not raise_error
-          expect { recent_status.reload }.to_not raise_error
+          expect(Status.find_by(id: [very_old_status.id, old_status.id, another_old_status.id]))
+            .to be_nil
+          expect { recent_status.reload }
+            .to_not raise_error
+          expect { unrelated_status.reload }
+            .to_not raise_error
+          expect { recent_status.reload }
+            .to_not raise_error
         end
       end
 
       context 'when called repeatedly with a budget of 2' do
-        it 'reports 2 then 1 deleted statuses' do
-          expect(subject.call(account_policy, 2)).to eq 2
-          expect(subject.call(account_policy, 2)).to eq 1
-        end
+        it 'reports 2 then 1 deleted statuses and deletes in expected order' do
+          expect(subject.call(account_policy, 2))
+            .to eq(2)
+          expect(Status.find_by(id: very_old_status.id))
+            .to be_nil
 
-        it 'actually deletes the statuses in the expected order' do
-          subject.call(account_policy, 2)
-          expect(Status.find_by(id: very_old_status.id)).to be_nil
-          subject.call(account_policy, 2)
-          expect(Status.find_by(id: [very_old_status.id, old_status.id, another_old_status.id])).to be_nil
+          expect(subject.call(account_policy, 2))
+            .to eq(1)
+          expect(Status.find_by(id: [very_old_status.id, old_status.id, another_old_status.id]))
+            .to be_nil
         end
       end
 
@@ -90,19 +86,24 @@ RSpec.describe AccountStatusesCleanupService do
           end
         end
 
-        it 'reports 0 deleted statuses then 0 then 3 then 0 again' do
-          expect(subject.call(account_policy, 10)).to eq 0
-          expect(subject.call(account_policy, 10)).to eq 0
-          expect(subject.call(account_policy, 10)).to eq 3
-          expect(subject.call(account_policy, 10)).to eq 0
+        it 'reports 0 deleted statuses then 0 then 3 then 0 again, and keeps id under oldest deletable record' do
+          expect(subject.call(account_policy, 10))
+            .to eq(0)
+          expect(subject.call(account_policy, 10))
+            .to eq(0)
+          expect(subject.call(account_policy, 10))
+            .to eq(3)
+          expect(subject.call(account_policy, 10))
+            .to eq(0)
+          expect(account_policy.last_inspected)
+            .to be < oldest_deletable_record_id
         end
 
-        it 'never causes the recorded id to get higher than oldest deletable toot' do
-          subject.call(account_policy, 10)
-          subject.call(account_policy, 10)
-          subject.call(account_policy, 10)
-          subject.call(account_policy, 10)
-          expect(account_policy.last_inspected).to be < Mastodon::Snowflake.id_at(account_policy.min_status_age.seconds.ago, with_random: false)
+        def oldest_deletable_record_id
+          Mastodon::Snowflake.id_at(
+            account_policy.min_status_age.seconds.ago,
+            with_random: false
+          )
         end
       end
     end
diff --git a/spec/services/activitypub/process_status_update_service_spec.rb b/spec/services/activitypub/process_status_update_service_spec.rb
index a97e84080..b6ceba374 100644
--- a/spec/services/activitypub/process_status_update_service_spec.rb
+++ b/spec/services/activitypub/process_status_update_service_spec.rb
@@ -2,10 +2,6 @@
 
 require 'rails_helper'
 
-def poll_option_json(name, votes)
-  { type: 'Note', name: name, replies: { type: 'Collection', totalItems: votes } }
-end
-
 RSpec.describe ActivityPub::ProcessStatusUpdateService do
   subject { described_class.new }
 
@@ -294,7 +290,6 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
     context 'when originally without media attachments' do
       before do
         stub_request(:get, 'https://example.com/foo.png').to_return(body: attachment_fixture('emojo.png'))
-        subject.call(status, json, json)
       end
 
       let(:payload) do
@@ -310,19 +305,18 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
         }
       end
 
-      it 'updates media attachments' do
-        media_attachment = status.reload.ordered_media_attachments.first
+      it 'updates media attachments, fetches attachment, records media change in edit' do
+        subject.call(status, json, json)
 
-        expect(media_attachment).to_not be_nil
-        expect(media_attachment.remote_url).to eq 'https://example.com/foo.png'
-      end
+        expect(status.reload.ordered_media_attachments.first)
+          .to be_present
+          .and(have_attributes(remote_url: 'https://example.com/foo.png'))
 
-      it 'fetches the attachment' do
-        expect(a_request(:get, 'https://example.com/foo.png')).to have_been_made
-      end
+        expect(a_request(:get, 'https://example.com/foo.png'))
+          .to have_been_made
 
-      it 'records media change in edit' do
-        expect(status.edits.reload.last.ordered_media_attachment_ids).to_not be_empty
+        expect(status.edits.reload.last.ordered_media_attachment_ids)
+          .to_not be_empty
       end
     end
 
@@ -344,27 +338,26 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
 
       before do
         allow(RedownloadMediaWorker).to receive(:perform_async)
+      end
+
+      it 'updates the existing media attachment in-place, does not queue redownload, updates media, records media change' do
         subject.call(status, json, json)
-      end
 
-      it 'updates the existing media attachment in-place' do
-        media_attachment = status.media_attachments.ordered.reload.first
+        expect(status.media_attachments.ordered.reload.first)
+          .to be_present
+          .and have_attributes(
+            remote_url: 'https://example.com/foo.png',
+            description: 'A picture'
+          )
 
-        expect(media_attachment).to_not be_nil
-        expect(media_attachment.remote_url).to eq 'https://example.com/foo.png'
-        expect(media_attachment.description).to eq 'A picture'
-      end
+        expect(RedownloadMediaWorker)
+          .to_not have_received(:perform_async)
 
-      it 'does not queue redownload for the existing media attachment' do
-        expect(RedownloadMediaWorker).to_not have_received(:perform_async)
-      end
+        expect(status.ordered_media_attachments.map(&:remote_url))
+          .to eq %w(https://example.com/foo.png)
 
-      it 'updates media attachments' do
-        expect(status.ordered_media_attachments.map(&:remote_url)).to eq %w(https://example.com/foo.png)
-      end
-
-      it 'records media change in edit' do
-        expect(status.edits.reload.last.ordered_media_attachment_ids).to_not be_empty
+        expect(status.edits.reload.last.ordered_media_attachment_ids)
+          .to_not be_empty
       end
     end
 
@@ -372,10 +365,11 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
       before do
         poll = Fabricate(:poll, status: status)
         status.update(preloadable_poll: poll)
-        subject.call(status, json, json)
       end
 
       it 'removes poll and records media change in edit' do
+        subject.call(status, json, json)
+
         expect(status.reload.poll).to be_nil
         expect(status.edits.reload.last.poll_options).to be_nil
       end
@@ -398,15 +392,13 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
         }
       end
 
-      before do
-        subject.call(status, json, json)
-      end
-
       it 'creates a poll and records media change in edit' do
-        poll = status.reload.poll
+        subject.call(status, json, json)
+
+        expect(status.reload.poll)
+          .to be_present
+          .and have_attributes(options: %w(Foo Bar Baz))
 
-        expect(poll).to_not be_nil
-        expect(poll.options).to eq %w(Foo Bar Baz)
         expect(status.edits.reload.last.poll_options).to eq %w(Foo Bar Baz)
       end
     end
@@ -419,4 +411,8 @@ RSpec.describe ActivityPub::ProcessStatusUpdateService do
         .to eq '2021-09-08 22:39:25 UTC'
     end
   end
+
+  def poll_option_json(name, votes)
+    { type: 'Note', name: name, replies: { type: 'Collection', totalItems: votes } }
+  end
 end
diff --git a/spec/services/authorize_follow_service_spec.rb b/spec/services/authorize_follow_service_spec.rb
index 533b791fb..de2857280 100644
--- a/spec/services/authorize_follow_service_spec.rb
+++ b/spec/services/authorize_follow_service_spec.rb
@@ -12,15 +12,15 @@ RSpec.describe AuthorizeFollowService do
 
     before do
       FollowRequest.create(account: bob, target_account: sender)
+    end
+
+    it 'removes follow request and creates follow relation' do
       subject.call(bob, sender)
-    end
 
-    it 'removes follow request' do
-      expect(bob.requested?(sender)).to be false
-    end
-
-    it 'creates follow relation' do
-      expect(bob.following?(sender)).to be true
+      expect(bob)
+        .to_not be_requested(sender)
+      expect(bob)
+        .to be_following(sender)
     end
   end
 
@@ -30,19 +30,17 @@ RSpec.describe AuthorizeFollowService do
     before do
       FollowRequest.create(account: bob, target_account: sender)
       stub_request(:post, bob.inbox_url).to_return(status: 200)
+    end
+
+    it 'removes follow request, creates follow relation, send accept activity', :inline_jobs do
       subject.call(bob, sender)
-    end
 
-    it 'removes follow request' do
-      expect(bob.requested?(sender)).to be false
-    end
-
-    it 'creates follow relation' do
-      expect(bob.following?(sender)).to be true
-    end
-
-    it 'sends an accept activity', :inline_jobs do
-      expect(a_request(:post, bob.inbox_url)).to have_been_made.once
+      expect(bob)
+        .to_not be_requested(sender)
+      expect(bob)
+        .to be_following(sender)
+      expect(a_request(:post, bob.inbox_url))
+        .to have_been_made.once
     end
   end
 end
diff --git a/spec/services/batched_remove_status_service_spec.rb b/spec/services/batched_remove_status_service_spec.rb
index 628bb198e..1ff73a633 100644
--- a/spec/services/batched_remove_status_service_spec.rb
+++ b/spec/services/batched_remove_status_service_spec.rb
@@ -24,32 +24,38 @@ RSpec.describe BatchedRemoveStatusService, :inline_jobs do
 
     status_alice_hello
     status_alice_other
+  end
 
+  it 'removes status records, removes from author and local follower feeds, notifies stream, sends delete' do
     subject.call([status_alice_hello, status_alice_other])
+
+    expect { Status.find(status_alice_hello.id) }
+      .to raise_error ActiveRecord::RecordNotFound
+    expect { Status.find(status_alice_other.id) }
+      .to raise_error ActiveRecord::RecordNotFound
+
+    expect(feed_ids_for(alice))
+      .to_not include(status_alice_hello.id, status_alice_other.id)
+
+    expect(feed_ids_for(jeff))
+      .to_not include(status_alice_hello.id, status_alice_other.id)
+
+    expect(redis)
+      .to have_received(:publish)
+      .with("timeline:#{jeff.id}", any_args).at_least(:once)
+
+    expect(redis)
+      .to have_received(:publish)
+      .with('timeline:public', any_args).at_least(:once)
+
+    expect(a_request(:post, 'http://example.com/inbox'))
+      .to have_been_made.at_least_once
   end
 
-  it 'removes statuses' do
-    expect { Status.find(status_alice_hello.id) }.to raise_error ActiveRecord::RecordNotFound
-    expect { Status.find(status_alice_other.id) }.to raise_error ActiveRecord::RecordNotFound
-  end
-
-  it 'removes statuses from author\'s home feed' do
-    expect(HomeFeed.new(alice).get(10).pluck(:id)).to_not include(status_alice_hello.id, status_alice_other.id)
-  end
-
-  it 'removes statuses from local follower\'s home feed' do
-    expect(HomeFeed.new(jeff).get(10).pluck(:id)).to_not include(status_alice_hello.id, status_alice_other.id)
-  end
-
-  it 'notifies streaming API of followers' do
-    expect(redis).to have_received(:publish).with("timeline:#{jeff.id}", any_args).at_least(:once)
-  end
-
-  it 'notifies streaming API of public timeline' do
-    expect(redis).to have_received(:publish).with('timeline:public', any_args).at_least(:once)
-  end
-
-  it 'sends delete activity to followers' do
-    expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.at_least_once
+  def feed_ids_for(account)
+    HomeFeed
+      .new(account)
+      .get(10)
+      .pluck(:id)
   end
 end
diff --git a/spec/services/block_service_spec.rb b/spec/services/block_service_spec.rb
index 46dd69198..d9687a540 100644
--- a/spec/services/block_service_spec.rb
+++ b/spec/services/block_service_spec.rb
@@ -26,15 +26,16 @@ RSpec.describe BlockService do
 
     before do
       stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
+    end
+
+    it 'creates a blocking relation and send block activity', :inline_jobs do
       subject.call(sender, bob)
-    end
 
-    it 'creates a blocking relation' do
-      expect(sender.blocking?(bob)).to be true
-    end
+      expect(sender)
+        .to be_blocking(bob)
 
-    it 'sends a block activity', :inline_jobs do
-      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
+      expect(a_request(:post, 'http://example.com/inbox'))
+        .to have_been_made.once
     end
   end
 end
diff --git a/spec/services/bulk_import_service_spec.rb b/spec/services/bulk_import_service_spec.rb
index 0197f81a4..f52fc4d7d 100644
--- a/spec/services/bulk_import_service_spec.rb
+++ b/spec/services/bulk_import_service_spec.rb
@@ -24,30 +24,19 @@ RSpec.describe BulkImportService do
         ].map { |data| import.rows.create!(data: data) }
       end
 
-      before do
-        account.follow!(Fabricate(:account))
-      end
+      before { account.follow!(Fabricate(:account)) }
 
-      it 'does not immediately change who the account follows' do
-        expect { subject.call(import) }.to_not(change { account.reload.active_relationships.to_a })
-      end
+      it 'does not immediately change who the account follows, enqueues workers, sends follow requests after worker run' do
+        expect { subject.call(import) }
+          .to_not(change { account.reload.active_relationships.to_a })
 
-      it 'enqueues workers for the expected rows' do
-        subject.call(import)
-        expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows.map(&:id))
-      end
+        expect(row_worker_job_args)
+          .to match_array(rows.map(&:id))
 
-      it 'requests to follow all the listed users once the workers have run' do
-        subject.call(import)
+        stub_resolve_account_and_drain_workers
 
-        resolve_account_service_double = instance_double(ResolveAccountService)
-        allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
-        allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
-        allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
-
-        Import::RowWorker.drain
-
-        expect(FollowRequest.includes(:target_account).where(account: account).map { |follow_request| follow_request.target_account.acct }).to contain_exactly('user@foo.bar', 'unknown@unknown.bar')
+        expect(FollowRequest.includes(:target_account).where(account: account).map { |follow_request| follow_request.target_account.acct })
+          .to contain_exactly('user@foo.bar', 'unknown@unknown.bar')
       end
     end
 
@@ -71,31 +60,20 @@ RSpec.describe BulkImportService do
         account.follow!(to_be_unfollowed)
       end
 
-      it 'unfollows user not present on list' do
-        subject.call(import)
-        expect(account.following?(to_be_unfollowed)).to be false
-      end
+      it 'updates the existing follow relationship as expected and unfollows user not on list, enqueues workers, sends follow reqs after worker run' do
+        expect { subject.call(import) }
+          .to change { Follow.where(account: account, target_account: followed).pick(:show_reblogs, :notify, :languages) }.from([true, false, nil]).to([false, true, ['en']])
 
-      it 'updates the existing follow relationship as expected' do
-        expect { subject.call(import) }.to change { Follow.where(account: account, target_account: followed).pick(:show_reblogs, :notify, :languages) }.from([true, false, nil]).to([false, true, ['en']])
-      end
+        expect(account)
+          .to_not be_following(to_be_unfollowed)
 
-      it 'enqueues workers for the expected rows' do
-        subject.call(import)
-        expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows[1..].map(&:id))
-      end
+        expect(row_worker_job_args)
+          .to match_array(rows[1..].map(&:id))
 
-      it 'requests to follow all the expected users once the workers have run' do
-        subject.call(import)
+        stub_resolve_account_and_drain_workers
 
-        resolve_account_service_double = instance_double(ResolveAccountService)
-        allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
-        allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
-        allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
-
-        Import::RowWorker.drain
-
-        expect(FollowRequest.includes(:target_account).where(account: account).map { |follow_request| follow_request.target_account.acct }).to contain_exactly('user@foo.bar', 'unknown@unknown.bar')
+        expect(FollowRequest.includes(:target_account).where(account: account).map { |follow_request| follow_request.target_account.acct })
+          .to contain_exactly('user@foo.bar', 'unknown@unknown.bar')
       end
     end
 
@@ -110,30 +88,19 @@ RSpec.describe BulkImportService do
         ].map { |data| import.rows.create!(data: data) }
       end
 
-      before do
-        account.block!(Fabricate(:account, username: 'already_blocked', domain: 'remote.org'))
-      end
+      before { account.block!(Fabricate(:account, username: 'already_blocked', domain: 'remote.org')) }
 
-      it 'does not immediately change who the account blocks' do
-        expect { subject.call(import) }.to_not(change { account.reload.blocking.to_a })
-      end
+      it 'does not immediately change who the account blocks, enqueues worker, blocks after run' do
+        expect { subject.call(import) }
+          .to_not(change { account.reload.blocking.to_a })
 
-      it 'enqueues workers for the expected rows' do
-        subject.call(import)
-        expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows.map(&:id))
-      end
+        expect(row_worker_job_args)
+          .to match_array(rows.map(&:id))
 
-      it 'blocks all the listed users once the workers have run' do
-        subject.call(import)
+        stub_resolve_account_and_drain_workers
 
-        resolve_account_service_double = instance_double(ResolveAccountService)
-        allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
-        allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
-        allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
-
-        Import::RowWorker.drain
-
-        expect(account.blocking.map(&:acct)).to contain_exactly('already_blocked@remote.org', 'user@foo.bar', 'unknown@unknown.bar')
+        expect(account.reload.blocking.map(&:acct))
+          .to contain_exactly('already_blocked@remote.org', 'user@foo.bar', 'unknown@unknown.bar')
       end
     end
 
@@ -157,27 +124,18 @@ RSpec.describe BulkImportService do
         account.block!(to_be_unblocked)
       end
 
-      it 'unblocks user not present on list' do
+      it 'unblocks user not present on list, enqueues worker, requests follow after run' do
         subject.call(import)
+
         expect(account.blocking?(to_be_unblocked)).to be false
-      end
 
-      it 'enqueues workers for the expected rows' do
-        subject.call(import)
-        expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows[1..].map(&:id))
-      end
+        expect(row_worker_job_args)
+          .to match_array(rows[1..].map(&:id))
 
-      it 'requests to follow all the expected users once the workers have run' do
-        subject.call(import)
+        stub_resolve_account_and_drain_workers
 
-        resolve_account_service_double = instance_double(ResolveAccountService)
-        allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
-        allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
-        allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
-
-        Import::RowWorker.drain
-
-        expect(account.blocking.map(&:acct)).to contain_exactly('blocked@foo.bar', 'user@foo.bar', 'unknown@unknown.bar')
+        expect(account.blocking.map(&:acct))
+          .to contain_exactly('blocked@foo.bar', 'user@foo.bar', 'unknown@unknown.bar')
       end
     end
 
@@ -192,30 +150,19 @@ RSpec.describe BulkImportService do
         ].map { |data| import.rows.create!(data: data) }
       end
 
-      before do
-        account.mute!(Fabricate(:account, username: 'already_muted', domain: 'remote.org'))
-      end
+      before { account.mute!(Fabricate(:account, username: 'already_muted', domain: 'remote.org')) }
 
-      it 'does not immediately change who the account blocks' do
-        expect { subject.call(import) }.to_not(change { account.reload.muting.to_a })
-      end
+      it 'does not immediately change who the account blocks, enqueures worker, mutes users after worker run' do
+        expect { subject.call(import) }
+          .to_not(change { account.reload.muting.to_a })
 
-      it 'enqueues workers for the expected rows' do
-        subject.call(import)
-        expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows.map(&:id))
-      end
+        expect(row_worker_job_args)
+          .to match_array(rows.map(&:id))
 
-      it 'mutes all the listed users once the workers have run' do
-        subject.call(import)
+        stub_resolve_account_and_drain_workers
 
-        resolve_account_service_double = instance_double(ResolveAccountService)
-        allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
-        allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
-        allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
-
-        Import::RowWorker.drain
-
-        expect(account.muting.map(&:acct)).to contain_exactly('already_muted@remote.org', 'user@foo.bar', 'unknown@unknown.bar')
+        expect(account.reload.muting.map(&:acct))
+          .to contain_exactly('already_muted@remote.org', 'user@foo.bar', 'unknown@unknown.bar')
       end
     end
 
@@ -239,31 +186,19 @@ RSpec.describe BulkImportService do
         account.mute!(to_be_unmuted)
       end
 
-      it 'updates the existing mute as expected' do
-        expect { subject.call(import) }.to change { Mute.where(account: account, target_account: muted).pick(:hide_notifications) }.from(false).to(true)
-      end
+      it 'updates the existing mute as expected and unblocks user not on list, and enqueues worker, and requests follow after worker run' do
+        expect { subject.call(import) }
+          .to change { Mute.where(account: account, target_account: muted).pick(:hide_notifications) }.from(false).to(true)
 
-      it 'unblocks user not present on list' do
-        subject.call(import)
         expect(account.muting?(to_be_unmuted)).to be false
-      end
 
-      it 'enqueues workers for the expected rows' do
-        subject.call(import)
-        expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows[1..].map(&:id))
-      end
+        expect(row_worker_job_args)
+          .to match_array(rows[1..].map(&:id))
 
-      it 'requests to follow all the expected users once the workers have run' do
-        subject.call(import)
+        stub_resolve_account_and_drain_workers
 
-        resolve_account_service_double = instance_double(ResolveAccountService)
-        allow(ResolveAccountService).to receive(:new).and_return(resolve_account_service_double)
-        allow(resolve_account_service_double).to receive(:call).with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
-        allow(resolve_account_service_double).to receive(:call).with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
-
-        Import::RowWorker.drain
-
-        expect(account.muting.map(&:acct)).to contain_exactly('muted@foo.bar', 'user@foo.bar', 'unknown@unknown.bar')
+        expect(account.muting.map(&:acct))
+          .to contain_exactly('muted@foo.bar', 'user@foo.bar', 'unknown@unknown.bar')
       end
     end
 
@@ -284,13 +219,11 @@ RSpec.describe BulkImportService do
         account.block_domain!('blocked.com')
       end
 
-      it 'blocks all the new domains' do
+      it 'blocks all the new domains and marks import finished' do
         subject.call(import)
-        expect(account.domain_blocks.pluck(:domain)).to contain_exactly('alreadyblocked.com', 'blocked.com', 'to-block.com')
-      end
 
-      it 'marks the import as finished' do
-        subject.call(import)
+        expect(account.domain_blocks.pluck(:domain))
+          .to contain_exactly('alreadyblocked.com', 'blocked.com', 'to-block.com')
         expect(import.reload.state_finished?).to be true
       end
     end
@@ -312,14 +245,13 @@ RSpec.describe BulkImportService do
         account.block_domain!('blocked.com')
       end
 
-      it 'blocks all the new domains' do
+      it 'blocks all the new domains and marks import finished' do
         subject.call(import)
-        expect(account.domain_blocks.pluck(:domain)).to contain_exactly('blocked.com', 'to-block.com')
-      end
 
-      it 'marks the import as finished' do
-        subject.call(import)
-        expect(import.reload.state_finished?).to be true
+        expect(account.domain_blocks.pluck(:domain))
+          .to contain_exactly('blocked.com', 'to-block.com')
+        expect(import.reload.state_finished?)
+          .to be true
       end
     end
 
@@ -347,22 +279,16 @@ RSpec.describe BulkImportService do
         account.bookmarks.create!(status: bookmarked)
       end
 
-      it 'enqueues workers for the expected rows' do
-        subject.call(import)
-        expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows.map(&:id))
-      end
-
-      it 'updates the bookmarks as expected once the workers have run' do
+      it 'enqueues workers for the expected rows and updates bookmarks after worker run' do
         subject.call(import)
 
-        service_double = instance_double(ActivityPub::FetchRemoteStatusService)
-        allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service_double)
-        allow(service_double).to receive(:call).with('https://domain.unknown/foo') { Fabricate(:status, uri: 'https://domain.unknown/foo') }
-        allow(service_double).to receive(:call).with('https://domain.unknown/private') { Fabricate(:status, uri: 'https://domain.unknown/private', visibility: :direct) }
+        expect(row_worker_job_args)
+          .to match_array(rows.map(&:id))
 
-        Import::RowWorker.drain
+        stub_fetch_remote_and_drain_workers
 
-        expect(account.bookmarks.map { |bookmark| bookmark.status.uri }).to contain_exactly(already_bookmarked.uri, status.uri, bookmarked.uri, 'https://domain.unknown/foo')
+        expect(account.bookmarks.map { |bookmark| bookmark.status.uri })
+          .to contain_exactly(already_bookmarked.uri, status.uri, bookmarked.uri, 'https://domain.unknown/foo')
       end
     end
 
@@ -390,23 +316,48 @@ RSpec.describe BulkImportService do
         account.bookmarks.create!(status: bookmarked)
       end
 
-      it 'enqueues workers for the expected rows' do
-        subject.call(import)
-        expect(Import::RowWorker.jobs.pluck('args').flatten).to match_array(rows.map(&:id))
-      end
-
-      it 'updates the bookmarks as expected once the workers have run' do
+      it 'enqueues workers for the expected rows and updates bookmarks' do
         subject.call(import)
 
-        service_double = instance_double(ActivityPub::FetchRemoteStatusService)
-        allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service_double)
-        allow(service_double).to receive(:call).with('https://domain.unknown/foo') { Fabricate(:status, uri: 'https://domain.unknown/foo') }
-        allow(service_double).to receive(:call).with('https://domain.unknown/private') { Fabricate(:status, uri: 'https://domain.unknown/private', visibility: :direct) }
+        expect(row_worker_job_args)
+          .to match_array(rows.map(&:id))
 
-        Import::RowWorker.drain
+        stub_fetch_remote_and_drain_workers
 
-        expect(account.bookmarks.map { |bookmark| bookmark.status.uri }).to contain_exactly(status.uri, bookmarked.uri, 'https://domain.unknown/foo')
+        expect(account.bookmarks.map { |bookmark| bookmark.status.uri })
+          .to contain_exactly(status.uri, bookmarked.uri, 'https://domain.unknown/foo')
       end
     end
+
+    def row_worker_job_args
+      Import::RowWorker
+        .jobs
+        .pluck('args')
+        .flatten
+    end
+
+    def stub_resolve_account_and_drain_workers
+      resolve_account_service_double = instance_double(ResolveAccountService)
+      allow(ResolveAccountService)
+        .to receive(:new)
+        .and_return(resolve_account_service_double)
+      allow(resolve_account_service_double)
+        .to receive(:call)
+        .with('user@foo.bar', any_args) { Fabricate(:account, username: 'user', domain: 'foo.bar', protocol: :activitypub) }
+      allow(resolve_account_service_double)
+        .to receive(:call)
+        .with('unknown@unknown.bar', any_args) { Fabricate(:account, username: 'unknown', domain: 'unknown.bar', protocol: :activitypub) }
+
+      Import::RowWorker.drain
+    end
+
+    def stub_fetch_remote_and_drain_workers
+      service_double = instance_double(ActivityPub::FetchRemoteStatusService)
+      allow(ActivityPub::FetchRemoteStatusService).to receive(:new).and_return(service_double)
+      allow(service_double).to receive(:call).with('https://domain.unknown/foo') { Fabricate(:status, uri: 'https://domain.unknown/foo') }
+      allow(service_double).to receive(:call).with('https://domain.unknown/private') { Fabricate(:status, uri: 'https://domain.unknown/private', visibility: :direct) }
+
+      Import::RowWorker.drain
+    end
   end
 end
diff --git a/spec/services/favourite_service_spec.rb b/spec/services/favourite_service_spec.rb
index c39362def..123e8699c 100644
--- a/spec/services/favourite_service_spec.rb
+++ b/spec/services/favourite_service_spec.rb
@@ -11,11 +11,9 @@ RSpec.describe FavouriteService do
     let(:bob)    { Fabricate(:account) }
     let(:status) { Fabricate(:status, account: bob) }
 
-    before do
-      subject.call(sender, status)
-    end
-
     it 'creates a favourite' do
+      subject.call(sender, status)
+
       expect(status.favourites.first).to_not be_nil
     end
   end
@@ -26,15 +24,16 @@ RSpec.describe FavouriteService do
 
     before do
       stub_request(:post, 'http://example.com/inbox').to_return(status: 200, body: '', headers: {})
+    end
+
+    it 'creates a favourite and sends like activity', :inline_jobs do
       subject.call(sender, status)
-    end
 
-    it 'creates a favourite' do
-      expect(status.favourites.first).to_not be_nil
-    end
+      expect(status.favourites.first)
+        .to_not be_nil
 
-    it 'sends a like activity', :inline_jobs do
-      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
+      expect(a_request(:post, 'http://example.com/inbox'))
+        .to have_been_made.once
     end
   end
 end
diff --git a/spec/services/follow_service_spec.rb b/spec/services/follow_service_spec.rb
index 0c4cd6004..bbd8a6f99 100644
--- a/spec/services/follow_service_spec.rb
+++ b/spec/services/follow_service_spec.rb
@@ -143,15 +143,16 @@ RSpec.describe FollowService do
 
     before do
       stub_request(:post, 'http://example.com/inbox').to_return(status: 200, body: '', headers: {})
+    end
+
+    it 'creates follow request and sends an activity to inbox', :inline_jobs do
       subject.call(sender, bob)
-    end
 
-    it 'creates follow request' do
-      expect(FollowRequest.find_by(account: sender, target_account: bob)).to_not be_nil
-    end
+      expect(FollowRequest.find_by(account: sender, target_account: bob))
+        .to_not be_nil
 
-    it 'sends a follow activity to the inbox', :inline_jobs do
-      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
+      expect(a_request(:post, 'http://example.com/inbox'))
+        .to have_been_made.once
     end
   end
 end
diff --git a/spec/services/process_mentions_service_spec.rb b/spec/services/process_mentions_service_spec.rb
index 2c202d3e5..3cc83d82f 100644
--- a/spec/services/process_mentions_service_spec.rb
+++ b/spec/services/process_mentions_service_spec.rb
@@ -16,20 +16,25 @@ RSpec.describe ProcessMentionsService do
     before do
       account.block!(individually_blocked_account)
       account.domain_blocks.create!(domain: domain_blocked_account.domain)
-
-      subject.call(status)
     end
 
-    it 'creates a mention to the non-blocked account' do
-      expect(non_blocked_account.mentions.where(status: status).count).to eq 1
+    it 'creates a mention to the non-blocked account but not the individually or domain blocked accounts' do
+      expect { subject.call(status) }
+        .to create_mention_for_non_blocked
+        .and skip_mention_for_individual
+        .and skip_mention_for_domain_blocked
     end
 
-    it 'does not create a mention to the individually blocked account' do
-      expect(individually_blocked_account.mentions.where(status: status).count).to eq 0
+    def create_mention_for_non_blocked
+      change { non_blocked_account.mentions.where(status: status).count }.to(1)
     end
 
-    it 'does not create a mention to the domain-blocked account' do
-      expect(domain_blocked_account.mentions.where(status: status).count).to eq 0
+    def skip_mention_for_individual
+      not_change { individually_blocked_account.mentions.where(status: status).count }.from(0)
+    end
+
+    def skip_mention_for_domain_blocked
+      not_change { domain_blocked_account.mentions.where(status: status).count }.from(0)
     end
   end
 
@@ -40,11 +45,9 @@ RSpec.describe ProcessMentionsService do
       context 'with a valid remote user' do
         let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
 
-        before do
-          subject.call(status)
-        end
-
         it 'creates a mention' do
+          subject.call(status)
+
           expect(remote_user.mentions.where(status: status).count).to eq 1
         end
       end
@@ -53,11 +56,9 @@ RSpec.describe ProcessMentionsService do
         let!(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
         let(:status)       { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct} @#{remote_user.acct} @#{remote_user.acct}", visibility: :public) }
 
-        before do
-          subject.call(status, save_records: false)
-        end
-
         it 'creates exactly one mention' do
+          subject.call(status, save_records: false)
+
           expect(status.mentions.size).to eq 1
         end
       end
@@ -66,11 +67,9 @@ RSpec.describe ProcessMentionsService do
         let!(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') }
         let!(:status) { Fabricate(:status, account: account, text: 'Hello @sneak@hæresiar.ch') }
 
-        before do
-          subject.call(status)
-        end
-
         it 'creates a mention' do
+          subject.call(status)
+
           expect(remote_user.mentions.where(status: status).count).to eq 1
         end
       end
@@ -79,11 +78,9 @@ RSpec.describe ProcessMentionsService do
         let!(:remote_user) { Fabricate(:account, username: 'foo', protocol: :activitypub, domain: 'xn--y9a3aq.xn--y9a3aq', inbox_url: 'http://example.com/inbox') }
         let!(:status) { Fabricate(:status, account: account, text: 'Hello @foo@հայ.հայ') }
 
-        before do
-          subject.call(status)
-        end
-
         it 'creates a mention' do
+          subject.call(status)
+
           expect(remote_user.mentions.where(status: status).count).to eq 1
         end
       end
@@ -95,10 +92,11 @@ RSpec.describe ProcessMentionsService do
       before do
         stub_request(:get, 'https://example.com/.well-known/host-meta').to_return(status: 404)
         stub_request(:get, 'https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com').to_return(status: 500)
-        subject.call(status)
       end
 
       it 'creates a mention' do
+        subject.call(status)
+
         expect(remote_user.mentions.where(status: status).count).to eq 1
       end
     end
diff --git a/spec/services/reject_follow_service_spec.rb b/spec/services/reject_follow_service_spec.rb
index d2c7a0020..eec0d6c1e 100644
--- a/spec/services/reject_follow_service_spec.rb
+++ b/spec/services/reject_follow_service_spec.rb
@@ -10,17 +10,15 @@ RSpec.describe RejectFollowService do
   describe 'local' do
     let(:bob) { Fabricate(:account) }
 
-    before do
-      FollowRequest.create(account: bob, target_account: sender)
+    before { FollowRequest.create(account: bob, target_account: sender) }
+
+    it 'removes follow request and does not create relation' do
       subject.call(bob, sender)
-    end
 
-    it 'removes follow request' do
-      expect(bob.requested?(sender)).to be false
-    end
-
-    it 'does not create follow relation' do
-      expect(bob.following?(sender)).to be false
+      expect(bob)
+        .to_not be_requested(sender)
+      expect(bob)
+        .to_not be_following(sender)
     end
   end
 
@@ -30,19 +28,17 @@ RSpec.describe RejectFollowService do
     before do
       FollowRequest.create(account: bob, target_account: sender)
       stub_request(:post, bob.inbox_url).to_return(status: 200)
+    end
+
+    it 'removes follow request, does not create relation, sends reject activity', :inline_jobs do
       subject.call(bob, sender)
-    end
 
-    it 'removes follow request' do
-      expect(bob.requested?(sender)).to be false
-    end
-
-    it 'does not create follow relation' do
-      expect(bob.following?(sender)).to be false
-    end
-
-    it 'sends a reject activity', :inline_jobs do
-      expect(a_request(:post, bob.inbox_url)).to have_been_made.once
+      expect(bob)
+        .to_not be_requested(sender)
+      expect(bob)
+        .to_not be_following(sender)
+      expect(a_request(:post, bob.inbox_url))
+        .to have_been_made.once
     end
   end
 end
diff --git a/spec/services/remove_from_followers_service_spec.rb b/spec/services/remove_from_followers_service_spec.rb
index 515600096..381daf1a5 100644
--- a/spec/services/remove_from_followers_service_spec.rb
+++ b/spec/services/remove_from_followers_service_spec.rb
@@ -10,13 +10,13 @@ RSpec.describe RemoveFromFollowersService do
   describe 'local' do
     let(:sender) { Fabricate(:account, username: 'alice') }
 
-    before do
-      Follow.create(account: sender, target_account: bob)
-      subject.call(bob, sender)
-    end
+    before { Follow.create(account: sender, target_account: bob) }
 
     it 'does not create follow relation' do
-      expect(bob.followed_by?(sender)).to be false
+      subject.call(bob, sender)
+
+      expect(bob)
+        .to_not be_followed_by(sender)
     end
   end
 
@@ -26,15 +26,16 @@ RSpec.describe RemoveFromFollowersService do
     before do
       Follow.create(account: sender, target_account: bob)
       stub_request(:post, sender.inbox_url).to_return(status: 200)
+    end
+
+    it 'does not create follow relation and sends reject activity', :inline_jobs do
       subject.call(bob, sender)
-    end
 
-    it 'does not create follow relation' do
-      expect(bob.followed_by?(sender)).to be false
-    end
+      expect(bob)
+        .to_not be_followed_by(sender)
 
-    it 'sends a reject activity', :inline_jobs do
-      expect(a_request(:post, sender.inbox_url)).to have_been_made.once
+      expect(a_request(:post, sender.inbox_url))
+        .to have_been_made.once
     end
   end
 end
diff --git a/spec/services/remove_status_service_spec.rb b/spec/services/remove_status_service_spec.rb
index 08f519b53..f2b46f05b 100644
--- a/spec/services/remove_status_service_spec.rb
+++ b/spec/services/remove_status_service_spec.rb
@@ -28,42 +28,38 @@ RSpec.describe RemoveStatusService, :inline_jobs do
       Fabricate(:status, account: bill, reblog: status, uri: 'hoge')
     end
 
-    it 'removes status from author\'s home feed' do
-      subject.call(status)
-      expect(HomeFeed.new(alice).get(10).pluck(:id)).to_not include(status.id)
-    end
-
-    it 'removes status from local follower\'s home feed' do
-      subject.call(status)
-      expect(HomeFeed.new(jeff).get(10).pluck(:id)).to_not include(status.id)
-    end
-
-    it 'publishes to public media timeline' do
+    it 'removes status from notifications and from author and local follower home feeds, publishes to media timeline, sends delete activities' do
       allow(redis).to receive(:publish).with(any_args)
 
-      subject.call(status)
+      expect { subject.call(status) }
+        .to remove_status_from_notifications
 
-      expect(redis).to have_received(:publish).with('timeline:public:media', Oj.dump(event: :delete, payload: status.id.to_s))
-    end
+      expect(home_feed_ids(alice))
+        .to_not include(status.id)
+      expect(home_feed_ids(jeff))
+        .to_not include(status.id)
 
-    it 'sends Delete activity to followers' do
-      subject.call(status)
+      expect(redis)
+        .to have_received(:publish).with('timeline:public:media', Oj.dump(event: :delete, payload: status.id.to_s))
 
       expect(delete_delivery(hank, status))
         .to have_been_made.once
-    end
-
-    it 'sends Delete activity to rebloggers' do
-      subject.call(status)
 
       expect(delete_delivery(bill, status))
         .to have_been_made.once
     end
 
-    it 'remove status from notifications' do
-      expect { subject.call(status) }.to change {
-        Notification.where(activity_type: 'Favourite', from_account: jeff, account: alice).count
-      }.from(1).to(0)
+    def home_feed_ids(personage)
+      HomeFeed
+        .new(personage)
+        .get(10)
+        .pluck(:id)
+    end
+
+    def remove_status_from_notifications
+      change { Notification.where(activity_type: 'Favourite', from_account: jeff, account: alice).count }
+        .from(1)
+        .to(0)
     end
 
     def delete_delivery(target, status)
diff --git a/spec/services/report_service_spec.rb b/spec/services/report_service_spec.rb
index 6518c5c27..4659e1c7a 100644
--- a/spec/services/report_service_spec.rb
+++ b/spec/services/report_service_spec.rb
@@ -31,14 +31,13 @@ RSpec.describe ReportService do
     context 'when forward is true', :inline_jobs do
       let(:forward) { true }
 
-      it 'sends ActivityPub payload when forward is true' do
-        subject.call(source_account, remote_account, forward: forward)
-        expect(a_request(:post, 'http://example.com/inbox')).to have_been_made
-      end
-
-      it 'has an uri' do
+      it 'has a URI and sends ActivityPub payload' do
         report = subject.call(source_account, remote_account, forward: forward)
-        expect(report.uri).to_not be_nil
+
+        expect(report.uri)
+          .to_not be_nil
+        expect(a_request(:post, 'http://example.com/inbox'))
+          .to have_been_made
       end
 
       context 'when reporting a reply on a different remote server' do
@@ -122,13 +121,12 @@ RSpec.describe ReportService do
         status.mentions.create(account: source_account)
       end
 
-      it 'creates a report' do
-        expect { subject.call }.to change { target_account.targeted_reports.count }.from(0).to(1)
-      end
+      it 'creates a report and attaches the DM to the report' do
+        expect { subject.call }
+          .to change { target_account.targeted_reports.count }.from(0).to(1)
 
-      it 'attaches the DM to the report' do
-        subject.call
-        expect(target_account.targeted_reports.pluck(:status_ids)).to eq [[status.id]]
+        expect(target_account.targeted_reports.pluck(:status_ids))
+          .to eq [[status.id]]
       end
     end
 
@@ -146,13 +144,12 @@ RSpec.describe ReportService do
           status.mentions.create(account: source_account)
         end
 
-        it 'creates a report' do
-          expect { subject.call }.to change { target_account.targeted_reports.count }.from(0).to(1)
-        end
+        it 'creates a report and attaches DM to report' do
+          expect { subject.call }
+            .to change { target_account.targeted_reports.count }.from(0).to(1)
 
-        it 'attaches the DM to the report' do
-          subject.call
-          expect(target_account.targeted_reports.pluck(:status_ids)).to eq [[status.id]]
+          expect(target_account.targeted_reports.pluck(:status_ids))
+            .to eq [[status.id]]
         end
       end
 
diff --git a/spec/services/resolve_account_service_spec.rb b/spec/services/resolve_account_service_spec.rb
index a856e019a..1bd4e9a8e 100644
--- a/spec/services/resolve_account_service_spec.rb
+++ b/spec/services/resolve_account_service_spec.rb
@@ -22,37 +22,38 @@ RSpec.describe ResolveAccountService do
       context 'when domain is banned' do
         before { Fabricate(:domain_block, domain: 'ap.example.com', severity: :suspend) }
 
-        it 'does not return an account' do
-          expect(subject.call('foo@ap.example.com', skip_webfinger: true)).to be_nil
-        end
-
-        it 'does not make a webfinger query' do
-          subject.call('foo@ap.example.com', skip_webfinger: true)
-          expect(a_request(:get, 'https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com')).to_not have_been_made
+        it 'does not return an account or make a webfinger query' do
+          expect(subject.call('foo@ap.example.com', skip_webfinger: true))
+            .to be_nil
+          expect(webfinger_discovery_request)
+            .to_not have_been_made
         end
       end
 
       context 'when domain is not banned' do
-        it 'returns the expected account' do
-          expect(subject.call('foo@ap.example.com', skip_webfinger: true)).to eq remote_account
-        end
-
-        it 'does not make a webfinger query' do
-          subject.call('foo@ap.example.com', skip_webfinger: true)
-          expect(a_request(:get, 'https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com')).to_not have_been_made
+        it 'returns the expected account and does not make a webfinger query' do
+          expect(subject.call('foo@ap.example.com', skip_webfinger: true))
+            .to eq remote_account
+          expect(webfinger_discovery_request)
+            .to_not have_been_made
         end
       end
     end
 
     context 'when account is not known' do
-      it 'does not return an account' do
-        expect(subject.call('foo@ap.example.com', skip_webfinger: true)).to be_nil
+      it 'does not return an account and does not make webfinger query' do
+        expect(subject.call('foo@ap.example.com', skip_webfinger: true))
+          .to be_nil
+        expect(webfinger_discovery_request)
+          .to_not have_been_made
       end
+    end
 
-      it 'does not make a webfinger query' do
-        subject.call('foo@ap.example.com', skip_webfinger: true)
-        expect(a_request(:get, 'https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com')).to_not have_been_made
-      end
+    def webfinger_discovery_request
+      a_request(
+        :get,
+        'https://ap.example.com/.well-known/webfinger?resource=acct:foo@ap.example.com'
+      )
     end
   end
 
@@ -84,13 +85,11 @@ RSpec.describe ResolveAccountService do
         allow(AccountDeletionWorker).to receive(:perform_async)
       end
 
-      it 'returns nil' do
-        expect(subject.call('hoge@example.com')).to be_nil
-      end
-
-      it 'queues account deletion worker' do
-        subject.call('hoge@example.com')
-        expect(AccountDeletionWorker).to have_received(:perform_async)
+      it 'returns nil and queues deletion worker' do
+        expect(subject.call('hoge@example.com'))
+          .to be_nil
+        expect(AccountDeletionWorker)
+          .to have_received(:perform_async)
       end
     end
 
@@ -110,9 +109,12 @@ RSpec.describe ResolveAccountService do
     it 'returns new remote account' do
       account = subject.call('Foo@redirected.example.com')
 
-      expect(account.activitypub?).to be true
-      expect(account.acct).to eq 'foo@ap.example.com'
-      expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
+      expect(account)
+        .to have_attributes(
+          activitypub?: true,
+          acct: 'foo@ap.example.com',
+          inbox_url: 'https://ap.example.com/users/foo/inbox'
+        )
     end
   end
 
@@ -125,9 +127,12 @@ RSpec.describe ResolveAccountService do
     it 'returns new remote account' do
       account = subject.call('Foo@redirected.example.com')
 
-      expect(account.activitypub?).to be true
-      expect(account.acct).to eq 'foo@ap.example.com'
-      expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
+      expect(account)
+        .to have_attributes(
+          activitypub?: true,
+          acct: 'foo@ap.example.com',
+          inbox_url: 'https://ap.example.com/users/foo/inbox'
+        )
     end
   end
 
@@ -161,9 +166,12 @@ RSpec.describe ResolveAccountService do
     it 'returns new remote account' do
       account = subject.call('foo@ap.example.com')
 
-      expect(account.activitypub?).to be true
-      expect(account.domain).to eq 'ap.example.com'
-      expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
+      expect(account)
+        .to have_attributes(
+          activitypub?: true,
+          domain: 'ap.example.com',
+          inbox_url: 'https://ap.example.com/users/foo/inbox'
+        )
     end
 
     context 'with multiple types' do
@@ -174,10 +182,13 @@ RSpec.describe ResolveAccountService do
       it 'returns new remote account' do
         account = subject.call('foo@ap.example.com')
 
-        expect(account.activitypub?).to be true
-        expect(account.domain).to eq 'ap.example.com'
-        expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
-        expect(account.actor_type).to eq 'Person'
+        expect(account)
+          .to have_attributes(
+            activitypub?: true,
+            domain: 'ap.example.com',
+            inbox_url: 'https://ap.example.com/users/foo/inbox',
+            actor_type: 'Person'
+          )
       end
     end
   end
@@ -186,20 +197,21 @@ RSpec.describe ResolveAccountService do
     let!(:duplicate) { Fabricate(:account, username: 'foo', domain: 'old.example.com', uri: 'https://ap.example.com/users/foo') }
     let!(:status)    { Fabricate(:status, account: duplicate, text: 'foo') }
 
-    it 'returns new remote account' do
+    it 'returns new remote account and merges accounts', :inline_jobs do
       account = subject.call('foo@ap.example.com')
 
-      expect(account.activitypub?).to be true
-      expect(account.domain).to eq 'ap.example.com'
-      expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
-      expect(account.uri).to eq 'https://ap.example.com/users/foo'
-    end
+      expect(account)
+        .to have_attributes(
+          activitypub?: true,
+          domain: 'ap.example.com',
+          inbox_url: 'https://ap.example.com/users/foo/inbox',
+          uri: 'https://ap.example.com/users/foo'
+        )
 
-    it 'merges accounts', :inline_jobs do
-      account = subject.call('foo@ap.example.com')
-
-      expect(status.reload.account_id).to eq account.id
-      expect(Account.where(uri: account.uri).count).to eq 1
+      expect(status.reload.account_id)
+        .to eq account.id
+      expect(Account.where(uri: account.uri).count)
+        .to eq 1
     end
   end
 
@@ -210,11 +222,15 @@ RSpec.describe ResolveAccountService do
     it 'returns new remote account' do
       account = subject.call('foo@ap.example.com')
 
-      expect(account.activitypub?).to be true
-      expect(account.domain).to eq 'ap.example.com'
-      expect(account.inbox_url).to eq 'https://ap.example.com/users/foo/inbox'
-      expect(account.uri).to eq 'https://ap.example.com/users/foo'
-      expect(status.reload.account).to eq(account)
+      expect(account)
+        .to have_attributes(
+          activitypub?: true,
+          domain: 'ap.example.com',
+          inbox_url: 'https://ap.example.com/users/foo/inbox',
+          uri: 'https://ap.example.com/users/foo'
+        )
+      expect(status.reload.account)
+        .to eq(account)
     end
   end
 
diff --git a/spec/services/resolve_url_service_spec.rb b/spec/services/resolve_url_service_spec.rb
index 80f2a5a4b..eaf00c1ed 100644
--- a/spec/services/resolve_url_service_spec.rb
+++ b/spec/services/resolve_url_service_spec.rb
@@ -51,12 +51,11 @@ RSpec.describe ResolveURLService do
           let(:url) { 'https://example.com/@foo/42' }
           let(:uri) { 'https://example.com/users/foo/statuses/42' }
 
-          it 'returns status by url' do
-            expect(subject.call(url, on_behalf_of: account)).to eq(status)
-          end
-
-          it 'returns status by uri' do
-            expect(subject.call(uri, on_behalf_of: account)).to eq(status)
+          it 'returns status by URL or URI' do
+            expect(subject.call(url, on_behalf_of: account))
+              .to eq(status)
+            expect(subject.call(uri, on_behalf_of: account))
+              .to eq(status)
           end
         end
 
@@ -75,12 +74,11 @@ RSpec.describe ResolveURLService do
           let(:url) { 'https://example.com/@foo/42' }
           let(:uri) { 'https://example.com/users/foo/statuses/42' }
 
-          it 'does not return the status by url' do
-            expect(subject.call(url, on_behalf_of: account)).to be_nil
-          end
-
-          it 'does not return the status by uri' do
-            expect(subject.call(uri, on_behalf_of: account)).to be_nil
+          it 'does not return the status by URL or URI' do
+            expect(subject.call(url, on_behalf_of: account))
+              .to be_nil
+            expect(subject.call(uri, on_behalf_of: account))
+              .to be_nil
           end
         end
 
@@ -107,22 +105,20 @@ RSpec.describe ResolveURLService do
           account.follow!(poster)
         end
 
-        it 'returns status by url' do
-          expect(subject.call(url, on_behalf_of: account)).to eq(status)
-        end
-
-        it 'returns status by uri' do
-          expect(subject.call(uri, on_behalf_of: account)).to eq(status)
+        it 'returns status by URL or URI' do
+          expect(subject.call(url, on_behalf_of: account))
+            .to eq(status)
+          expect(subject.call(uri, on_behalf_of: account))
+            .to eq(status)
         end
       end
 
       context 'when the account does not follow the poster' do
-        it 'does not return the status by url' do
-          expect(subject.call(url, on_behalf_of: account)).to be_nil
-        end
-
-        it 'does not return the status by uri' do
-          expect(subject.call(uri, on_behalf_of: account)).to be_nil
+        it 'does not return the status by URL or URI' do
+          expect(subject.call(url, on_behalf_of: account))
+            .to be_nil
+          expect(subject.call(uri, on_behalf_of: account))
+            .to be_nil
         end
       end
     end
diff --git a/spec/services/translate_status_service_spec.rb b/spec/services/translate_status_service_spec.rb
index 0779fbbe6..cd92fb8d1 100644
--- a/spec/services/translate_status_service_spec.rb
+++ b/spec/services/translate_status_service_spec.rb
@@ -32,20 +32,14 @@ RSpec.describe TranslateStatusService do
       allow(TranslationService).to receive_messages(configured?: true, configured: translation_service)
     end
 
-    it 'returns translated status content' do
-      expect(service.call(status, 'es').content).to eq '<p>Hola</p>'
-    end
-
-    it 'returns source language' do
-      expect(service.call(status, 'es').detected_source_language).to eq 'en'
-    end
-
-    it 'returns translation provider' do
-      expect(service.call(status, 'es').provider).to eq 'Dummy'
-    end
-
-    it 'returns original status' do
-      expect(service.call(status, 'es').status).to eq status
+    it 'returns translated status content and source language and provider and original status' do
+      expect(service.call(status, 'es'))
+        .to have_attributes(
+          content: '<p>Hola</p>',
+          detected_source_language: 'en',
+          provider: 'Dummy',
+          status: status
+        )
     end
 
     describe 'status has content with custom emoji' do
@@ -155,26 +149,16 @@ RSpec.describe TranslateStatusService do
         let!(:source_texts) { service.send(:source_texts) }
 
         it 'returns formatted poll options' do
-          expect(source_texts.size).to eq 3
-          expect(source_texts.values).to eq %w(<p>Hello</p> Blue Green)
-        end
-
-        it 'has a first key with content' do
-          expect(source_texts.keys.first).to eq :content
-        end
-
-        it 'has the first option in the second key with correct options' do
-          option1 = source_texts.keys.second
-          expect(option1).to be_a Poll::Option
-          expect(option1.id).to eq '0'
-          expect(option1.title).to eq 'Blue'
-        end
-
-        it 'has the second option in the third key with correct options' do
-          option2 = source_texts.keys.third
-          expect(option2).to be_a Poll::Option
-          expect(option2.id).to eq '1'
-          expect(option2.title).to eq 'Green'
+          expect(source_texts)
+            .to have_attributes(
+              size: 3,
+              values: %w(<p>Hello</p> Blue Green),
+              keys: contain_exactly(
+                eq(:content),
+                be_a(Poll::Option).and(have_attributes(id: '0', title: 'Blue')),
+                be_a(Poll::Option).and(have_attributes(id: '1', title: 'Green'))
+              )
+            )
         end
       end
     end
diff --git a/spec/services/unblock_domain_service_spec.rb b/spec/services/unblock_domain_service_spec.rb
index 405fe1cfd..daa1d480a 100644
--- a/spec/services/unblock_domain_service_spec.rb
+++ b/spec/services/unblock_domain_service_spec.rb
@@ -12,26 +12,32 @@ RSpec.describe UnblockDomainService do
     let!(:silenced) { Fabricate(:account, domain: 'example.com', silenced_at: domain_block.created_at) }
     let!(:suspended) { Fabricate(:account, domain: 'example.com', suspended_at: domain_block.created_at) }
 
-    it 'unsilences accounts and removes block' do
-      domain_block.update(severity: :silence)
+    context 'with severity of silence' do
+      before { domain_block.update(severity: :silence) }
 
-      subject.call(domain_block)
-      expect_deleted_domain_block
-      expect(silenced.reload.silenced?).to be false
-      expect(suspended.reload.suspended?).to be true
-      expect(independently_suspended.reload.suspended?).to be true
-      expect(independently_silenced.reload.silenced?).to be true
+      it 'unsilences accounts and removes block' do
+        subject.call(domain_block)
+
+        expect_deleted_domain_block
+        expect(silenced.reload.silenced?).to be false
+        expect(suspended.reload.suspended?).to be true
+        expect(independently_suspended.reload.suspended?).to be true
+        expect(independently_silenced.reload.silenced?).to be true
+      end
     end
 
-    it 'unsuspends accounts and removes block' do
-      domain_block.update(severity: :suspend)
+    context 'with severity of suspend' do
+      before { domain_block.update(severity: :suspend) }
 
-      subject.call(domain_block)
-      expect_deleted_domain_block
-      expect(suspended.reload.suspended?).to be false
-      expect(silenced.reload.silenced?).to be false
-      expect(independently_suspended.reload.suspended?).to be true
-      expect(independently_silenced.reload.silenced?).to be true
+      it 'unsuspends accounts and removes block' do
+        subject.call(domain_block)
+
+        expect_deleted_domain_block
+        expect(suspended.reload.suspended?).to be false
+        expect(silenced.reload.silenced?).to be false
+        expect(independently_suspended.reload.suspended?).to be true
+        expect(independently_silenced.reload.silenced?).to be true
+      end
     end
   end
 
diff --git a/spec/services/unblock_service_spec.rb b/spec/services/unblock_service_spec.rb
index 6132e7441..a2c5188f0 100644
--- a/spec/services/unblock_service_spec.rb
+++ b/spec/services/unblock_service_spec.rb
@@ -10,13 +10,13 @@ RSpec.describe UnblockService do
   describe 'local' do
     let(:bob) { Fabricate(:account) }
 
-    before do
-      sender.block!(bob)
-      subject.call(sender, bob)
-    end
+    before { sender.block!(bob) }
 
     it 'destroys the blocking relation' do
-      expect(sender.blocking?(bob)).to be false
+      subject.call(sender, bob)
+
+      expect(sender)
+        .to_not be_blocking(bob)
     end
   end
 
@@ -26,15 +26,15 @@ RSpec.describe UnblockService do
     before do
       sender.block!(bob)
       stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
+    end
+
+    it 'destroys the blocking relation and sends unblock activity', :inline_jobs do
       subject.call(sender, bob)
-    end
 
-    it 'destroys the blocking relation' do
-      expect(sender.blocking?(bob)).to be false
-    end
-
-    it 'sends an unblock activity', :inline_jobs do
-      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
+      expect(sender)
+        .to_not be_blocking(bob)
+      expect(a_request(:post, 'http://example.com/inbox'))
+        .to have_been_made.once
     end
   end
 end
diff --git a/spec/services/unfollow_service_spec.rb b/spec/services/unfollow_service_spec.rb
index 0c206c4b9..6cf24ca5e 100644
--- a/spec/services/unfollow_service_spec.rb
+++ b/spec/services/unfollow_service_spec.rb
@@ -10,13 +10,13 @@ RSpec.describe UnfollowService do
   describe 'local' do
     let(:bob) { Fabricate(:account, username: 'bob') }
 
-    before do
-      sender.follow!(bob)
-      subject.call(sender, bob)
-    end
+    before { sender.follow!(bob) }
 
     it 'destroys the following relation' do
-      expect(sender.following?(bob)).to be false
+      subject.call(sender, bob)
+
+      expect(sender)
+        .to_not be_following(bob)
     end
   end
 
@@ -26,15 +26,15 @@ RSpec.describe UnfollowService do
     before do
       sender.follow!(bob)
       stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
+    end
+
+    it 'destroys the following relation and sends unfollow activity' do
       subject.call(sender, bob)
-    end
 
-    it 'destroys the following relation' do
-      expect(sender.following?(bob)).to be false
-    end
-
-    it 'sends an unfollow activity' do
-      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
+      expect(sender)
+        .to_not be_following(bob)
+      expect(a_request(:post, 'http://example.com/inbox'))
+        .to have_been_made.once
     end
   end
 
@@ -44,15 +44,15 @@ RSpec.describe UnfollowService do
     before do
       bob.follow!(sender)
       stub_request(:post, 'http://example.com/inbox').to_return(status: 200)
+    end
+
+    it 'destroys the following relation and sends a reject activity' do
       subject.call(bob, sender)
-    end
 
-    it 'destroys the following relation' do
-      expect(bob.following?(sender)).to be false
-    end
-
-    it 'sends a reject activity' do
-      expect(a_request(:post, 'http://example.com/inbox')).to have_been_made.once
+      expect(sender)
+        .to_not be_following(bob)
+      expect(a_request(:post, 'http://example.com/inbox'))
+        .to have_been_made.once
     end
   end
 end
diff --git a/spec/services/update_account_service_spec.rb b/spec/services/update_account_service_spec.rb
index d066db481..f9059af07 100644
--- a/spec/services/update_account_service_spec.rb
+++ b/spec/services/update_account_service_spec.rb
@@ -18,23 +18,19 @@ RSpec.describe UpdateAccountService do
       FollowService.new.call(alice, account)
       FollowService.new.call(bob, account)
       FollowService.new.call(eve, account)
+    end
 
+    it 'auto accepts pending follow requests from appropriate accounts' do
       subject.call(account, { locked: false })
-    end
 
-    it 'auto-accepts pending follow requests' do
-      expect(alice.following?(account)).to be true
-      expect(alice.requested?(account)).to be false
-    end
+      expect(alice).to be_following(account)
+      expect(alice).to_not be_requested(account)
 
-    it 'does not auto-accept pending follow requests from silenced users' do
-      expect(bob.following?(account)).to be false
-      expect(bob.requested?(account)).to be true
-    end
+      expect(bob).to_not be_following(account)
+      expect(bob).to be_requested(account)
 
-    it 'auto-accepts pending follow requests from muted users so as to not leak mute' do
-      expect(eve.following?(account)).to be true
-      expect(eve.requested?(account)).to be false
+      expect(eve).to be_following(account)
+      expect(eve).to_not be_requested(account)
     end
   end
 end
diff --git a/spec/services/update_status_service_spec.rb b/spec/services/update_status_service_spec.rb
index de06fb13c..7c92adeff 100644
--- a/spec/services/update_status_service_spec.rb
+++ b/spec/services/update_status_service_spec.rb
@@ -10,15 +10,15 @@ RSpec.describe UpdateStatusService do
 
     before do
       allow(ActivityPub::DistributionWorker).to receive(:perform_async)
+    end
+
+    it 'does not create an edit or notify anyone' do
       subject.call(status, status.account_id, text: 'Foo')
-    end
 
-    it 'does not create an edit' do
-      expect(status.reload.edits).to be_empty
-    end
-
-    it 'does not notify anyone' do
-      expect(ActivityPub::DistributionWorker).to_not have_received(:perform_async)
+      expect(status.reload.edits)
+        .to be_empty
+      expect(ActivityPub::DistributionWorker)
+        .to_not have_received(:perform_async)
     end
   end
 
@@ -28,18 +28,16 @@ RSpec.describe UpdateStatusService do
 
     before do
       PreviewCardsStatus.create(status: status, preview_card: preview_card)
+    end
+
+    it 'updates text, resets card, saves edit history' do
       subject.call(status, status.account_id, text: 'Bar')
-    end
 
-    it 'updates text' do
-      expect(status.reload.text).to eq 'Bar'
-    end
-
-    it 'resets preview card' do
-      expect(status.reload.preview_card).to be_nil
-    end
-
-    it 'saves edit history' do
+      expect(status.reload)
+        .to have_attributes(
+          text: 'Bar',
+          preview_card: be_nil
+        )
       expect(status.edits.ordered.pluck(:text)).to eq %w(Foo Bar)
     end
   end
@@ -50,15 +48,15 @@ RSpec.describe UpdateStatusService do
 
     before do
       PreviewCardsStatus.create(status: status, preview_card: preview_card)
+    end
+
+    it 'updates content warning and saves history' do
       subject.call(status, status.account_id, text: 'Foo', spoiler_text: 'Bar')
-    end
 
-    it 'updates content warning' do
-      expect(status.reload.spoiler_text).to eq 'Bar'
-    end
-
-    it 'saves edit history' do
-      expect(status.edits.ordered.pluck(:text, :spoiler_text)).to eq [['Foo', ''], ['Foo', 'Bar']]
+      expect(status.reload.spoiler_text)
+        .to eq 'Bar'
+      expect(status.edits.ordered.pluck(:text, :spoiler_text))
+        .to eq [['Foo', ''], ['Foo', 'Bar']]
     end
   end
 
@@ -69,23 +67,19 @@ RSpec.describe UpdateStatusService do
 
     before do
       status.media_attachments << detached_media_attachment
+    end
+
+    it 'updates media attachments, handles attachments, saves history' do
       subject.call(status, status.account_id, text: 'Foo', media_ids: [attached_media_attachment.id.to_s])
-    end
 
-    it 'updates media attachments' do
-      expect(status.ordered_media_attachments).to eq [attached_media_attachment]
-    end
-
-    it 'does not detach detached media attachments' do
-      expect(detached_media_attachment.reload.status_id).to eq status.id
-    end
-
-    it 'attaches attached media attachments' do
-      expect(attached_media_attachment.reload.status_id).to eq status.id
-    end
-
-    it 'saves edit history' do
-      expect(status.edits.ordered.pluck(:ordered_media_attachment_ids)).to eq [[detached_media_attachment.id], [attached_media_attachment.id]]
+      expect(status.ordered_media_attachments)
+        .to eq [attached_media_attachment]
+      expect(detached_media_attachment.reload.status_id)
+        .to eq status.id
+      expect(attached_media_attachment.reload.status_id)
+        .to eq status.id
+      expect(status.edits.ordered.pluck(:ordered_media_attachment_ids))
+        .to eq [[detached_media_attachment.id], [attached_media_attachment.id]]
     end
   end
 
@@ -95,19 +89,18 @@ RSpec.describe UpdateStatusService do
 
     before do
       status.media_attachments << media_attachment
+    end
+
+    it 'does not detach media attachment, updates description, and saves history' do
       subject.call(status, status.account_id, text: 'Foo', media_ids: [media_attachment.id.to_s], media_attributes: [{ id: media_attachment.id, description: 'New description' }])
-    end
 
-    it 'does not detach media attachment' do
-      expect(media_attachment.reload.status_id).to eq status.id
-    end
-
-    it 'updates the media attachment description' do
-      expect(media_attachment.reload.description).to eq 'New description'
-    end
-
-    it 'saves edit history' do
-      expect(status.edits.ordered.map { |edit| edit.ordered_media_attachments.map(&:description) }).to eq [['Old description'], ['New description']]
+      expect(media_attachment.reload)
+        .to have_attributes(
+          status_id: status.id,
+          description: 'New description'
+        )
+      expect(status.edits.ordered.map { |edit| edit.ordered_media_attachments.map(&:description) })
+        .to eq [['Old description'], ['New description']]
     end
   end
 
@@ -120,28 +113,27 @@ RSpec.describe UpdateStatusService do
     before do
       status.update(poll: poll)
       VoteService.new.call(voter, poll, [0])
+    end
+
+    it 'updates poll, resets votes, saves history, requeues notifications' do
       subject.call(status, status.account_id, text: 'Foo', poll: { options: %w(Bar Baz Foo), expires_in: 5.days.to_i })
-    end
 
-    it 'updates poll' do
       poll = status.poll.reload
-      expect(poll.options).to eq %w(Bar Baz Foo)
-    end
 
-    it 'resets votes' do
-      poll = status.poll.reload
-      expect(poll.votes_count).to eq 0
-      expect(poll.votes.count).to eq 0
-      expect(poll.cached_tallies).to eq [0, 0, 0]
-    end
+      expect(poll)
+        .to have_attributes(
+          options: %w(Bar Baz Foo),
+          votes_count: 0,
+          cached_tallies: [0, 0, 0]
+        )
+      expect(poll.votes.count)
+        .to eq(0)
 
-    it 'saves edit history' do
-      expect(status.edits.ordered.pluck(:poll_options)).to eq [%w(Foo Bar), %w(Bar Baz Foo)]
-    end
+      expect(status.edits.ordered.pluck(:poll_options))
+        .to eq [%w(Foo Bar), %w(Bar Baz Foo)]
 
-    it 'requeues expiration notification' do
-      poll = status.poll.reload
-      expect(PollExpirationNotifyWorker).to have_enqueued_sidekiq_job(poll.id).at(poll.expires_at + 5.minutes)
+      expect(PollExpirationNotifyWorker)
+        .to have_enqueued_sidekiq_job(poll.id).at(poll.expires_at + 5.minutes)
     end
   end
 
@@ -151,16 +143,13 @@ RSpec.describe UpdateStatusService do
     let!(:bob) { Fabricate(:account, username: 'bob') }
     let!(:status) { PostStatusService.new.call(account, text: 'Hello @alice') }
 
-    before do
+    it 'changes mentions and keeps old as silent' do
       subject.call(status, status.account_id, text: 'Hello @bob')
-    end
 
-    it 'changes mentions' do
-      expect(status.active_mentions.pluck(:account_id)).to eq [bob.id]
-    end
-
-    it 'keeps old mentions as silent mentions' do
-      expect(status.mentions.pluck(:account_id)).to contain_exactly(alice.id, bob.id)
+      expect(status.active_mentions.pluck(:account_id))
+        .to eq [bob.id]
+      expect(status.mentions.pluck(:account_id))
+        .to contain_exactly(alice.id, bob.id)
     end
   end
 
@@ -168,11 +157,9 @@ RSpec.describe UpdateStatusService do
     let!(:account) { Fabricate(:account) }
     let!(:status) { PostStatusService.new.call(account, text: 'Hello #foo') }
 
-    before do
-      subject.call(status, status.account_id, text: 'Hello #bar')
-    end
-
     it 'changes tags' do
+      subject.call(status, status.account_id, text: 'Hello #bar')
+
       expect(status.tags.pluck(:name)).to eq %w(bar)
     end
   end

From ebdeac0731c31b73438aaa369a234d9ac5798b43 Mon Sep 17 00:00:00 2001
From: Matt Jankowski <matt@jankowski.online>
Date: Fri, 4 Oct 2024 10:15:14 -0400
Subject: [PATCH 67/70] Add coverage for missing status scenario in
 NotificationMailer (#32256)

---
 app/mailers/notification_mailer.rb       |  2 +-
 spec/mailers/notification_mailer_spec.rb | 14 ++++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/app/mailers/notification_mailer.rb b/app/mailers/notification_mailer.rb
index 4c374f5d5..a20992dcb 100644
--- a/app/mailers/notification_mailer.rb
+++ b/app/mailers/notification_mailer.rb
@@ -86,7 +86,7 @@ class NotificationMailer < ApplicationMailer
   end
 
   def thread_by_conversation!
-    return if @status.conversation.nil?
+    return if @status&.conversation.nil?
 
     conversation_message_id = "<conversation-#{@status.conversation.id}.#{@status.conversation.created_at.to_date}@#{Rails.configuration.x.local_domain}>"
 
diff --git a/spec/mailers/notification_mailer_spec.rb b/spec/mailers/notification_mailer_spec.rb
index 4c6107d9f..d97c01858 100644
--- a/spec/mailers/notification_mailer_spec.rb
+++ b/spec/mailers/notification_mailer_spec.rb
@@ -14,6 +14,17 @@ RSpec.describe NotificationMailer do
     end
   end
 
+  shared_examples 'delivery without status' do
+    context 'when notification target_status is missing' do
+      before { allow(notification).to receive(:target_status).and_return(nil) }
+
+      it 'does not deliver mail' do
+        emails = capture_emails { mail.deliver_now }
+        expect(emails).to be_empty
+      end
+    end
+  end
+
   let(:receiver)       { Fabricate(:user, account_attributes: { username: 'alice' }) }
   let(:sender)         { Fabricate(:account, username: 'bob') }
   let(:foreign_status) { Fabricate(:status, account: sender, text: 'The body of the foreign status') }
@@ -37,6 +48,7 @@ RSpec.describe NotificationMailer do
     end
 
     include_examples 'delivery to non functional user'
+    include_examples 'delivery without status'
   end
 
   describe 'follow' do
@@ -75,6 +87,7 @@ RSpec.describe NotificationMailer do
     end
 
     include_examples 'delivery to non functional user'
+    include_examples 'delivery without status'
   end
 
   describe 'reblog' do
@@ -95,6 +108,7 @@ RSpec.describe NotificationMailer do
     end
 
     include_examples 'delivery to non functional user'
+    include_examples 'delivery without status'
   end
 
   describe 'follow_request' do

From 1f720366e94c175f171156a86d65787d6f0d7ba6 Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Fri, 4 Oct 2024 16:29:23 +0200
Subject: [PATCH 68/70] Fix notification push notifications not including the
 author's username (#32254)

---
 app/javascript/mastodon/locales/en.json                   | 1 +
 .../mastodon/service_worker/web_push_locales.js           | 8 +++++++-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/app/javascript/mastodon/locales/en.json b/app/javascript/mastodon/locales/en.json
index 1e7874535..7f8dc7477 100644
--- a/app/javascript/mastodon/locales/en.json
+++ b/app/javascript/mastodon/locales/en.json
@@ -516,6 +516,7 @@
   "notification.label.private_reply": "Private reply",
   "notification.label.reply": "Reply",
   "notification.mention": "Mention",
+  "notification.mentioned_you": "{name} mentioned you",
   "notification.moderation-warning.learn_more": "Learn more",
   "notification.moderation_warning": "You have received a moderation warning",
   "notification.moderation_warning.action_delete_statuses": "Some of your posts have been removed.",
diff --git a/app/javascript/mastodon/service_worker/web_push_locales.js b/app/javascript/mastodon/service_worker/web_push_locales.js
index 89ae20007..3e39c9a4e 100644
--- a/app/javascript/mastodon/service_worker/web_push_locales.js
+++ b/app/javascript/mastodon/service_worker/web_push_locales.js
@@ -6,6 +6,12 @@
 const fs   = require('fs');
 const path = require('path');
 
+const { defineMessages } = require('react-intl');
+
+const messages = defineMessages({
+  mentioned_you: { id: 'notification.mentioned_you', defaultMessage: '{name} mentioned you' },
+});
+
 const filtered  = {};
 const filenames = fs.readdirSync(path.resolve(__dirname, '../locales'));
 
@@ -20,7 +26,7 @@ filenames.forEach(filename => {
     'notification.favourite': full['notification.favourite'] || '',
     'notification.follow': full['notification.follow'] || '',
     'notification.follow_request': full['notification.follow_request'] || '',
-    'notification.mention': full['notification.mention'] || '',
+    'notification.mention': full[messages.mentioned_you.id] || '',
     'notification.reblog': full['notification.reblog'] || '',
     'notification.poll': full['notification.poll'] || '',
     'notification.status': full['notification.status'] || '',

From 51769e06708f79a485c444d139cbdbdd7249b0cb Mon Sep 17 00:00:00 2001
From: Claire <claire.github-309c@sitedethib.com>
Date: Fri, 4 Oct 2024 16:55:44 +0200
Subject: [PATCH 69/70] Fix media gallery items having incorrect borders when
 hidden (#32257)

---
 app/javascript/mastodon/components/media_gallery.jsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/javascript/mastodon/components/media_gallery.jsx b/app/javascript/mastodon/components/media_gallery.jsx
index 1380d244a..e05997844 100644
--- a/app/javascript/mastodon/components/media_gallery.jsx
+++ b/app/javascript/mastodon/components/media_gallery.jsx
@@ -336,14 +336,14 @@ class MediaGallery extends PureComponent {
 
     return (
       <div className={`media-gallery media-gallery--layout-${size}`} style={style} ref={this.handleRef}>
+        {children}
+
         {(!visible || uncached) && (
           <div className={classNames('spoiler-button', { 'spoiler-button--click-thru': uncached })}>
             {spoilerButton}
           </div>
         )}
 
-        {children}
-
         {(visible && !uncached) && (
           <div className='media-gallery__actions'>
             <button className='media-gallery__actions__pill' onClick={this.handleOpen}><FormattedMessage id='media_gallery.hide' defaultMessage='Hide' /></button>

From c40ab43dc703be988c277d4ba6b7987a3a80e16c Mon Sep 17 00:00:00 2001
From: Christian Schmidt <github@chsc.dk>
Date: Fri, 4 Oct 2024 18:23:05 +0200
Subject: [PATCH 70/70] Remove redundant title attribute (#32258)

---
 .../mastodon/features/ui/components/column_link.jsx           | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/app/javascript/mastodon/features/ui/components/column_link.jsx b/app/javascript/mastodon/features/ui/components/column_link.jsx
index 3386c17f0..f27ed5067 100644
--- a/app/javascript/mastodon/features/ui/components/column_link.jsx
+++ b/app/javascript/mastodon/features/ui/components/column_link.jsx
@@ -15,7 +15,7 @@ const ColumnLink = ({ icon, activeIcon, iconComponent, activeIconComponent, text
 
   if (href) {
     return (
-      <a href={href} className={className} data-method={method} title={text} {...other}>
+      <a href={href} className={className} data-method={method} {...other}>
         {active ? activeIconElement : iconElement}
         <span>{text}</span>
         {badgeElement}
@@ -23,7 +23,7 @@ const ColumnLink = ({ icon, activeIcon, iconComponent, activeIconComponent, text
     );
   } else {
     return (
-      <NavLink to={to} className={className} title={text} exact {...other}>
+      <NavLink to={to} className={className} exact {...other}>
         {active ? activeIconElement : iconElement}
         <span>{text}</span>
         {badgeElement}