From 75ba314e6b957401d181a938f304dc0f8f0e696a Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 10 Feb 2026 11:59:47 +0100 Subject: [PATCH] Migrate to new theming infrastructure (#37612) --- .storybook/preview.tsx | 4 +- app/controllers/application_controller.rb | 15 +---- app/helpers/theme_helper.rb | 3 - app/javascript/styles/contrast.scss | 1 - app/javascript/styles/mastodon-light.scss | 1 - app/lib/themes.rb | 2 +- app/views/layouts/embedded.html.haml | 2 +- .../preferences/appearance/show.html.haml | 55 ++++++++++--------- config/locales/en.yml | 5 +- config/settings.yml | 2 +- config/themes.yml | 2 - ...209142402_migrate_default_theme_setting.rb | 22 ++++++++ .../20260209143308_migrate_user_theme.rb | 31 +++++++++++ db/schema.rb | 5 +- .../application_controller_spec.rb | 18 ------ spec/helpers/theme_helper_spec.rb | 22 -------- .../settings/preferences/appearance_spec.rb | 4 +- 17 files changed, 93 insertions(+), 101 deletions(-) delete mode 100644 app/javascript/styles/contrast.scss delete mode 100644 app/javascript/styles/mastodon-light.scss create mode 100644 db/migrate/20260209142402_migrate_default_theme_setting.rb create mode 100644 db/migrate/20260209143308_migrate_user_theme.rb diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index d2d34db80d..efdebc3bda 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -21,9 +21,7 @@ import { reducerWithInitialState } from '@/mastodon/reducers'; import { defaultMiddleware } from '@/mastodon/store/store'; import { mockHandlers, unhandledRequestHandler } from '@/testing/api'; -// If you want to run the dark theme during development, -// you can change the below to `/application.scss` -import '../app/javascript/styles/mastodon-light.scss'; +import '../app/javascript/styles/application.scss'; import './styles.css'; import { modes } from './modes'; diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index efee54cb7b..7b62c95aa6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -180,22 +180,11 @@ class ApplicationController < ActionController::Base end def color_scheme - current = current_user&.setting_color_scheme - return current if current && current != 'auto' - - return 'dark' if current_theme.include?('default') || current_theme.include?('contrast') - return 'light' if current_theme.include?('light') - - 'auto' + current_user&.setting_color_scheme || 'auto' end def contrast - current = current_user&.setting_contrast - return current if current && current != 'auto' - - return 'high' if current_theme.include?('contrast') - - 'auto' + current_user&.setting_contrast || 'auto' end def respond_with_error(code) diff --git a/app/helpers/theme_helper.rb b/app/helpers/theme_helper.rb index f651a495ff..ea3cdfd39e 100644 --- a/app/helpers/theme_helper.rb +++ b/app/helpers/theme_helper.rb @@ -18,9 +18,6 @@ module ThemeHelper end def theme_style_tags(theme) - # TODO: get rid of that when we retire the themes and perform the settings migration - theme = 'default' if %w(mastodon-light contrast system).include?(theme) - vite_stylesheet_tag "themes/#{theme}", type: :virtual, media: 'all', crossorigin: 'anonymous' end diff --git a/app/javascript/styles/contrast.scss b/app/javascript/styles/contrast.scss deleted file mode 100644 index cc23627a15..0000000000 --- a/app/javascript/styles/contrast.scss +++ /dev/null @@ -1 +0,0 @@ -@use 'common'; diff --git a/app/javascript/styles/mastodon-light.scss b/app/javascript/styles/mastodon-light.scss deleted file mode 100644 index cc23627a15..0000000000 --- a/app/javascript/styles/mastodon-light.scss +++ /dev/null @@ -1 +0,0 @@ -@use 'common'; diff --git a/app/lib/themes.rb b/app/lib/themes.rb index 8b68c92e45..67b1275d44 100644 --- a/app/lib/themes.rb +++ b/app/lib/themes.rb @@ -16,6 +16,6 @@ class Themes end def names - ['system'] + @conf.keys + @conf.keys end end diff --git a/app/views/layouts/embedded.html.haml b/app/views/layouts/embedded.html.haml index d815b59140..48577d98b3 100644 --- a/app/views/layouts/embedded.html.haml +++ b/app/views/layouts/embedded.html.haml @@ -15,7 +15,7 @@ = vite_client_tag = vite_react_refresh_tag = vite_polyfills_tag - = theme_style_tags 'system' + = theme_style_tags 'default' = vite_preload_file_tag "mastodon/locales/#{I18n.locale}.json" = render_initial_state = vite_typescript_tag 'embed.tsx', integrity: true, crossorigin: 'anonymous' diff --git a/app/views/settings/preferences/appearance/show.html.haml b/app/views/settings/preferences/appearance/show.html.haml index ec1c799a3a..f5dba2606f 100644 --- a/app/views/settings/preferences/appearance/show.html.haml +++ b/app/views/settings/preferences/appearance/show.html.haml @@ -23,33 +23,34 @@ .fields-group = f.simple_fields_for :settings, current_user.settings do |ff| - = ff.input :theme, - collection: Themes.instance.names, - hint: false, - include_blank: false, - label_method: ->(theme) { I18n.t("themes.#{theme}", default: theme) }, - label: I18n.t('simple_form.labels.defaults.setting_theme'), - wrapper: :with_label, - required: false - - if Mastodon::Feature.new_theme_options_enabled? - .input.horizontal-options - = ff.input :'web.color_scheme', - as: :radio_buttons, - collection: %w(auto light dark), - include_blank: false, - label: I18n.t('simple_form.labels.defaults.setting_color_scheme'), - label_method: ->(contrast) { I18n.t("color_scheme.#{contrast}", default: contrast) }, - wrapper: :with_label, - required: false - .input.horizontal-options - = ff.input :'web.contrast', - as: :radio_buttons, - collection: %w(auto high), - include_blank: false, - label: I18n.t('simple_form.labels.defaults.setting_contrast'), - label_method: ->(contrast) { I18n.t("contrast.#{contrast}", default: contrast) }, - wrapper: :with_label, - required: false + - if Themes.instance.names.size > 1 + = ff.input :theme, + collection: Themes.instance.names, + hint: false, + include_blank: false, + label_method: ->(theme) { I18n.t("themes.#{theme}", default: theme) }, + label: I18n.t('simple_form.labels.defaults.setting_theme'), + wrapper: :with_label, + required: false + + .input.horizontal-options + = ff.input :'web.color_scheme', + as: :radio_buttons, + collection: %w(auto light dark), + include_blank: false, + label: I18n.t('simple_form.labels.defaults.setting_color_scheme'), + label_method: ->(contrast) { I18n.t("color_scheme.#{contrast}", default: contrast) }, + wrapper: :with_label, + required: false + .input.horizontal-options + = ff.input :'web.contrast', + as: :radio_buttons, + collection: %w(auto high), + include_blank: false, + label: I18n.t('simple_form.labels.defaults.setting_contrast'), + label_method: ->(contrast) { I18n.t("contrast.#{contrast}", default: contrast) }, + wrapper: :with_label, + required: false .fields-group = f.simple_fields_for :settings, current_user.settings do |ff| diff --git a/config/locales/en.yml b/config/locales/en.yml index 2b9a182ec1..8d8657d1d2 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2022,10 +2022,7 @@ en: review_link: Review terms of service title: The terms of service of %{domain} are changing themes: - contrast: Mastodon (High contrast) - default: Mastodon (Dark) - mastodon-light: Mastodon (Light) - system: Automatic (use system theme) + default: Mastodon time: formats: default: "%b %d, %Y, %H:%M" diff --git a/config/settings.yml b/config/settings.yml index ae7548df26..3292034f0b 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -19,7 +19,7 @@ defaults: &defaults show_staff_badge: true preview_sensitive_media: false noindex: false - theme: 'system' + theme: 'default' trends: true trendable_by_default: false disallowed_hashtags: # space separated string or list of hashtags without the hash diff --git a/config/themes.yml b/config/themes.yml index 9c21c9459f..a1049fae7d 100644 --- a/config/themes.yml +++ b/config/themes.yml @@ -1,3 +1 @@ default: styles/application.scss -contrast: styles/contrast.scss -mastodon-light: styles/mastodon-light.scss diff --git a/db/migrate/20260209142402_migrate_default_theme_setting.rb b/db/migrate/20260209142402_migrate_default_theme_setting.rb new file mode 100644 index 0000000000..d8f6ca310f --- /dev/null +++ b/db/migrate/20260209142402_migrate_default_theme_setting.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class MigrateDefaultThemeSetting < ActiveRecord::Migration[8.0] + class Setting < ApplicationRecord; end + + def up + Setting.reset_column_information + + setting = Setting.find_by(var: 'theme') + return unless setting.present? && setting.attributes['value'].present? && %w(mastodon-light contrast system).include?(setting.attributes['value']) + + Setting.upsert( + { + var: 'theme', + value: "--- default\n", + }, + unique_by: index_exists?(:settings, [:thing_type, :thing_id, :var]) ? [:thing_type, :thing_id, :var] : :var + ) + end + + def down; end +end diff --git a/db/migrate/20260209143308_migrate_user_theme.rb b/db/migrate/20260209143308_migrate_user_theme.rb new file mode 100644 index 0000000000..93870055db --- /dev/null +++ b/db/migrate/20260209143308_migrate_user_theme.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +class MigrateUserTheme < ActiveRecord::Migration[8.0] + disable_ddl_transaction! + + # Dummy classes, to make migration possible across version changes + class User < ApplicationRecord; end + + def up + User.where.not(settings: nil).find_each do |user| + settings = Oj.load(user.attributes_before_type_cast['settings']) + next if settings.nil? || settings['theme'].blank? || %w(system default mastodon-light contrast).exclude?(settings['theme']) + + case settings['theme'] + when 'default' + settings['web.color_scheme'] = 'dark' + settings['web.contrast'] = 'auto' + when 'contrast' + settings['web.color_scheme'] = 'dark' + settings['web.contrast'] = 'high' + when 'mastodon-light' + settings['web.color_scheme'] = 'light' + settings['web.contrast'] = 'auto' + end + + settings['theme'] = 'default' + + user.update_column('settings', Oj.dump(settings)) + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 9080de8fb8..21ee37e418 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.0].define(version: 2026_01_27_141820) do +ActiveRecord::Schema[8.0].define(version: 2026_02_09_143308) do # These are extensions that must be enabled in order to support this database enable_extension "pg_catalog.plpgsql" @@ -1127,6 +1127,7 @@ ActiveRecord::Schema[8.0].define(version: 2026_01_27_141820) do t.string "poll_options", array: true t.boolean "sensitive" t.bigint "quote_id" + t.string "content_type" t.index ["account_id"], name: "index_status_edits_on_account_id" t.index ["status_id"], name: "index_status_edits_on_status_id" end @@ -1189,6 +1190,8 @@ ActiveRecord::Schema[8.0].define(version: 2026_01_27_141820) do t.bigint "ordered_media_attachment_ids", array: true t.datetime "fetched_replies_at" t.integer "quote_approval_policy", default: 0, null: false + t.boolean "local_only" + t.string "content_type" t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)" t.index ["account_id"], name: "index_statuses_on_account_id" t.index ["conversation_id"], name: "index_statuses_on_conversation_id" diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index ea182a047f..f5e42ab4eb 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -97,24 +97,6 @@ RSpec.describe ApplicationController do expect(controller.view_context.current_theme).to eq 'contrast' end - - it 'returns instances\'s default theme when user didn\'t set theme' do - current_user = Fabricate(:user) - current_user.settings.update(theme: 'contrast', noindex: false) - current_user.save - sign_in current_user - - expect(controller.view_context.current_theme).to eq 'contrast' - end - - it 'returns user\'s theme when it is set' do - current_user = Fabricate(:user) - current_user.settings.update(theme: 'mastodon-light') - current_user.save - sign_in current_user - - expect(controller.view_context.current_theme).to eq 'mastodon-light' - end end context 'with ActionController::RoutingError' do diff --git a/spec/helpers/theme_helper_spec.rb b/spec/helpers/theme_helper_spec.rb index a402885711..a9403f8b0c 100644 --- a/spec/helpers/theme_helper_spec.rb +++ b/spec/helpers/theme_helper_spec.rb @@ -6,17 +6,6 @@ RSpec.describe ThemeHelper do describe 'theme_style_tags' do let(:result) { helper.theme_style_tags(theme) } - context 'when using "system" theme' do - let(:theme) { 'system' } - - it 'returns the default theme' do - expect(html_links.first.attributes.symbolize_keys) - .to include( - href: have_attributes(value: match(/default/)) - ) - end - end - context 'when using "default" theme' do let(:theme) { 'default' } @@ -27,17 +16,6 @@ RSpec.describe ThemeHelper do ) end end - - context 'when using other theme' do - let(:theme) { 'contrast' } - - it 'returns the theme stylesheet without color scheme information' do - expect(html_links.first.attributes.symbolize_keys) - .to include( - href: have_attributes(value: match(/default/)) - ) - end - end end describe 'theme_color_tags' do diff --git a/spec/system/settings/preferences/appearance_spec.rb b/spec/system/settings/preferences/appearance_spec.rb index e8fb0c5de8..b9de36ee78 100644 --- a/spec/system/settings/preferences/appearance_spec.rb +++ b/spec/system/settings/preferences/appearance_spec.rb @@ -13,15 +13,13 @@ RSpec.describe 'Settings preferences appearance page' do expect(page) .to have_private_cache_control - select 'contrast', from: theme_selection_field check confirm_reblog_field uncheck confirm_delete_field check advanced_layout_field expect { save_changes } - .to change { user.reload.settings.theme }.to('contrast') - .and change { user.reload.settings['web.reblog_modal'] }.to(true) + .to change { user.reload.settings['web.reblog_modal'] }.to(true) .and change { user.reload.settings['web.delete_modal'] }.to(false) .and(change { user.reload.settings['web.advanced_layout'] }.to(true)) expect(page)