From e8ce28648db2ff4216fe1ddc9511cef4f08513e4 Mon Sep 17 00:00:00 2001 From: Guillem Arias Fauste Date: Tue, 19 May 2026 14:41:02 +0200 Subject: [PATCH] refactor: rename beta features gate to preview features (#1837) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: rename beta features gate to preview features Renames the opt-in gate introduced in PR #1829 from "beta" to "preview". Same shape (per-user JSONB toggle, `before_action` concern, marker pill) just retitled so the surface speaks the language Sure uses elsewhere ("preview" reads as in-progress, "beta" had baggage with provider maturity copy and external testing programs). Renames: - BetaGateable -> PreviewGateable - require_beta_features! -> require_preview_features! - beta_features_enabled? -> preview_features_enabled? - preferences["beta_features_enabled"] -> preferences["preview_features_enabled"] - DS::Pill default label "Beta" -> "Preview" - Settings -> Preferences toggle copy "beta features" -> "preview features" - config/locales/views/beta/ -> config/locales/views/preview/ - docs/llm-guides/gating-a-beta-feature.md -> gating-a-preview-feature.md Includes a data migration that copies any existing `beta_features_enabled` JSONB key into `preview_features_enabled` so early opt-ins survive the rename, then removes the old key. The migration is fully reversible. Provider maturity copy ("maturity.beta = Beta" under Settings -> Bank sync) is intentionally untouched - that's a separate concept describing a provider's integration stability, not Sure's feature gate. * review: apply CodeRabbit findings on PR #1837 - Settings::PreferencesController#update now routes the `preview_features_enabled` input through strong params and casts via ActiveModel::Type::Boolean instead of reading raw params and string- comparing to "1". Matches Sure's controller convention for permitted params and avoids stringly-typed boolean handling. - Rename migration now wraps the destination JSONB key write in COALESCE so a row that somehow ends up with both keys keeps the destination value instead of having it overwritten by the source. Up and down paths get the same defensive shape. * 📝 CodeRabbit Chat: Implement requested code changes * 📝 CodeRabbit Chat: Implement requested code changes * fix: restore all missing translation keys; rename beta→preview label * fix: restore all missing sections (appearances, debugs, llm_usages, providers, etc.); rename beta→preview * fix: restore missing keys (member_removal_failed, confirm_delete, etc.); add preview section * fix(i18n/ca): use 'està en vista prèvia' instead of 'és una vista prèvia' * fix(i18n/ca): use 'en desenvolupament'; drop article in preview title * fix(i18n/es): use 'en desarrollo' instead of 'en progreso' * fix(i18n/ca): use 'funcions experimentals' instead of 'vista prèvia' * fix(i18n/es): use 'funciones experimentales' instead of 'vista previa' * fix(i18n/ca): use 'funcions experimentals' in preferences.show.preview * fix(i18n/es): use 'funciones experimentales' in preferences.show.preview * fix(i18n/ca): use 'Experimental' pill label instead of 'Vista prèvia' * fix(i18n/es): use 'Experimental' pill label instead of 'Vista previa' --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- app/controllers/application_controller.rb | 2 +- app/controllers/concerns/beta_gateable.rb | 20 --- app/controllers/concerns/preview_gateable.rb | 20 +++ .../settings/preferences_controller.rb | 7 +- app/models/user.rb | 4 +- app/views/settings/preferences/show.html.erb | 22 +-- config/locales/views/beta/en.yml | 4 - config/locales/views/components/ca.yml | 2 +- config/locales/views/components/en.yml | 2 +- config/locales/views/components/es.yml | 6 +- config/locales/views/preview/ca.yml | 4 + config/locales/views/preview/en.yml | 4 + config/locales/views/preview/es.yml | 4 + config/locales/views/settings/ca.yml | 7 +- config/locales/views/settings/en.yml | 6 +- config/locales/views/settings/es.yml | 3 + ...rename_beta_features_enabled_preference.rb | 30 ++++ db/schema.rb | 2 +- docs/llm-guides/gating-a-beta-feature.md | 128 ------------------ docs/llm-guides/gating-a-preview-feature.md | 128 ++++++++++++++++++ .../previews/pill_component_preview.rb | 2 +- .../settings/preferences_controller_test.rb | 22 +-- test/models/user_test.rb | 20 +-- 23 files changed, 248 insertions(+), 201 deletions(-) delete mode 100644 app/controllers/concerns/beta_gateable.rb create mode 100644 app/controllers/concerns/preview_gateable.rb delete mode 100644 config/locales/views/beta/en.yml create mode 100644 config/locales/views/preview/ca.yml create mode 100644 config/locales/views/preview/en.yml create mode 100644 config/locales/views/preview/es.yml create mode 100644 db/migrate/20260519092118_rename_beta_features_enabled_preference.rb delete mode 100644 docs/llm-guides/gating-a-beta-feature.md create mode 100644 docs/llm-guides/gating-a-preview-feature.md diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 1d1389487..7306d504f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -2,7 +2,7 @@ class ApplicationController < ActionController::Base include RestoreLayoutPreferences, Onboardable, Localize, AutoSync, Authentication, Invitable, SelfHostable, StoreLocation, Impersonatable, Breadcrumbable, FeatureGuardable, Notifiable, SafePagination, AccountAuthorizable, - BetaGateable + PreviewGateable include Pundit::Authorization include Pagy::Backend diff --git a/app/controllers/concerns/beta_gateable.rb b/app/controllers/concerns/beta_gateable.rb deleted file mode 100644 index 4268baaa6..000000000 --- a/app/controllers/concerns/beta_gateable.rb +++ /dev/null @@ -1,20 +0,0 @@ -module BetaGateable - extend ActiveSupport::Concern - - included do - helper_method :beta_features_enabled? - end - - def beta_features_enabled? - Current.user&.beta_features_enabled? == true - end - - # Use as a `before_action` on controllers that gate a beta feature. - # Redirects non-beta users to the dashboard with a flash explaining the - # feature is opt-in. Self-served via Settings → Preferences. - def require_beta_features! - return if beta_features_enabled? - - redirect_to root_path, alert: I18n.t("beta.not_enabled") - end -end diff --git a/app/controllers/concerns/preview_gateable.rb b/app/controllers/concerns/preview_gateable.rb new file mode 100644 index 000000000..7ae25ce8f --- /dev/null +++ b/app/controllers/concerns/preview_gateable.rb @@ -0,0 +1,20 @@ +module PreviewGateable + extend ActiveSupport::Concern + + included do + helper_method :preview_features_enabled? + end + + def preview_features_enabled? + Current.user&.preview_features_enabled? == true + end + + # Use as a `before_action` on controllers that gate a preview feature. + # Redirects users without preview access to the dashboard with a flash + # explaining the feature is opt-in. Self-served via Settings → Preferences. + def require_preview_features! + return if preview_features_enabled? + + redirect_to root_path, alert: I18n.t("preview.not_enabled") + end +end diff --git a/app/controllers/settings/preferences_controller.rb b/app/controllers/settings/preferences_controller.rb index d5bf9d75a..5798c573e 100644 --- a/app/controllers/settings/preferences_controller.rb +++ b/app/controllers/settings/preferences_controller.rb @@ -11,11 +11,14 @@ class Settings::PreferencesController < ApplicationController # UsersController#update flow (which expects a full user form payload). def update @user = Current.user + user_params = params.permit(user: [ :preview_features_enabled ]).fetch(:user, {}) + @user.transaction do @user.lock! updated_prefs = (@user.preferences || {}).deep_dup - if params.dig(:user, :beta_features_enabled) - updated_prefs["beta_features_enabled"] = params.dig(:user, :beta_features_enabled) == "1" + if user_params.key?(:preview_features_enabled) + updated_prefs["preview_features_enabled"] = + ActiveModel::Type::Boolean.new.cast(user_params[:preview_features_enabled]) end @user.update!(preferences: updated_prefs) end diff --git a/app/models/user.rb b/app/models/user.rb index bcaa1446b..b1c40bb76 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -365,8 +365,8 @@ class User < ApplicationRecord preferences&.dig("dashboard_two_column") == true end - def beta_features_enabled? - preferences&.dig("beta_features_enabled") == true + def preview_features_enabled? + preferences&.dig("preview_features_enabled") == true end def update_transactions_preferences(prefs) diff --git a/app/views/settings/preferences/show.html.erb b/app/views/settings/preferences/show.html.erb index 6c5410fbf..67c04b44a 100644 --- a/app/views/settings/preferences/show.html.erb +++ b/app/views/settings/preferences/show.html.erb @@ -207,26 +207,26 @@ <% end %> <% end %> -<%# Beta features toggle — visible to all users, not just admins. Lives at the - bottom of Preferences as a standalone card (no section header) so the toggle - row is the entire surface. Posts directly to settings#preferences#update via - the Settings::PreferencesController, matching the auto-submit pattern used - on the Appearance page. %> +<%# Preview features toggle — visible to all users, not just admins. Lives at + the bottom of Preferences as a standalone card (no section header) so the + toggle row is the entire surface. Posts directly to + settings#preferences#update via the Settings::PreferencesController, + matching the auto-submit pattern used on the Appearance page. %>
<%= form_with url: settings_preferences_path, method: :patch, data: { controller: "auto-submit-form" } do |f| %> <%# Wrapping the row in