|
2 | 2 |
|
3 | 3 | ## Summary |
4 | 4 |
|
5 | | -The Abilities Explorer experiment provides an admin interface for discovering, inspecting, testing, and documenting all abilities registered via the WordPress Abilities API. It adds a menu item ("Abilities Explorer") under the "Tools" menu in the WordPress admin that displays a searchable, filterable table of all registered abilities with the ability to view details and invoke abilities directly from the UI. |
| 5 | +The Abilities Explorer experiment adds an admin screen for finding, inspecting and testing every ability registered through the WordPress Abilities API. It lives at **Tools → Abilities Explorer** (`tools.php?page=ai-abilities-explorer`) and requires the `manage_options` capability. |
6 | 6 |
|
7 | | -## Overview |
| 7 | +The screen is a React application built on `@wordpress/dataviews`, backed by three `ai/v1` REST routes. |
8 | 8 |
|
9 | | -### For End Users |
| 9 | +## What the screen does |
10 | 10 |
|
11 | | -When enabled, the Abilities Explorer adds an "Abilities Explorer" menu item under the "Tools" menu in the WordPress admin sidebar. This provides: |
| 11 | +### List |
12 | 12 |
|
13 | | -**Key Features:** |
| 13 | +- Statistics for Total, Core, Plugins and Theme, counted by each ability's origin. They are computed in the browser from the whole list, so search and filters do not change them. |
| 14 | +- A table of every registered ability, including abilities that are not opted into REST, with Name, Slug and Provider columns. Search covers name, slug and description. The table sorts by name, slug and provider, shows 20 rows per page, and has a **Refresh** button. |
| 15 | +- Filters: |
| 16 | + - **Provider** (single choice). Core, Plugin and Theme match the ability's origin; any other option is a custom `meta.provider` label and matches that exact label. An ability with a custom label from a plugin therefore appears under both "Plugin" and its own label. |
| 17 | + - **Category**. Each category label is listed once and rendered as text. |
| 18 | +- Row actions under each ability's name, shown on hover or keyboard focus as in `WP_List_Table` (always shown on small screens): **View** and **Test**. |
14 | 19 |
|
15 | | -- Dashboard with statistics showing total abilities by provider (Core, Plugin, Theme) |
16 | | -- Searchable, sortable table of all registered abilities |
17 | | -- Filter abilities by provider type |
18 | | -- Detailed view for each ability showing input/output schemas |
19 | | -- Built-in test runner to invoke abilities with custom JSON input |
20 | | -- JSON validation against input schemas |
21 | | -- Copy-to-clipboard functionality for schemas |
| 20 | +### Detail view and test runner |
22 | 21 |
|
23 | | -### For Developers |
| 22 | +- The detail view shows the description, provider, input schema, output schema and raw data. Each JSON block has a Copy button. |
| 23 | +- The test runner prefills example input generated from the input schema, validates it in the browser (**Validate Input**), invokes the ability (**Invoke Ability**) and clears the result and validation panels (**Clear Result**). The JSON input is labelled "Ability test input (JSON)". |
| 24 | +- Validation checks the input's top-level type, required fields, and each top-level property's type, `enum`, `minimum` and `maximum`. Nested objects are not validated. |
24 | 25 |
|
25 | | -The experiment consists of four main classes: |
| 26 | +### Views and links |
26 | 27 |
|
27 | | -1. **Abilities_Explorer** (`Abilities_Explorer.php`): Main experiment class handling registration and asset loading |
28 | | -2. **Admin_Page** (`Admin_Page.php`): Manages admin menu, page rendering, and AJAX handlers |
29 | | -3. **Ability_Handler** (`Ability_Handler.php`): Interfaces with the WordPress Abilities API to fetch, format, validate, and invoke abilities |
30 | | -4. **Ability_Table** (`Ability_Table.php`): Extends `WP_List_Table` for the abilities listing |
| 28 | +Views are addressed by the same query arguments as before, so existing links keep working: |
31 | 29 |
|
32 | | -## Architecture & Implementation |
| 30 | +- List: `tools.php?page=ai-abilities-explorer` |
| 31 | +- Detail: `&action=view&ability=<name>` |
| 32 | +- Test runner: `&action=test&ability=<name>` |
33 | 33 |
|
34 | | -### Key Hooks & Entry Points |
| 34 | +Moving between views updates the URL, so browser Back and Forward work, and each view has a single `h1`, "Abilities Explorer". |
35 | 35 |
|
36 | | -- `WordPress\AI\Experiments\Abilities_Explorer\Abilities_Explorer::register()` wires everything once enabled: |
37 | | - - `admin_enqueue_scripts` → enqueues JS/CSS on the `toplevel_page_ai-abilities-explorer` screen |
38 | | - - `admin_menu` → adds the top-level "Abilities" menu page |
39 | | - - `wp_ajax_ai_ability_explorer_invoke` → handles AJAX ability invocation |
| 36 | +The list remembers its layout, visible columns, sort and page size in the browser's local storage (`ai.abilitiesExplorer.view`). Search, filters and the current page are not saved. |
40 | 37 |
|
41 | | -### Assets & Data Flow |
| 38 | +## REST routes |
42 | 39 |
|
43 | | -1. **PHP Side:** |
44 | | - - `enqueue_assets()` loads `experiments/abilities-explorer` bundle and localizes `window.AbilityExplorer` with: |
45 | | - - `enabled`: Whether the experiment is enabled |
46 | | - - `ajaxUrl`: WordPress AJAX URL |
47 | | - - `nonce`: Security nonce for AJAX requests |
48 | | - - `strings`: Localized UI strings |
| 40 | +All three routes are registered only while the experiment is enabled. They share one permission check: the user must have `manage_options` and must be authenticated by the logged-in cookie with the REST nonce. Requests authenticated any other way, including application passwords, are refused with a 403. None of the routes is registered as an ability. |
49 | 41 |
|
50 | | -2. **JavaScript Side:** |
51 | | - - Handles test runner functionality (invoke, validate, clear) |
52 | | - - Real-time JSON validation in the input textarea |
53 | | - - Copy-to-clipboard for schemas |
54 | | - - Auto-formatting of JSON input |
| 42 | +Ability names are passed as a parameter, never in the path, because names contain `/`. |
55 | 43 |
|
56 | | -### Admin Page Views |
| 44 | +| Method | Route | Parameters | Returns | |
| 45 | +|---|---|---|---| |
| 46 | +| `GET` | `ai/v1/abilities` | none | `items` (summary fields and `meta` for every ability) | |
| 47 | +| `GET` | `ai/v1/abilities/item` | `name` | One ability with `input_schema`, `output_schema`, `raw_data` and `example_input` | |
| 48 | +| `POST` | `ai/v1/abilities/invoke` | `name`, `input` (a JSON string; empty means no input) | `success` with `data`, or `success: false` with `error.code`, `error.message` and `error.data` | |
57 | 49 |
|
58 | | -The admin page supports three views: |
| 50 | +The invoke route answers 404 for an unknown ability and 400 for malformed JSON or input that fails the Explorer's schema check. The ability's own outcome, including its permission check and core's input validation, is a 200 with `success: false`. Invoking writes no AI request log row. |
59 | 51 |
|
60 | | -1. **List View** (`action=list`, default): Statistics dashboard + searchable table |
61 | | -2. **Detail View** (`action=view&ability=slug`): Full ability details with schemas |
62 | | -3. **Test Runner** (`action=test&ability=slug`): Interactive ability testing interface |
| 52 | +A field of a list item that cannot be encoded as JSON is sent as `null` and named in that item's `unencodable_fields`, so one ability cannot stop the list from loading. |
63 | 53 |
|
64 | | -### Provider Detection |
| 54 | +## Behavior changes from the previous screen |
65 | 55 |
|
66 | | -Abilities are categorized by provider: |
| 56 | +The screen used to be a PHP `WP_List_Table` with admin-ajax handlers. Compared with that version: |
67 | 57 |
|
68 | | -- **Core**: Abilities with `wordpress`, `wp`, or `core` namespace prefix |
69 | | -- **Theme**: Abilities matching the active theme's stylesheet or template name |
70 | | -- **Plugin**: All other abilities (default) |
| 58 | +- **Invoke runs under REST.** Abilities are invoked from a REST request, so `is_admin()` is `false` during the call. The route loads `wp-admin/includes/admin.php` first, so admin functions such as `get_plugins()` remain available. |
| 59 | +- **Errors show a code, message and data.** A failed invoke shows the error's `code`, `message` and `data` in the error panel. The old `trace` field, which was always empty, is gone. |
| 60 | +- **Back to List keeps the view's layout.** Returning from the detail view or the test runner keeps the list's layout, visible columns, sort and page size. Search and filters reset. |
| 61 | +- **The list, detail and runner come from REST.** The screen builds all three from REST requests, where `is_admin()` is `false`. An ability that a plugin registers only when `is_admin()` is `true` no longer appears. |
| 62 | +- **The routes accept only an administrator's browser session.** The `ai/v1` Explorer routes require `manage_options`, cookie authentication from a logged-in session, and a valid `wp_rest` nonce (the `X-WP-Nonce` header, or the `_wpnonce` parameter). Application passwords and other non-cookie authentication are refused, so scripts and custom clients cannot call them. |
71 | 63 |
|
72 | | -### Input Validation |
73 | | - |
74 | | -The experiment performs JSON Schema validation: |
75 | | - |
76 | | -- Required field checking |
77 | | -- Type validation (string, number, integer, boolean, array, object) |
78 | | -- Real-time JSON syntax validation in the UI |
| 64 | +## Permissions |
79 | 65 |
|
80 | | -## Using the Test Runner |
| 66 | +- The screen and all three routes require `manage_options`. |
| 67 | +- Invoking an ability still runs that ability's own permission check and input validation through `WP_Ability::execute()`. |
81 | 68 |
|
82 | | -### Testing an Ability |
| 69 | +## Testing |
83 | 70 |
|
84 | | -1. Navigate to **Abilities Explorer** in the "Tools" menu in the admin menu |
85 | | -2. Find the ability you want to test |
86 | | -3. Click the **Test** button |
87 | | -4. Edit the JSON input (pre-populated with example values from the schema) |
88 | | -5. Click **Validate Input** to check JSON syntax and schema compliance |
89 | | -6. Click **Invoke Ability** to execute |
90 | | -7. View the result in the Result section |
| 71 | +### Automated |
91 | 72 |
|
92 | | -### Example Input Generation |
| 73 | +- PHP: `tests/Integration/Includes/Experiments/Abilities_Explorer/` (`Admin_PageTest`, `Ability_HandlerTest`, `Abilities_ExplorerTest`, `REST/Abilities_ControllerTest`). |
| 74 | +- End to end: `tests/e2e/specs/experiments/abilities-explorer-*.spec.js` (list, runner, navigation), with helpers in `tests/e2e/utils/abilities-explorer.js`, fixture abilities from `tests/e2e-testing/e2e-testing.php` and the field extension fixture in `tests/e2e-plugins/abilities-explorer-field`. |
93 | 75 |
|
94 | | -The test runner automatically generates example input based on the ability's input schema: |
| 76 | +### Manual |
95 | 77 |
|
96 | | -- Uses `default` values if specified |
97 | | -- Falls back to `example` values |
98 | | -- Generates type-appropriate defaults (empty string, 0, false, empty array/object) |
| 78 | +1. Go to **Settings → AI** and enable **Abilities Explorer**. |
| 79 | +2. Open **Tools → Abilities Explorer**. Check the statistics, search, the Provider and Category filters, and sorting. |
| 80 | +3. Use **View** on a row. Check the schemas and the Copy buttons. |
| 81 | +4. Use **Test** on a row. Edit the input, then use **Validate Input**, **Invoke Ability** and **Clear Result**. |
99 | 82 |
|
100 | | -## Permissions |
| 83 | +## Adding fields to the table |
101 | 84 |
|
102 | | -- Requires `manage_options` capability to access the admin page |
103 | | -- AJAX requests verify nonce and capability before invoking abilities |
| 85 | +Other plugins can add read-only columns to the Abilities Explorer list through the `ai.abilitiesExplorer.fields` JavaScript filter. The list is built with `@wordpress/dataviews`, and each column is a DataViews field. |
104 | 86 |
|
105 | | -## Testing |
| 87 | +### Enqueueing the script |
106 | 88 |
|
107 | | -### Manual Testing |
| 89 | +Enqueue the script on the Explorer screen (its hook suffix is `tools_page_ai-abilities-explorer`) and make it depend on `wp-hooks`. Do not depend on the Explorer's own script handle: the Explorer bundle loads deferred, and its handle is not a public API. The filter is read when the list renders and again whenever a callback is added to or removed from it, so the column shows whether your script runs before or after the Explorer. |
108 | 90 |
|
109 | | -1. **Enable the experiment:** |
110 | | - - Go to `Settings → AI` |
111 | | - - Toggle **Abilities Explorer** to enabled |
| 91 | +```php |
| 92 | +add_action( |
| 93 | + 'admin_enqueue_scripts', |
| 94 | + static function ( string $hook_suffix ): void { |
| 95 | + if ( 'tools_page_ai-abilities-explorer' !== $hook_suffix ) { |
| 96 | + return; |
| 97 | + } |
112 | 98 |
|
113 | | -2. **Test the list view:** |
114 | | - - Navigate to **Abilities Explorer** in the "Tools" menu in the admin menu |
115 | | - - Verify statistics are displayed |
116 | | - - Search for abilities by name or description |
117 | | - - Filter by provider type |
118 | | - - Sort columns |
| 99 | + wp_enqueue_script( |
| 100 | + 'my-plugin-explorer-field', |
| 101 | + plugins_url( 'explorer-field.js', __FILE__ ), |
| 102 | + array( 'wp-hooks' ), |
| 103 | + '1.0.0', |
| 104 | + true |
| 105 | + ); |
| 106 | + } |
| 107 | +); |
| 108 | +``` |
119 | 109 |
|
120 | | -3. **Test the detail view:** |
121 | | - - Click on an ability name or **View** button |
122 | | - - Verify input/output schemas are displayed |
123 | | - - Test copy buttons |
| 110 | +### Adding a field |
124 | 111 |
|
125 | | -4. **Test the test runner:** |
126 | | - - Click **Test** on any ability |
127 | | - - Modify the JSON input |
128 | | - - Click **Validate Input** |
129 | | - - Click **Invoke Ability** |
130 | | - - Verify results are displayed |
| 112 | +The filter receives the current fields, built-ins first, and returns the full list. Append your field and return the array. |
131 | 113 |
|
132 | | -## Notes & Considerations |
| 114 | +```js |
| 115 | +wp.hooks.addFilter( |
| 116 | + 'ai.abilitiesExplorer.fields', |
| 117 | + 'my-plugin/slug-length', |
| 118 | + ( fields ) => [ |
| 119 | + ...fields, |
| 120 | + { |
| 121 | + id: 'my-plugin/slug-length', |
| 122 | + label: 'Slug length', |
| 123 | + type: 'integer', |
| 124 | + enableSorting: true, |
| 125 | + getValue: ( { item } ) => item.slug.length, |
| 126 | + }, |
| 127 | + ] |
| 128 | +); |
| 129 | +``` |
133 | 130 |
|
134 | | -### Requirements |
| 131 | +Each row (`item`) is one ability as returned by `GET ai/v1/abilities`, so a field can read properties such as `slug`, `name`, `provider`, `category` and `meta`. Several of them, including `name`, `provider`, `category` and `meta`, can be `null`, for example when the server could not encode that part of an ability, so read them with a fallback. Prefix the field ID with your plugin's namespace so it cannot clash with another extension. |
135 | 132 |
|
136 | | -- Requires the WordPress Abilities API (`wp_get_abilities`, `wp_get_ability` functions) |
137 | | -- Users must have `manage_options` capability |
| 133 | +### What a field may carry |
138 | 134 |
|
139 | | -### Limitations |
| 135 | +An extension field keeps only these DataViews properties: `id`, `type`, `label`, `header`, `description`, `render`, `getValue`, `getValueFormatted`, `sort`, `format`, `elements`, `filterBy`, `enableSorting`, `enableHiding` and `enableGlobalSearch`. Anything else is dropped, including edit controls (`Edit`, `setValue`, `isValid`) and anything like `actions`, and every extension field is marked `readOnly`. Extension fields never add row actions. |
140 | 136 |
|
141 | | -- Provider detection is heuristic-based and may not be accurate for all abilities |
142 | | -- Input validation is basic JSON Schema validation (required fields, types) |
143 | | -- Does not support nested object validation |
| 137 | +- **Built-in fields win.** A field whose ID matches a built-in field (`name`, `slug`, `provider`, `category`, `description`) is ignored, and returning a list without the built-ins does not remove them. If two extensions use the same ID, the first one wins. |
| 138 | +- **Failures fall back.** If a filter callback throws or returns something other than an array, the list shows only the built-in fields. A `getValue`, `getValueFormatted` or `sort` callback that throws yields an empty value (or no reordering), and a `render` callback that throws renders an empty cell, so one extension cannot break the table. |
| 139 | +- **Saved views keep your column.** A new field is added to the visible columns the first time the Explorer sees it, including on a first visit with nothing saved. After that it is the user's choice to hide it. If your plugin is deactivated, its column's place in the user's saved view is kept, and it shows again when the plugin is reactivated. |
0 commit comments