Commit 9082fd51cca for woocommerce

commit 9082fd51cca2d541adb1f77c0bfbd55da1b9f1ae
Author: Daniel Mallory <daniel.mallory@automattic.com>
Date:   Fri Jul 24 14:40:49 2026 +0100

    Fix hidden Save button on Settings UI fallback pages (#66958)

    fix(settings): Keep classic body classes when settings UI falls back

    The woocommerce-settings-ui-page body class was added whenever the
    feature flag was on and the page had a Settings UI adapter, even when
    schema or script handle resolution had failed and output() had fallen
    back to the legacy renderer. The settings UI stylesheet hides
    `#mainform > .submit` under that class because the shell brings its own
    save UI, so the fallback page rendered navigation and legacy fields
    with an invisible Save button: merchants could view but not save.

    Skip all settings UI body classes when the request context reports a
    schema or script handle failure, using the same gate the drill-down
    class already applied. The fallback page keeps the full classic
    styling, including the visible Save button. Schema resolution now
    happens at admin_body_class time for top-level pages too; it is
    memoized in the request context, so output() reuses the result.

    Refs #66908

    Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/fix-66908-settings-ui-fallback-body-class b/plugins/woocommerce/changelog/fix-66908-settings-ui-fallback-body-class
new file mode 100644
index 00000000000..c3cd7c7ee41
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-66908-settings-ui-fallback-body-class
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Show the Save button when an experimental Settings UI page falls back to the legacy renderer.
diff --git a/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php b/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
index 5573e5b6d7d..5e5ad88b8e6 100644
--- a/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
+++ b/plugins/woocommerce/includes/admin/settings/class-wc-settings-page.php
@@ -146,9 +146,13 @@ if ( ! class_exists( 'WC_Settings_Page', false ) ) :
 					return $classes;
 				}

-				$is_rendering_drill_down = $context->is_drill_down()
-					&& ! $context->has_schema_failed()
-					&& ! $context->has_script_handles_failed();
+				// The legacy fallback renderer needs the classic styling: the settings UI
+				// body class hides the legacy Save button via CSS.
+				if ( $context->has_schema_failed() || $context->has_script_handles_failed() ) {
+					return $classes;
+				}
+
+				$is_rendering_drill_down = $context->is_drill_down();
 			} catch ( \Throwable $e ) {
 				return $classes;
 			}
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php
index f73398752d8..70afbd50eca 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUIFeatureFlagTest.php
@@ -462,9 +462,9 @@ class SettingsUIFeatureFlagTest extends WC_Unit_Test_Case {
 	}

 	/**
-	 * @testdox Should not add the drill-down body class when schema generation falls back to legacy rendering.
+	 * @testdox Should not add the Settings UI body classes when schema generation falls back to legacy rendering.
 	 */
-	public function test_settings_ui_drill_down_body_class_is_not_added_when_schema_generation_fails(): void {
+	public function test_settings_ui_body_classes_are_not_added_when_schema_generation_fails(): void {
 		add_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );

 		global $current_section, $current_tab;
@@ -474,14 +474,13 @@ class SettingsUIFeatureFlagTest extends WC_Unit_Test_Case {

 		$classes = $page->add_settings_ui_body_class( 'existing-class' );

-		$this->assertStringContainsString( 'woocommerce-settings-ui-page', $classes );
-		$this->assertStringNotContainsString( 'woocommerce-settings-ui-drill-down', $classes );
+		$this->assertSame( 'existing-class', $classes, 'The fallback page should keep the classic body classes so the legacy Save button stays visible' );
 	}

 	/**
-	 * @testdox Should not add the drill-down body class when script handle resolution falls back to legacy rendering.
+	 * @testdox Should not add the Settings UI body classes when script handle resolution falls back to legacy rendering.
 	 */
-	public function test_settings_ui_drill_down_body_class_is_not_added_when_script_handle_resolution_fails(): void {
+	public function test_settings_ui_body_classes_are_not_added_when_script_handle_resolution_fails(): void {
 		add_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );

 		global $current_section, $current_tab;
@@ -491,8 +490,23 @@ class SettingsUIFeatureFlagTest extends WC_Unit_Test_Case {

 		$classes = $page->add_settings_ui_body_class( 'existing-class' );

-		$this->assertStringContainsString( 'woocommerce-settings-ui-page', $classes );
-		$this->assertStringNotContainsString( 'woocommerce-settings-ui-drill-down', $classes );
+		$this->assertSame( 'existing-class', $classes, 'The fallback page should keep the classic body classes so the legacy Save button stays visible' );
+	}
+
+	/**
+	 * @testdox Should not add the Settings UI body class when a top-level page falls back to legacy rendering.
+	 */
+	public function test_settings_ui_body_class_is_not_added_when_a_top_level_page_falls_back(): void {
+		add_filter( 'woocommerce_admin_features', array( $this, 'enable_settings_ui_feature' ) );
+
+		global $current_section, $current_tab;
+		$current_section = 'failing_section';
+		$current_tab     = 'settings_ui_flag_test';
+		$page            = $this->get_settings_ui_test_page_with_failing_schema();
+
+		$classes = $page->add_settings_ui_body_class( 'existing-class' );
+
+		$this->assertSame( 'existing-class', $classes, 'The fallback page should keep the classic body classes so the legacy Save button stays visible' );
 	}

 	/**