Commit aeaaef5605c for woocommerce
commit aeaaef5605c8e64883b1792946a3c0d2e62fd9d2
Author: Daniel Mallory <daniel.mallory@automattic.com>
Date: Thu Sep 3 16:34:21 2026 +0100
Remove card header actions and obsolete styles from the Settings UI (#67865)
* Remove card header actions from the Settings UI schema
* Add changelog entries for Settings UI group actions
* Remove the group actions schema validation
Card header actions are no longer part of the Settings UI schema, so
validating the actions key guarded a contract that no longer exists.
The validator ignores unknown group keys, so a schema still declaring
actions now degrades to ignored instead of failing closed.
Refs WOOPRD-3610
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Remove obsolete Settings UI styles
* Add changelog entry for Settings UI style cleanup
* refactor(settings-ui): drop the unused runtime exports from the package index
The package index re-exported the registry resolvers and the hidden-input
helpers alongside the mount components. Nothing outside the package used
them: the adapter and the page consume the resolvers directly, and the page
owns hidden-input serialisation for the form_post save adapter. Exporting
them invited extensions to couple to renderer internals, which is the
contract the DataForm move deliberately removed.
Trim the index to the mount components, registerSettingsExtension, and the
types an extension author needs.
Refs WOOPRD-3603
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZLDkAqFMmHDyq9XrEeb1P
* refactor(settings-ui): remove the window.wcSettingsUI global
The registry assigned a second global, window.wcSettingsUI, next to the
window.wc.settingsUi global that the wc-settings-ui script handle already
provides. The docs and README only ever show the import path, which
resolves to wc.settingsUi at runtime, so the extra global was an
undocumented alias with its own Window typing to maintain.
Drop the assignment, the SettingsUIRegistry type and the global Window
declaration, and point the e2e registration plugin at wc.settingsUi.
Refs WOOPRD-3603
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LZLDkAqFMmHDyq9XrEeb1P
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
diff --git a/packages/js/settings-ui/changelog/update-remove-group-actions b/packages/js/settings-ui/changelog/update-remove-group-actions
new file mode 100644
index 00000000000..ec45996dd8d
--- /dev/null
+++ b/packages/js/settings-ui/changelog/update-remove-group-actions
@@ -0,0 +1,4 @@
+Significance: minor
+Type: update
+
+Remove group header actions from the Settings UI schema.
diff --git a/packages/js/settings-ui/src/index.ts b/packages/js/settings-ui/src/index.ts
index 082f55280f7..04219b856d4 100644
--- a/packages/js/settings-ui/src/index.ts
+++ b/packages/js/settings-ui/src/index.ts
@@ -1,19 +1,9 @@
export { SettingsUIErrorBoundary, SettingsUIPage } from './settings-ui-page';
-export { HiddenInputs, getHiddenInputs } from './hidden-inputs';
-export {
- registerSettingsExtension,
- resolveFieldComponent,
- resolveFieldVisibilityPredicate,
- resolveGroupVisibilityPredicate,
- resolveRegionComponent,
- resolveSaveHandler,
-} from './registry';
+export { registerSettingsExtension } from './registry';
export type {
SettingsUIField,
SettingsUIGroup,
- SettingsUIGroupAction,
SettingsUIOption,
- SettingsUIRegistry,
SettingsUISaveSchema,
SettingsUISaveStrategy,
SettingsUISchema,
diff --git a/packages/js/settings-ui/src/registry.ts b/packages/js/settings-ui/src/registry.ts
index 428c1aae09c..c7a4c21c44a 100644
--- a/packages/js/settings-ui/src/registry.ts
+++ b/packages/js/settings-ui/src/registry.ts
@@ -268,10 +268,3 @@ export const resolveRegionComponent = (
} );
return undefined;
};
-
-if ( typeof window !== 'undefined' ) {
- window.wcSettingsUI = {
- ...( window.wcSettingsUI || {} ),
- registerSettingsExtension,
- };
-}
diff --git a/packages/js/settings-ui/src/types.ts b/packages/js/settings-ui/src/types.ts
index 21a606801ed..726dd1ab1e3 100644
--- a/packages/js/settings-ui/src/types.ts
+++ b/packages/js/settings-ui/src/types.ts
@@ -43,20 +43,10 @@ export type SettingsUIField = {
save?: SettingsUISaveSchema;
};
-export type SettingsUIGroupAction = {
- id: string;
- label: string;
- href: string;
- variant?: 'primary' | 'secondary' | 'tertiary' | 'link' | string;
- target?: string;
- rel?: string;
-};
-
export type SettingsUIGroup = {
id: string;
title?: string;
description?: string;
- actions?: SettingsUIGroupAction[];
fields: SettingsUIField[];
};
@@ -201,15 +191,3 @@ export type SettingsExtensionRegistration = {
saveHandlers?: Record< string, SettingsSaveHandler >;
regions?: Record< string, SettingsRegionComponent >;
};
-
-export type SettingsUIRegistry = {
- registerSettingsExtension: (
- registration: SettingsExtensionRegistration
- ) => void;
-};
-
-declare global {
- interface Window {
- wcSettingsUI?: SettingsUIRegistry;
- }
-}
diff --git a/plugins/woocommerce/changelog/dev-clean-settings-ui-styles b/plugins/woocommerce/changelog/dev-clean-settings-ui-styles
new file mode 100644
index 00000000000..bf47496c849
--- /dev/null
+++ b/plugins/woocommerce/changelog/dev-clean-settings-ui-styles
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Remove obsolete Settings UI renderer styles and duplicate DataViews CSS; no merchant-facing change.
diff --git a/plugins/woocommerce/changelog/dev-remove-settings-ui-group-actions b/plugins/woocommerce/changelog/dev-remove-settings-ui-group-actions
new file mode 100644
index 00000000000..31df787c562
--- /dev/null
+++ b/plugins/woocommerce/changelog/dev-remove-settings-ui-group-actions
@@ -0,0 +1,3 @@
+Significance: patch
+Type: dev
+Comment: Remove unsupported group header actions from the experimental Settings UI schema.
diff --git a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
index 0e3369ea51d..f7d6a9debe6 100644
--- a/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
+++ b/plugins/woocommerce/client/admin/client/wp-admin-scripts/settings-embed/settings-ui.scss
@@ -1,5 +1,3 @@
-@import "@wordpress/dataviews/build-style/style.css";
-
body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
background-color: var(--wpds-color-background-surface-neutral);
@@ -216,164 +214,6 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
max-width: var(--wpds-dimension-surface-width-xl);
}
- .wc-settings-ui__section {
- margin: 0;
- }
-
- .wc-settings-ui__section-card {
- background: #fff;
- border: 1px solid var(--wpds-color-stroke-surface-neutral-weak, #f0f0f0);
- border-radius: var(--wpds-border-radius-lg, 8px);
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- width: 100%;
- }
-
- .wc-settings-ui__section-header {
- align-items: flex-start;
- display: flex;
- gap: var(--wpds-dimension-gap-xl, 24px);
- justify-content: space-between;
- padding: 24px;
- }
-
- .wc-settings-ui__section-heading {
- min-width: 0;
- }
-
- .wc-settings-ui__section-header h2 {
- color: #1e1e1e;
- font-size: 15px;
- font-weight: var(--wpds-typography-font-weight-medium, 500);
- line-height: 20px;
- margin: 0;
- }
-
- .wc-settings-ui__section-description {
- color: var(--wpds-color-foreground-content-neutral-weak);
- margin: var(--wpds-dimension-gap-sm) 0 0;
-
- p {
- margin: 0 0 var(--wpds-dimension-gap-sm);
- }
-
- p:last-child {
- margin-bottom: 0;
- }
- }
-
- .wc-settings-ui__section-actions {
- display: flex;
- flex: 0 0 auto;
- flex-wrap: wrap;
- gap: var(--wpds-dimension-gap-sm, 8px);
- }
-
- .wc-settings-ui__section-fields {
- display: flex;
- flex-direction: column;
- gap: var(--wpds-dimension-gap-lg, 16px);
- padding: 0 24px 24px;
- }
-
- .wc-settings-ui__section-card > .wc-settings-ui__section-fields:first-child {
- padding-top: 24px;
- }
-
- .wc-settings-ui__field {
- margin: 0;
- width: 100%;
- }
-
- .wc-settings-ui__info {
- background: var(--wpds-color-background-surface-neutral-weak);
- border-radius: var(--wpds-border-radius-md);
- padding: var(--wpds-dimension-padding-lg);
-
- strong {
- display: block;
- margin-bottom: var(--wpds-dimension-gap-xs);
- }
-
- p {
- margin: 0;
- }
- }
-
- .wc-settings-ui__control.components-base-control {
- margin-bottom: 0;
- width: 100%;
- }
-
- .wc-settings-ui__control {
- .components-base-control__field {
- margin-bottom: 0;
- }
-
- .components-input-control__label {
- line-height: 16px;
- }
-
- .components-base-control__help {
- color: var(--wpds-color-foreground-content-neutral-weak);
- font-size: var(--wpds-typography-font-size-md);
- line-height: var(--wpds-typography-line-height-sm);
- margin-top: var(--wpds-dimension-gap-sm);
- }
- }
-
- .wc-settings-ui__number-control {
- position: relative;
- }
-
- // Owned input skin: deliberately not borrowing internal
- // @wordpress/components classnames, which carry no compatibility
- // guarantee. Values mirror the default-size TextControl input.
- .wc-settings-ui__number-control-input {
- appearance: textfield;
- border: var(--wpds-border-width-xs) solid
- var(--wpds-color-stroke-interactive-neutral);
- border-radius: var(--wpds-border-radius-sm);
- box-shadow: 0 0 0 transparent;
- box-sizing: border-box;
- font-family: inherit;
- // Fonts smaller than 16px cause mobile Safari to zoom on focus.
- font-size: 16px;
- line-height: normal;
- margin: 0;
- min-height: var(--wpds-dimension-size-lg);
- padding: 6px var(--wpds-dimension-padding-md);
- // Keep typed values clear of the spin buttons.
- padding-right: 64px;
- transition: box-shadow 0.1s linear;
- width: 100%;
-
- @media (min-width: 600px) {
- font-size: var(--wpds-typography-font-size-md);
- }
-
- &:focus {
- border-color: var(--wpds-color-stroke-focus);
- box-shadow: 0 0 0 0.5px var(--wpds-color-stroke-focus);
- outline: var(--wpds-border-width-sm) solid transparent;
- }
-
- &::-webkit-inner-spin-button,
- &::-webkit-outer-spin-button {
- appearance: none;
- margin: 0;
- }
- }
-
- .wc-settings-ui__number-control-spin-buttons {
- display: flex;
- position: absolute;
- right: 6px;
- top: 50%;
- transform: translateY(-50%);
- }
-
.wc-settings-ui__unsaved-changes-modal {
.components-modal__header {
min-height: 72px;
@@ -437,13 +277,5 @@ body.woocommerce_page_wc-settings.woocommerce-settings-ui-page {
var(--wpds-dimension-gap-2xl);
max-width: none;
}
-
- .wc-settings-ui__section-header {
- display: block;
- }
-
- .wc-settings-ui__section-actions {
- margin-top: var(--wpds-dimension-gap-lg);
- }
}
}
diff --git a/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUISchema.php b/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUISchema.php
index 2720114e301..2fae9a99db3 100644
--- a/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUISchema.php
+++ b/plugins/woocommerce/src/Internal/Admin/Settings/SettingsUISchema.php
@@ -66,7 +66,6 @@ class SettingsUISchema {
'id' => $current_id,
'title' => isset( $setting['title'] ) && is_scalar( $setting['title'] ) ? html_entity_decode( (string) $setting['title'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) : '',
'description' => isset( $setting['desc'] ) && is_scalar( $setting['desc'] ) ? wp_kses_post( (string) $setting['desc'] ) : '',
- 'actions' => self::get_group_actions( $setting ),
'order' => isset( $setting['order'] ) ? (int) $setting['order'] : $group_index,
'fields' => array(),
);
@@ -197,7 +196,6 @@ class SettingsUISchema {
foreach ( $schema['groups'] as $group ) {
$group_id = $group['id'];
self::assert_optional_strings( $group, array( 'title', 'description' ), sprintf( 'Group "%s"', $group_id ) );
- self::assert_group_actions( $group['actions'] ?? null, $group_id );
if ( ! isset( $group['fields'] ) || ! is_array( $group['fields'] ) || ! ArrayUtil::array_is_list( $group['fields'] ) ) {
throw self::invalid_schema( sprintf( 'Group "%s" fields must be a list.', $group_id ) );
@@ -800,58 +798,6 @@ class SettingsUISchema {
return $attributes;
}
- /**
- * Normalize group header actions.
- *
- * @param array $setting Legacy title setting definition.
- * @return array
- */
- private static function get_group_actions( array $setting ): array {
- if ( empty( $setting['actions'] ) || ! is_array( $setting['actions'] ) ) {
- return array();
- }
-
- $actions = array();
-
- foreach ( $setting['actions'] as $index => $action ) {
- if ( ! is_array( $action ) || empty( $action['label'] ) || ! is_scalar( $action['label'] ) ) {
- continue;
- }
-
- $href = $action['href'] ?? $action['url'] ?? '';
- if ( ! is_scalar( $href ) || '' === (string) $href ) {
- continue;
- }
-
- $href = esc_url_raw( (string) $href );
- if ( '' === $href ) {
- continue;
- }
-
- $normalized_action = array(
- 'id' => isset( $action['id'] ) && is_scalar( $action['id'] ) ? sanitize_key( (string) $action['id'] ) : 'action_' . $index,
- 'label' => wp_strip_all_tags( html_entity_decode( (string) $action['label'], ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401 ) ),
- 'href' => $href,
- );
-
- if ( isset( $action['variant'] ) && is_scalar( $action['variant'] ) ) {
- $normalized_action['variant'] = sanitize_key( (string) $action['variant'] );
- }
-
- if ( isset( $action['target'] ) && is_scalar( $action['target'] ) && in_array( (string) $action['target'], array( '_blank', '_self', '_parent', '_top' ), true ) ) {
- $normalized_action['target'] = (string) $action['target'];
- }
-
- if ( isset( $action['rel'] ) && is_scalar( $action['rel'] ) ) {
- $normalized_action['rel'] = sanitize_text_field( (string) $action['rel'] );
- }
-
- $actions[] = $normalized_action;
- }
-
- return $actions;
- }
-
// The assertion helpers deliberately propagate InvalidArgumentException to
// the public boundary method, whose contract documents that exception.
// phpcs:disable Squiz.Commenting.FunctionCommentThrowTag.Missing
@@ -1002,42 +948,6 @@ class SettingsUISchema {
}
}
- /**
- * Assert group header actions.
- *
- * @param mixed $actions Group actions, or null when omitted.
- * @param string $group_id Group id.
- */
- private static function assert_group_actions( $actions, string $group_id ): void {
- if ( null === $actions ) {
- return;
- }
-
- if ( ! is_array( $actions ) || ! ArrayUtil::array_is_list( $actions ) ) {
- throw self::invalid_schema( sprintf( 'Group "%s" actions must be a list.', $group_id ) );
- }
-
- $ids = array();
- foreach ( $actions as $index => $action ) {
- if ( ! is_array( $action ) ) {
- throw self::invalid_schema( sprintf( 'Group "%s" action %d must be an array.', $group_id, $index ) );
- }
-
- self::assert_non_empty_string( $action['id'] ?? null, sprintf( 'Group "%s" action %d id must be a non-empty string.', $group_id, $index ) );
- if ( isset( $ids[ $action['id'] ] ) ) {
- throw self::invalid_schema( sprintf( 'Group "%s" action id "%s" is duplicated.', $group_id, $action['id'] ) );
- }
- $ids[ $action['id'] ] = true;
-
- foreach ( array( 'label', 'href' ) as $property ) {
- if ( ! isset( $action[ $property ] ) || ! is_string( $action[ $property ] ) ) {
- throw self::invalid_schema( sprintf( 'Group "%s" action %d %s must be a string.', $group_id, $index, $property ) );
- }
- }
-
- self::assert_optional_strings( $action, array( 'variant', 'target', 'rel' ), sprintf( 'Group "%s" action %d', $group_id, $index ) );
- }
- }
/**
* Assert a field definition.
@@ -1275,7 +1185,6 @@ class SettingsUISchema {
'id' => $group_id,
'title' => '',
'description' => '',
- 'actions' => array(),
'order' => $order,
'fields' => array(),
);
diff --git a/plugins/woocommerce/tests/e2e/test-plugins/settings-ui-component-registration/settings-ui-component-registration.php b/plugins/woocommerce/tests/e2e/test-plugins/settings-ui-component-registration/settings-ui-component-registration.php
index 0b51299f659..0fc7b5b6c8d 100644
--- a/plugins/woocommerce/tests/e2e/test-plugins/settings-ui-component-registration/settings-ui-component-registration.php
+++ b/plugins/woocommerce/tests/e2e/test-plugins/settings-ui-component-registration/settings-ui-component-registration.php
@@ -41,7 +41,7 @@ final class WC_Settings_UI_Component_Registration_Test_Plugin {
wp_add_inline_script(
self::REGISTERED_HANDLE,
<<<'JS'
-window.wcSettingsUI.registerSettingsExtension( {
+window.wc.settingsUi.registerSettingsExtension( {
scope: { page: 'products', section: 'settings_ui_component_registered' },
components: {
'woocommerce/settings-ui-component-test': function SettingsUIComponentTest( props ) {
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUISchemaTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUISchemaTest.php
index 3f3f0e93228..5bd028c85c8 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUISchemaTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Settings/SettingsUISchemaTest.php
@@ -144,9 +144,9 @@ class SettingsUISchemaTest extends WC_Unit_Test_Case {
}
/**
- * @testdox It preserves sanitized group description markup and header actions.
+ * @testdox It preserves sanitized group description markup and omits header actions.
*/
- public function test_from_legacy_settings_preserves_group_description_and_actions(): void {
+ public function test_from_legacy_settings_preserves_group_description_without_actions(): void {
$schema = SettingsUISchema::from_legacy_settings(
'test',
'advanced',
@@ -174,19 +174,7 @@ class SettingsUISchemaTest extends WC_Unit_Test_Case {
$group = $schema['groups']['group'];
$this->assertSame( 'Read the <a href="https://woocommerce.com">documentation</a>alert("x").', $group['description'] );
- $this->assertSame(
- array(
- array(
- 'id' => 'learn-more',
- 'label' => 'Learn more',
- 'href' => 'https://woocommerce.com/documentation',
- 'variant' => 'secondary',
- 'target' => '_blank',
- 'rel' => 'noopener noreferrer',
- ),
- ),
- $group['actions']
- );
+ $this->assertArrayNotHasKey( 'actions', $group );
}
/**
@@ -1193,14 +1181,6 @@ class SettingsUISchemaTest extends WC_Unit_Test_Case {
'intent' => array( 'invalid' ),
),
);
- $invalid_action = $valid;
- $invalid_action['groups']['main']['actions'] = array(
- array(
- 'id' => '',
- 'label' => 'Docs',
- 'href' => 'https://example.com',
- ),
- );
$invalid_page_save = $valid;
$invalid_page_save['save'] = array( 'adapter' => 'custom' );
$invalid_navigation_component = $valid;
@@ -1229,7 +1209,6 @@ class SettingsUISchemaTest extends WC_Unit_Test_Case {
'malformed shell navigation' => array( $invalid_shell, 'Shell navigation item 0 href must be a string.' ),
'malformed breadcrumb' => array( $invalid_breadcrumb, 'Shell breadcrumb 0 label must be a string.' ),
'invalid badge intent' => array( $invalid_badge, 'Shell badge 0 intent must be a string.' ),
- 'empty group action id' => array( $invalid_action, 'Group "main" action 0 id must be a non-empty string.' ),
'custom save without handler' => array( $invalid_page_save, 'Schema custom save strategy must define a non-empty handler.' ),
'empty navigation component' => array( $invalid_navigation_component, 'Shell navigationComponent must be a non-empty string.' ),
'group map id mismatch' => array( $invalid_group_map, 'Group map key "other" must match group id "main".' ),
@@ -1237,15 +1216,15 @@ class SettingsUISchemaTest extends WC_Unit_Test_Case {
}
/**
- * @testdox It accepts valid optional shell, field, visibility, action, and save metadata.
+ * @testdox It accepts valid optional shell, field, visibility, and save metadata.
*/
public function test_assert_valid_schema_accepts_optional_metadata(): void {
- $schema = self::get_valid_schema_for_validation();
- $schema['save'] = array(
+ $schema = self::get_valid_schema_for_validation();
+ $schema['save'] = array(
'adapter' => 'custom',
'handler' => 'acme/save',
);
- $schema['shell'] = array(
+ $schema['shell'] = array(
'header' => 'visible',
'title' => 'Acme settings',
'subtitle' => 'Configure Acme.',
@@ -1272,16 +1251,6 @@ class SettingsUISchemaTest extends WC_Unit_Test_Case {
'sectionNavigation' => array(),
'navigationComponent' => 'acme/navigation',
);
- $schema['groups']['main']['actions'] = array(
- array(
- 'id' => 'docs',
- 'label' => 'Documentation',
- 'href' => 'https://example.com/docs',
- 'variant' => 'link',
- 'target' => '_blank',
- 'rel' => 'noopener',
- ),
- );
$schema['groups']['main']['fields'][0]['component'] = 'acme/text';
$schema['groups']['main']['fields'][0]['visibility'] = array(
'controller' => 'acme_enabled',
@@ -1322,7 +1291,6 @@ class SettingsUISchemaTest extends WC_Unit_Test_Case {
'id' => 'main',
'title' => 'Main',
'description' => 'Main settings.',
- 'actions' => array(),
'fields' => array(
array(
'id' => 'acme_field',