Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
c62c394
Switch settings page controls from checkboxes to toggles
jorgefilipecosta Apr 7, 2026
ed9a3a8
Remove save button styles from settings page
jorgefilipecosta Apr 7, 2026
93268ad
Update E2E helpers for auto-save behavior
jorgefilipecosta Apr 7, 2026
ab9e199
Update E2E selectors from getByRole to getByLabel for toggles
jorgefilipecosta Apr 8, 2026
d9b47d9
lint fix
jorgefilipecosta Apr 8, 2026
786cb6c
Add get_settings_fields API to Abstract_Feature
jorgefilipecosta Apr 8, 2026
e3538a1
Migrate Content Classification settings to DataForm fields
jorgefilipecosta Apr 8, 2026
9ea2b2f
Include settingsFields in feature metadata for frontend
jorgefilipecosta Apr 8, 2026
dcf52bf
Render inline DataForm sub-settings for experiments
jorgefilipecosta Apr 8, 2026
42efcb9
Update E2E setStrategy helper for DataForms settings page
jorgefilipecosta Apr 8, 2026
4d95d11
Add missing JSDoc params to InlineFeatureSettings and createFeatureTo…
jorgefilipecosta Apr 8, 2026
215197c
Add PHP tests for settings fields API and bootstrap metadata
jorgefilipecosta Apr 8, 2026
a77607e
Fix content-classification E2E tests to use display label
jorgefilipecosta Apr 8, 2026
7a333e6
Fix stale 'checkboxes' comment in settings E2E test
jorgefilipecosta Apr 8, 2026
2945b5f
Remove unnecessary method_exists guard in bootstrap
jorgefilipecosta Apr 8, 2026
b323601
Replace hardcoded border color with rgba for theme adaptability
jorgefilipecosta Apr 8, 2026
630f849
Fix default fallback for integer fields in parseSettingsField
jorgefilipecosta Apr 8, 2026
5ad2819
Fix InlineFeatureSettings remount losing pending edits
jorgefilipecosta Apr 8, 2026
222514b
Revert optimistic toggle edit on auto-save failure
jorgefilipecosta Apr 8, 2026
8b8524b
Add regression E2E test for inline settings state preservation
jorgefilipecosta Apr 8, 2026
82fb142
Add get_settings_fields_metadata to Feature interface
jorgefilipecosta Apr 8, 2026
83ef452
Revert optimistic inline settings edit on save failure
jorgefilipecosta Apr 8, 2026
31682fa
Unset reference variable after foreach in get_settings_fields_metadata
jorgefilipecosta Apr 8, 2026
c180ebc
Invalidate editComponentsRef cache when feature definition changes
jorgefilipecosta Apr 8, 2026
897299a
Guard toggle auto-save against concurrent requests
jorgefilipecosta Apr 8, 2026
bcb10e5
Assert snackbar text in inline settings regression test
jorgefilipecosta Apr 8, 2026
7ce4260
Use theme-aware CSS custom property for inline settings border
jorgefilipecosta Apr 8, 2026
b31d4f5
Remove non-null assertion when reading cached edit component
jorgefilipecosta Apr 8, 2026
7ec3a76
Use ARIA selector for disabled-toggle assertion in settings E2E test
jorgefilipecosta Apr 8, 2026
3309a10
Assert snackbar text in content-classification setStrategy helper
jorgefilipecosta Apr 8, 2026
2b84cf0
Stabilize handleChange by reading data from a ref
jorgefilipecosta Apr 8, 2026
38f3309
Clear inline settings pending edits when feature definition changes
jorgefilipecosta Apr 8, 2026
eea4831
Use core-data edits for toggle auto-save and inline settings
jorgefilipecosta Apr 8, 2026
be94c87
Disable inline Save button during save and scope isSaving locally
jorgefilipecosta Apr 8, 2026
5aeadc7
Align settings field shape with DataForm, add min/max validation and …
jorgefilipecosta Apr 8, 2026
9bb1067
Polish inline settings UI: remove separator, fix label casing, and in…
jorgefilipecosta Apr 8, 2026
6385943
fix alignment
jorgefilipecosta Apr 8, 2026
fc72a08
Extract suggestion bounds into constants and stabilize noop reference
jorgefilipecosta Apr 8, 2026
65ae809
fixes
jorgefilipecosta Apr 8, 2026
7a77733
lint fixes
jorgefilipecosta Apr 8, 2026
574c656
lint fix
jorgefilipecosta Apr 8, 2026
9e679fc
lint fix try 2
jorgefilipecosta Apr 8, 2026
d3be6e9
feedback
jorgefilipecosta Apr 8, 2026
689c3a5
ent to end fixes
jorgefilipecosta Apr 8, 2026
32301c3
e2e fixes
jorgefilipecosta Apr 8, 2026
e0bddc2
lint fix
jorgefilipecosta Apr 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions includes/Abstracts/Abstract_Feature.php
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,55 @@ public function register_settings(): void {
// Child classes can override to register custom settings.
}

