Skip to content
Closed
Show file tree
Hide file tree
Changes from 56 commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
72b4e56
Build admin settings screen
Nov 2, 2025
013f8e7
Add test coverage for Example_Feature register and render methods
Nov 2, 2025
379e44f
Refactor React components into separate files
Nov 2, 2025
0fc0a70
Include build directory for plugin activation
Nov 2, 2025
7774324
Enable JavaScript linting in test workflow
Nov 2, 2025
a7eacdc
Format code with Prettier
Nov 2, 2025
bfc6154
Revert "Format code with Prettier"
Nov 2, 2025
0194419
Stop tracking build output
Nov 2, 2025
08f883c
Disable JS lint workflow again
Nov 2, 2025
89394cc
Remove generated ai.zip
Nov 2, 2025
876955d
Fix JS lint violations
Nov 2, 2025
cf77782
Restore bootstrap baseline
Nov 2, 2025
db48a13
Re-enable JS lint workflow
Nov 2, 2025
b6740e2
Fix plugin path constants for assets
Nov 2, 2025
6922346
Document admin settings architecture
Nov 2, 2025
cf7fc73
Restore footer illustration in developer guide
Nov 2, 2025
3035c5f
Resolve developer guide footer conflict
Nov 2, 2025
f11fdf4
Merge branch 'trunk' into feature/admin-settings-screen-issue-25
Ref34t Nov 2, 2025
20441b0
Silence phpstan require warning
Nov 3, 2025
2aca4bb
Refactor admin asset loader
Nov 3, 2025
87c57b3
Pass toggles service to default feature filter
Nov 3, 2025
48b2b28
Include build directory in package files
Nov 3, 2025
d2cc9a5
Build bundle before packaging
Nov 3, 2025
bedc00a
Fallback to Playground comment when body update fails
Nov 3, 2025
092ee3b
Guard Playground automation for upstream PRs
Nov 3, 2025
3bd52bf
Revert Playground automation guard
Nov 3, 2025
0d6537b
Re-run Playground workflow for testing
Nov 3, 2025
b6155d5
Restore Playground PR comment fallback
Nov 9, 2025
ce9c0d0
Refactor feature lifecycle and admin UI structure
Nov 9, 2025
b40d86b
Use abstract default-enabled hook directly
Nov 9, 2025
e799aae
Fix coding standard warnings
Nov 9, 2025
33fe657
Refresh developer guide for new structure
Nov 9, 2025
af8ef99
Align assignments per coding standards
Nov 9, 2025
dd1c67f
Tidy alignment per WPCS
Nov 9, 2025
1b1afb8
Fix indentation in feature toggle resolver
Nov 9, 2025
9d3121a
Normalize trait assignment alignment
Nov 9, 2025
d5e2afc
DRY admin settings page title
Nov 9, 2025
b9c502c
Keep notice visible during repeat toggles
Nov 9, 2025
fbe3665
Pin notice placeholder to prevent layout jump
Nov 9, 2025
57ee557
Prevent settings card jump when spinner shows
Nov 9, 2025
52a83e7
Align global toggle layout with feature cards
Nov 9, 2025
b7e6e43
Remove redundant helper copy
Nov 9, 2025
716750e
Decouple global vs feature saving state
Nov 9, 2025
77b7d22
Drop unused helper card body
Nov 9, 2025
d42255b
Add breathing room above global toggle card
Nov 9, 2025
229ed80
Keep feature cards rendered regardless of master toggle
Nov 9, 2025
ba48de1
Use WP_AI_DIR for style file path
Nov 9, 2025
8f44aad
Refine Asset_Loader includes
Nov 9, 2025
092d239
Import Asset_Loader in settings assets
Nov 9, 2025
6a56e7e
Use FQCN for Asset_Loader calls
Nov 9, 2025
3784468
Remove unused CardDivider import
Nov 9, 2025
16ab9e0
Align asset loader with shared implementation
Nov 9, 2025
5e0f0da
Provide helper for admin page title translation
Nov 9, 2025
b9714ee
Stabilize header spinner and fix alignment
Nov 9, 2025
4d40ed4
Remove global toggle spinner
Nov 9, 2025
07bbc1c
Clarify admin layout in developer guide
Nov 9, 2025
8702a48
Streamline admin settings stack
Nov 14, 2025
1778157
Trim developer guide instructions
Nov 14, 2025
3516583
Remove key design principles section
Nov 14, 2025
f165518
Merge branch 'upstream-trunk' into feature/admin-settings-screen-issu…
Nov 14, 2025
ad438dd
Fix Title_Generation feature to match Abstract_Feature architecture
Nov 14, 2025
f7e911b
Fix lint config and tests
Nov 14, 2025
0c6a86d
Implement test feature hooks
Nov 14, 2025
8c57baf
Add settings section for Title Generation feature
Nov 14, 2025
a26ad32
Fix Title Generation ability formatting
Nov 14, 2025
c7975e9
Merge branch 'trunk' into feature/admin-settings-screen-issue-25
Ref34t Nov 14, 2025
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
1 change: 0 additions & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,6 @@ jobs:
permissions:
contents: read
timeout-minutes: 20
if: false # Temporarily disabled