/**
* Gets the field definitions for feature-specific settings.
*
* Override this method in child classes to declare custom settings fields
* that will be rendered as a DataForm on the settings page. Each field
* should use the short option name (e.g. 'strategy'), not the full
* namespaced option name.
*
* @since x.x.x
*
* @return array<int, array{
* id: string,
* label: string,
* type: string,
* default?: mixed,
* elements?: list<array{value: string, label: string}>,
* isValid?: array{min?: int, max?: int},
* }> Array of field definitions matching the DataForm Field shape.
*/
public function get_settings_fields(): array {
return array();
}

/**
* Gets field definitions with fully resolved option names.
*
* Transforms the short field IDs from get_settings_fields() into
* full WordPress option names suitable for the REST API and frontend.
*
* @since x.x.x
*
* @return array<int, array{
* id: string,
* label: string,
* type: string,
* default?: mixed,
* elements?: list<array{value: string, label: string}>,
* isValid?: array{min?: int, max?: int},
* }> Array of field definitions with full option names.
*/
public function get_settings_fields_metadata(): array {
$fields = $this->get_settings_fields();
foreach ( $fields as &$field ) {
$field['id'] = $this->get_field_option_name( $field['id'] );
}
unset( $field );
return $fields;
}

/**
* Gets the option name for a custom feature setting field.
*
Expand Down
18 changes: 18 additions & 0 deletions includes/Contracts/Feature.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,22 @@ public function register(): void;
* @return bool True if enabled, false otherwise.
*/
public function is_enabled(): bool;

/**
* Gets field definitions with fully resolved option names.
*
* Returns an empty array when the feature has no custom settings.
*
* @since x.x.x
*
* @return array<int, array{
* id: string,
* label: string,
* type: string,
* default?: mixed,
* elements?: list<array{value: string, label: string}>,
* isValid?: array{min?: int, max?: int},
* }> Array of field definitions with full option names.
*/
public function get_settings_fields_metadata(): array;
}
116 changes: 62 additions & 54 deletions includes/Experiments/Content_Classification/Content_Classification.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ class Content_Classification extends Abstract_Feature {
*/
public const DEFAULT_MAX_SUGGESTIONS = 5;

/**
* The minimum allowed number of suggestions.
*
* @since x.x.x
*
* @var int
*/
public const MIN_SUGGESTIONS = 1;

/**
* The maximum allowed number of suggestions.
*
* @since x.x.x
*
* @var int
*/
public const MAX_SUGGESTIONS = 10;

/**
* {@inheritDoc}
*/
Expand Down Expand Up @@ -153,6 +171,12 @@ public function register_settings(): void {
'type' => 'string',
'default' => self::STRATEGY_EXISTING_ONLY,
'sanitize_callback' => array( $this, 'sanitize_strategy' ),
'show_in_rest' => array(
'schema' => array(
'type' => 'string',
'enum' => array( self::STRATEGY_EXISTING_ONLY, self::STRATEGY_ALLOW_NEW ),
),
),
)
);

Expand All @@ -163,65 +187,49 @@ public function register_settings(): void {
'type' => 'integer',
'default' => self::DEFAULT_MAX_SUGGESTIONS,
'sanitize_callback' => array( $this, 'sanitize_max_suggestions' ),
'show_in_rest' => array(
'schema' => array(
'type' => 'integer',
'minimum' => self::MIN_SUGGESTIONS,
'maximum' => self::MAX_SUGGESTIONS,
),
),
)
);
}