steps:
- name: Checkout repository
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@
"scripts": {
"format": "phpcbf --standard=phpcs.xml.dist",
"lint": "phpcs --standard=phpcs.xml.dist",
"phpstan": "phpstan analyse --memory-limit=1G",
"stan": "phpstan analyse --memory-limit=1G",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason for this naming change?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to have the same way as the rest. can be reverted if it doesn't make sense for you

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to revert

"test": "phpunit --strict-coverage"
},
"scripts-descriptions": {
Expand Down
141 changes: 120 additions & 21 deletions docs/DEVELOPER_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,14 @@ ai/
│ └── Example_Feature/ # Each feature in own directory
│ ├── Example_Feature.php
│ └── README.md
├── admin/ # Admin interface (planned)
├── includes/Admin/ # Admin settings services, controllers, assets
│ ├── Admin_Settings_Page.php # Registers WP admin menu/page
│ ├── Settings_Page_Assets.php # Enqueues scripts/styles
│ ├── Settings_Payload_Builder.php # Serializes registry data
│ └── Settings/ # Admin settings sub-namespace
│ ├── Feature_Toggles.php
│ ├── Settings_Service.php
│ └── …
├── assets/ # CSS, JS, images
├── docs/ # Documentation
│ ├── DEVELOPER_GUIDE.md # This guide
Expand Down Expand Up @@ -140,12 +147,20 @@ class My_Feature extends Abstract_Feature {
}

/**
* Registers the feature's hooks and functionality.
* Registers hooks that should always run.
*
* @since 0.1.0
*/
public function register(): void {
// Register your hooks here
protected function register_shared_hooks(): void {
// Admin settings or dependency checks go here.
}

/**
* Registers hooks that only run when the feature is enabled.
*
* @since 0.1.0
*/
protected function register_enabled_hooks(): void {
add_action( 'init', array( $this, 'initialize' ) );
add_filter( 'the_content', array( $this, 'filter_content' ) );
}
Expand Down Expand Up @@ -176,19 +191,25 @@ class My_Feature extends Abstract_Feature {

### Step 3: Register the Feature

Add your feature class name to the default features list in `Feature_Loader::get_default_features()`:
Add your feature to the default list returned by `Feature_Loader::get_default_features()`:

```php
private function get_default_features(): array {
$feature_classes = array(
'WordPress\AI\Features\Example_Feature\Example_Feature',
'WordPress\AI\Features\My_Feature\My_Feature', // Add your feature
$feature_toggles_provider = apply_filters( 'ai_feature_toggles_service', null );
$feature_toggles = $feature_toggles_provider instanceof Feature_Toggles ? $feature_toggles_provider : null;
$feature_toggles_factory = $this->resolve_feature_toggles_factory( $feature_toggles_provider );

$features = array(
new \WordPress\AI\Features\Example_Feature\Example_Feature( $feature_toggles, $feature_toggles_factory ),
new \WordPress\AI\Features\My_Feature\My_Feature( $feature_toggles, $feature_toggles_factory ),
);

// ... rest of the method
return apply_filters( 'ai_default_features', $features, $feature_toggles, $feature_toggles_factory );
}
```

Third-party developers should prefer the `ai_register_features` or `ai_default_features` hooks described later in this guide so core files don’t need to be modified.

### Step 4: Add Feature Documentation

Create a `README.md` in your feature directory:
Expand All @@ -213,6 +234,81 @@ Examples of how to use the feature.
Any settings or filters available.
```

## Admin Settings Architecture

The admin settings screen allows site administrators to manage AI Experiments globally and per feature. The PHP services live under `includes/Admin/` and the React application under `src/`.

```
includes/
├── Admin/
│ ├── Admin_Settings_Page.php # Registers the options page and fallback markup
│ ├── Settings_Page_Assets.php # Enqueues the React bundle when viewing the page
│ ├── Settings_Payload_Builder.php # Builds the data passed to the React app
│ └── Settings/
│ ├── Feature_Toggles.php # Persists per-feature enable/disable state
│ ├── Settings_Renderer.php # Renders the fallback UI for the toggle section
│ ├── Settings_Registry.php # Registry of settings sections registered by features
│ ├── Settings_Section.php # Immutable value object describing a section
│ ├── Settings_Service.php # Coordinates registration of toggles, sections, and page
│ └── Settings_Toggle.php # Manages the global experiments option
└── Features/
└── Traits/
└── Provides_Settings_Section.php # Helper trait for feature-owned sections

src/
├── admin/
│ └── settings/
│ ├── app.tsx # Top-level settings UI
│ └── components/
│ ├── feature-section.tsx # Card UI for per-feature toggles
│ └── toggle-section.tsx # Card UI for the global toggle
├── global.d.ts # Ambient declaration for the payload on window
├── index.tsx # React entry point mounted on the admin page
├── style.scss # Styles for the settings screen
└── types.ts # Shared payload types
```

`includes/bootstrap.php` wires the settings services on the `init` hook via `initialize_admin_settings()`. That function:

1. Instantiates the toggle, registry, renderer, payload builder, page assets handler, and admin page controller.
2. Registers the shared `Feature_Toggles` service on the `ai_feature_toggles_service` filter. The filter may return an instantiated service, a callable factory, or a `class-string<Feature_Toggles>`, allowing features to resolve the dependency only when they actually need it.
3. Calls `Settings_Service::register()` to hook the global toggle option, expose REST fields, register the admin menu, and trigger section registration with `ai_register_settings_sections`.

Feature settings panels should be registered inside the `ai_register_settings_sections` hook. The `Provides_Settings_Section` trait streamlines the process:

```php
class Example_Feature extends Abstract_Feature {
use Provides_Settings_Section;

protected function register_shared_hooks(): void {
add_action( 'ai_register_settings_sections', array( $this, 'register_settings_sections' ) );
}

protected function register_enabled_hooks(): void {
// Register functional hooks only when enabled.
}

public function register_settings_sections( Settings_Registry $registry ): void {
$this->register_feature_settings_section(
$registry,
'example-feature',
__( 'Example Feature', 'ai' ),
array( $this, 'render_settings_section' ),
array(
'description' => __( 'Demonstration controls for the example feature.', 'ai' ),
'priority' => 20,
)
);
}

public function render_settings_section( Settings_Toggle $toggle, Settings_Section $section ): void {
// Output fallback markup when JavaScript is unavailable.
}
}
```

`Settings_Payload_Builder` serializes the registry into a payload consumed by the React app. Each section’s `enabled` state reflects persisted data from `Feature_Toggles`, ensuring the UI mirrors stored values immediately.

### Conditional Features

If your feature has requirements (PHP extensions, other plugins, etc.), implement validation in your constructor:
Expand Down Expand Up @@ -251,21 +347,24 @@ add_action( 'ai_register_features', function( $registry ) {

### Filtering Default Features

Modify the list of default feature classes before they are instantiated:
Modify the list of default feature instances before they are registered:

```php
add_filter( 'ai_default_feature_classes', function( $feature_classes ) {
add_filter( 'ai_default_features', function( $features, $feature_toggles, $feature_toggles_factory ) {
// Add a custom feature
$feature_classes[] = 'My_Namespace\My_Custom_Feature';

// Remove a default feature
$key = array_search( 'WordPress\AI\Features\Example_Feature\Example_Feature', $feature_classes );
if ( false !== $key ) {
unset( $feature_classes[ $key ] );
}
$features[] = new My_Namespace\My_Custom_Feature(
$feature_toggles,
$feature_toggles_factory
);

return $feature_classes;
} );
// Remove the bundled Example Feature
return array_filter(
$features,
static function ( $feature ) {
return ! $feature instanceof WordPress\AI\Features\Example_Feature\Example_Feature;
}
);
}, 10, 3 );
```

### Disabling a Feature
Expand Down Expand Up @@ -371,4 +470,4 @@ GPL-2.0-or-later

---

<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
<br/><br/><p align="center"><img src="https://s.w.org/style/images/codeispoetry.png?1" alt="Code is Poetry." /></p>
Loading
Loading