/**
* Renders experiment-specific settings fields.
*
* @since x.x.x
* {@inheritDoc}
*/
public function render_settings_fields(): void {
$strategy_option = $this->get_field_option_name( 'strategy' );
$max_suggestions_option = $this->get_field_option_name( 'max_suggestions' );
$current_strategy = get_option( $this->get_field_option_name( 'strategy' ), self::STRATEGY_EXISTING_ONLY );
$current_max = get_option( $this->get_field_option_name( 'max_suggestions' ), self::DEFAULT_MAX_SUGGESTIONS );
?>
<fieldset class="ai-experiments__item-fields">
<legend class="screen-reader-text"><?php esc_html_e( 'Content Classification Settings', 'ai' ); ?></legend>
<table class="ai-experiments__settings-table" role="presentation">
<tr>
<td>
<label for="<?php echo esc_attr( $strategy_option ); ?>">
<?php esc_html_e( 'Taxonomy strategy:', 'ai' ); ?>
</label>
</td>
<td>
<select
id="<?php echo esc_attr( $strategy_option ); ?>"
name="<?php echo esc_attr( $strategy_option ); ?>"
>
<option value="<?php echo esc_attr( self::STRATEGY_EXISTING_ONLY ); ?>" <?php selected( $current_strategy, self::STRATEGY_EXISTING_ONLY ); ?>>
<?php esc_html_e( 'Only suggest existing terms', 'ai' ); ?>
</option>
<option value="<?php echo esc_attr( self::STRATEGY_ALLOW_NEW ); ?>" <?php selected( $current_strategy, self::STRATEGY_ALLOW_NEW ); ?>>
<?php esc_html_e( 'Suggest new terms based on context', 'ai' ); ?>
</option>
</select>
</td>
</tr>
<tr>
<td>
<label for="<?php echo esc_attr( $max_suggestions_option ); ?>">
<?php esc_html_e( 'Maximum suggestions:', 'ai' ); ?>
</label>
</td>
<td>
<input
type="number"
id="<?php echo esc_attr( $max_suggestions_option ); ?>"
name="<?php echo esc_attr( $max_suggestions_option ); ?>"
value="<?php echo esc_attr( (string) $current_max ); ?>"
min="1"
max="10"
step="1"
/>
</td>
</tr>
</table>
</fieldset>
<?php
public function get_settings_fields(): array {
return array(
array(
'id' => 'strategy',
'label' => __( 'Taxonomy strategy', 'ai' ),
'type' => 'text',
'default' => self::STRATEGY_EXISTING_ONLY,
'elements' => array(
array(
'value' => self::STRATEGY_EXISTING_ONLY,
'label' => __( 'Only suggest existing terms', 'ai' ),
),
array(
'value' => self::STRATEGY_ALLOW_NEW,
'label' => __( 'Suggest new terms based on context', 'ai' ),
),
),
),
array(
'id' => 'max_suggestions',
'label' => __( 'Maximum suggestions', 'ai' ),
'type' => 'integer',
'default' => self::DEFAULT_MAX_SUGGESTIONS,
'isValid' => array(
'min' => self::MIN_SUGGESTIONS,
'max' => self::MAX_SUGGESTIONS,
),
),
);
}

/**
Expand Down Expand Up @@ -249,7 +257,7 @@ public function sanitize_strategy( $value ): string {
public function sanitize_max_suggestions( $value ): int {
$value = absint( $value );

return max( 1, min( 10, $value ) );
return max( self::MIN_SUGGESTIONS, min( self::MAX_SUGGESTIONS, $value ) );
}

/**
Expand Down
11 changes: 6 additions & 5 deletions includes/bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,12 @@ function get_settings_feature_metadata( Registry $registry ): array {

$categories_in_use[ $category ] = true;
$features[] = array(
'id' => $feature_id,
'settingName' => "wpai_feature_{$feature_id}_enabled",
'label' => $feature->get_label(),
'description' => wp_strip_all_tags( $feature->get_description() ),
'category' => $category,
'id' => $feature_id,
'settingName' => "wpai_feature_{$feature_id}_enabled",
'label' => $feature->get_label(),
'description' => wp_strip_all_tags( $feature->get_description() ),
'category' => $category,
'settingsFields' => $feature->get_settings_fields_metadata(),
);
}

Expand Down
Loading
Loading