Repository navigation
Abilities Explorer: move to DataViews and REST (from #1074) - #1181
Conversation
Closes #1016. See #203. Moves the whole Abilities Explorer screen (list, statistics, the "Exposed in" column and its actions, detail view, test runner) off `WP_List_Table`, vanilla JS and admin-ajax onto a `@wordpress/dataviews` screen backed by four new `ai/v1` routes. Stacked on `feat/ai-workspace` (#1004), because the "Exposed in" column and its actions exist only there. The Explorer was the plugin's last admin screen on the old stack, and its hand-written search, sort and filters kept needing repair (#883, owner most needs answered, which abilities the assistant can reach, but it could not be filtered without more hand-written filtering. #203 asked for a custom-column hook, which would have shipped an API against a table about to be replaced. **REST routes** (`includes/Experiments/Abilities_Explorer/REST/Abilities_Controller.php`), registered only while the experiment is on and never as abilities: | Route | Method | Purpose | |---|---|---| | `ai/v1/abilities` | GET | every registered ability, including ones without `show_in_rest`, plus the assistant policy state | | `ai/v1/abilities/item?name=` | GET | one ability with schemas, raw data and example input | | `ai/v1/abilities/invoke` | POST | runs the ability through `WP_Ability::execute()`, so its own permission check still applies | | `ai/v1/abilities/surface` | POST | remove, restore, disable_policy or enable_policy | All four share one permission check: `manage_options`, cookie authentication, a valid `wp_rest` nonce, and no application password. The nonce is checked in the callback itself because core skips its own check when an earlier authentication filter has already answered. Ability names travel in the query string or body, never the path, so an encoded `/` cannot 404 on Apache. Each list item is encode-checked on its own, so one ability with unencodable data cannot blank the list. **Screen** (`src/experiments/abilities-explorer/`): a React app mounted like the AI Request Logs page. It routes between list, detail, runner and not-found on the existing `action` and `ability` query args, with browser Back and Forward. The provider filter keeps the #883 rule (origin for Core, Plugin and Theme, exact label otherwise). "Exposed in" is now filterable by assistant state. Surface changes wait for the server, and responses carry a sequence number, tracked per row, so a late refresh cannot undo a newer change. **#203:** plugins that depend on `wp-hooks` can add read-only fields with the `ai.abilitiesExplorer.fields` filter. Built-in field IDs win on a collision, a filter that throws or returns a non-array leaves the built-ins, and a saved view keeps a third-party column's ID while its plugin is inactive. Documented in `docs/experiments/abilities-explorer.md`. **Behavior changes worth knowing:** - Invoke now runs under REST, where `is_admin()` is false. The route loads `wp-admin/includes/admin.php` first. - The list is built from a REST request too, so an ability a plugin registers only when `is_admin()` is true no longer appears. - Errors show the ability's code, message and data instead of the always-null `trace`. - The admin-ajax action `ai_ability_explorer_invoke` and the `Ability_Table` class are removed. - Old list bookmarks with `s`, `orderby` or filter args land on the unfiltered list. - Back to List keeps the view's layout, columns, sort and page size. Search and filters reset. - Row actions (View, Test, and Remove from or Return to assistant) sit under each ability's name and show on hover or keyboard focus, as in `WP_List_Table`. They are always shown below 782px. AI assistance: Yes Tool(s): Claude Code Model(s): Claude Opus 5.5 Used for: Planning, implementation of the REST controller, React screen, tests and docs, and a multi-reviewer code review whose confirmed findings were fixed in this branch. <!-- JAKE: add your review attestation here in your own words before opening. --> 1. Enable Experiments and the Abilities Explorer experiment, then open **Tools > Abilities Explorer**. 2. Filter by Provider, Category and "Exposed in", search, sort and page. The statistics should not change with search or filters. 3. With the AI Workspace experiment on, remove an ability from the assistant, return it, and turn the admission policy off and on. Each change should show a notice. 4. Open an ability's detail view, copy a schema, then open **Test Ability**. For `ai/content-classification`, `max_suggestions` 11 should report "must be at most 10", and 5 should validate. 5. Use browser Back and reload on each view. - `npm run build`, `npm run typecheck`, `npm run lint:js`, `npm run lint:php`, `npm run lint:php:stan`: all pass. - `npm run test:php`: 1988 tests, 40 skipped, 0 failures. - Explorer e2e, 24 tests across four files, all passing: - `tests/e2e/specs/experiments/abilities-explorer-list.spec.js`: - shows the statistics and the table under a single heading - narrows the rows with the "Category" filter - matches "Plugin" by origin and "Acme" by its exact label - keeps the statistics unaffected by search - filters "Exposed in" down to what the assistant can reach - restores layout, fields and sort from a saved view, but not search or filters - loads a saved view with an unknown field ID and keeps the ID - `tests/e2e/specs/experiments/abilities-explorer-surface.spec.js`: - removes an ability and returns it, with a notice for each - returning a withheld ability shows the withheld reason, not the assistant badge - turns the admission policy off and on, updating every row - disables a pending change, so a second click sends nothing - does not let a refresh answered after a remove undo it - moves focus to the list when a change filters its row out - `tests/e2e/specs/experiments/abilities-explorer-runner.spec.js`: - shows each detail section and copies the JSON - validates deep-linked input against the schema - shows the success and error panels, and Clear hides them - never shows one runner the result of another ability in flight - `tests/e2e/specs/experiments/abilities-explorer-navigation.spec.js`: - keeps one heading per view, and Back and reload keep the view - fetches an ability again when it is opened after returning to the list - shows an extension column on first load and keeps it through deactivation - keeps the built-in columns when the filter returns a non-array - does not let an extension replace a built-in field - is unreachable when the experiment is off - is unreachable when AI is globally off - Full `npm run test:e2e`: running at the time this PR was opened; the result will be added below. <details><summary>PHPUnit tests added or moved in this branch (67)</summary> `Abilities_ExplorerTest` (2): - `test_rest_routes_are_registered_when_enabled` - `test_rest_routes_are_absent_when_disabled` `Ability_HandlerTest` (5): - `test_validate_input_accepts_a_type_list` - `test_generate_example_input_returns_empty_for_empty_schema` - `test_generate_example_input_uses_default_values` - `test_generate_example_input_uses_example_values` - `test_generate_example_input_generates_type_defaults` `Admin_PageTest` (12): - `test_load_hook_registers_help_tabs_and_assets` - `test_assets_are_hooked_only_when_the_screen_loads` - `test_enqueue_assets_enqueues_bundle_and_dataviews_fallback` - `test_enqueue_assets_skips_dataviews_fallback_when_core_registers_it` - `test_enqueue_assets_does_nothing_for_an_editor` - `test_localized_settings_route_map_matches_controller_constants` - `test_localized_settings_carry_reason_and_provider_labels` - `test_render_page_works_for_admin` - `test_render_page_ignores_action_for_server_render` - `test_render_page_outputs_nothing_for_editor` - `test_render_page_outputs_nothing_when_logged_out` - `test_help_tabs_register_on_screen` `Abilities_ControllerTest` (48): - `test_list_includes_an_ability_not_shown_in_rest` - `test_list_keeps_origin_and_provider_apart_and_sends_the_category_label_unescaped` - `test_list_decodes_a_category_label_escaped_by_a_filter` - `test_list_survives_an_ability_with_unencodable_meta` - `test_item_returns_schemas_and_example_input` - `test_item_returns_404_for_an_unregistered_name` - `test_item_returns_400_for_a_missing_name` - `test_invoke_with_valid_input_returns_the_data` - `test_invoke_with_input_failing_explorer_validation_returns_400` - `test_invoke_with_input_failing_core_validation_returns_the_ability_error` - `test_invoke_accepts_a_property_typed_with_a_type_list` - `test_invoke_without_an_input_schema_accepts_empty_and_null` - `test_invoke_with_input_omitted_invokes_with_no_input` - `test_invoke_decodes_scalar_input_on_the_server` - `test_invoke_with_malformed_json_returns_400` - `test_invoke_refuses_non_string_input` - `test_invoke_returns_404_for_an_unregistered_ability` - `test_invoke_reports_the_ability_permission_denial_as_its_outcome` - `test_invoke_writes_no_request_log_row` - `test_surface_remove_stores_the_exclusion_and_a_repeat_is_no_change` - `test_surface_restore_clears_the_exclusion` - `test_surface_restore_of_a_withheld_ability_keeps_it_off_the_assistant` - `test_surface_policy_switch_disables_and_enables_and_returns_the_list` - `test_surface_refuses_an_unknown_change` - `test_surface_remove_refuses_an_unregistered_or_missing_name` - `test_surface_get_changes_nothing` - `test_list_reports_only_known_origins` - `test_list_carries_a_custom_provider_beside_the_known_origins` - `test_list_lets_a_custom_provider_row_match_its_origin_and_its_label` - `test_list_marks_a_declared_admitted_ability_as_on_the_assistant` - `test_list_carries_the_reason_an_ability_is_off_the_assistant` - `test_list_and_function_declaration_share_one_description_source` - `test_list_does_not_report_a_removed_ability_as_held_without_the_workspace_bootstrap` - `test_list_reports_the_general_public_flag_on_both_channels` - `test_list_reports_rest_and_mcp_exposure_independently` - `test_surface_remove_takes_the_ability_off_the_model_declarations` - `test_surface_restore_returns_the_ability_to_the_model_declarations` - `test_surface_policy_switch_withdraws_and_returns_admitted_abilities` - `test_editor_is_refused_on_every_route` - `test_application_password_is_refused_on_every_route` - `test_application_password_is_refused_even_alongside_the_cookie_flag` - `test_determine_current_user_without_a_cookie_is_refused_on_every_route` - `test_cookie_administrator_without_a_nonce_is_refused_on_every_route` - `test_cookie_administrator_with_an_invalid_nonce_is_refused_on_every_route` - `test_cookie_administrator_with_a_nonce_for_another_action_is_refused_on_every_route` - `test_cookie_administrator_with_the_nonce_as_a_parameter_is_accepted` - `test_cookie_administrator_reaches_every_route` - `test_no_route_is_registered_as_an_ability` </details> From the branch's code review, not blocking: - [ ] P3 — `src/experiments/abilities-explorer/api.ts` — The response sequencer and `validate.ts` have no JS unit tests, because the repo has no JS unit test setup. They are covered only through e2e. - [ ] P3 — `includes/Experiments/Abilities_Explorer/REST/Abilities_Controller.php` — The invoke success payload is not passed through the strict encode check the list and item use, so an ability returning NaN or INF produces a generic client error. - [ ] P3 — `src/experiments/abilities-explorer/fields.tsx` — `BUILT_IN_FIELD_IDS` is a hand-kept list. Deriving it from `getBuiltInFields()` would stop a new built-in field from being overridable. - [ ] P3 — Response ordering uses one server's `microtime()`. On multi-node hosting with clock skew, a stale list could briefly show a row's old state until the next refresh. > Changed - Rebuilt the Abilities Explorer on DataViews with REST routes, a filterable "Exposed in" column, and a JavaScript filter for adding read-only table fields. 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- wp-playground-preview:start --> <a href="https://playground.wordpress.net?blueprint-url=data:application/json,%7B%22preferredVersions%22%3A%7B%22wp%22%3A%22latest%22%7D%2C%22login%22%3Atrue%2C%22landingPage%22%3A%22%2Fwp-admin%2Fadmin.php%3Fpage%3Dai-wp-admin%22%2C%22steps%22%3A%5B%7B%22step%22%3A%22installPlugin%22%2C%22pluginData%22%3A%7B%22resource%22%3A%22url%22%2C%22url%22%3A%22https%3A%2F%2Fgithub.com%2FWordPress%2Fai%2Freleases%2Fdownload%2Fci-artifacts%2Fpr-1074-88aa03bda577ce567570cbd90eb3be378851aad0.zip%22%7D%7D%5D%7D" target="_blank" rel="noopener noreferrer"> <img src="https://raw.githubusercontent.com/adamziel/playground-preview/refs/heads/trunk/assets/playground-preview-button.svg" alt="Open WordPress Playground Preview" width="220" height="57" /> </a> <!-- wp-playground-preview:end --> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Conflicts resolved while picking onto develop: - Ability_Table.php, index.js, Ability_TableTest.php and abilities-explorer.spec.js: kept the deletion. - .wp-env.test.json: added only the abilities-explorer-field plugin, not the ai-provider-for-anthropic zip from the Workspace branch. - Admin_Page.php, Ability_Handler.php, index.scss: took this commit's side. - Ability_HandlerTest.php: kept the type-list test from #1073 beside this commit's, with its expected message updated to the ", " join used here. (cherry picked from commit 0c96aab) Co-authored-by: Jeff Paul <jeff.paul@fueled.com>
The DataViews rewrite from #1074 was built on the AI Workspace branch (#1004), which is not on develop. Its "Exposed in" column and the assistant surface controls read and write AI_Workspace\Tool_Policy, which does not exist here and would fatal. Removes: - The `ai/v1/abilities/surface` route, `policy_state()` and the response sequence numbers, which only kept a late list refresh from undoing a surface change. - The surface reason labels and the `show_in_rest`, `show_in_mcp`, `conversational_surface`, `surface_reason`, `surface_reason_label` and `owner_excluded` list fields. Each row still carries `meta`. - The "Exposed in" field and filter, the row and policy surface actions, and the client response sequencer. - The surface e2e spec, its helpers and fixtures, and the PHPUnit cases for the surface route, assistant state and model declarations. - The AI Workspace text in the Explorer docs. The `ai.abilitiesExplorer.fields` filter stays, so the Workspace can add its column back from its own code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DM4Q4KoGRf8wxhcCQy1hfA Co-authored-by: Jeff Paul <jeff.paul@fueled.com>
- The "unreachable when AI is globally off" e2e test switched off the `wpai_features_enabled` option, which #985 removed from develop, so the Explorer stayed reachable and the test failed. The E2E Testing plugin now has an `ai-e2e/v1/ai-disabled` switch that turns every feature off through the `wpai_features_enabled` filter, which is how AI is globally off on develop. - `test_no_route_is_registered_as_an_ability` boots every experiment, and Markdown Feeds (on develop, not on the Workspace branch) adds its feed to the global `$wp_rewrite`. The test now restores the feeds, so a later Markdown_FeedsTest is not left with a doubled feed URL. - The manual testing step no longer says to turn AI on. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DM4Q4KoGRf8wxhcCQy1hfA Co-authored-by: Jeff Paul <jeff.paul@fueled.com>
✅ WordPress Plugin Check Report
📊 ReportAll checks passed! No errors or warnings found. 🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check |
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #1181 +/- ##
=============================================
+ Coverage 82.25% 83.74% +1.49%
+ Complexity 3403 3388 -15
=============================================
Files 136 136
Lines 13225 13062 -163
=============================================
+ Hits 10878 10939 +61
+ Misses 2347 2123 -224
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… the next commit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DM4Q4KoGRf8wxhcCQy1hfA Co-authored-by: Jeff Paul <jeff.paul@fueled.com>
They stay reachable from the previous commit, which the PR description links to. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DM4Q4KoGRf8wxhcCQy1hfA Co-authored-by: Jeff Paul <jeff.paul@fueled.com>
whyisjake
left a comment
There was a problem hiding this comment.
Thanks for carrying this over, @jeffpaul. Approving.
I checked the cherry-pick (a51a92e) against the original squash commit (0c96aab): the only differences are the conflict resolutions listed in the description. The Workspace removal in b6c78b5 is complete. Nothing in the Explorer's code, tests or docs still refers to Tool_Policy, AI_Workspace, the surface route, or the response sequence numbers. Dropping the sequencer is safe, since Refresh is disabled while a load is in flight and the surface writes it guarded against are gone. The ai.abilitiesExplorer.fields filter stays, so #1004 can add the "Exposed in" column back from its own code. The test adaptations in 2a77b4b look right: the AI-off switch goes through the same wpai_features_enabled filter Features\Loader checks, and restoring $wp_rewrite->feeds fixes real cross-test pollution.
One optional suggestion: ai_e2e_ai_disabled is a persistent option. If a run is killed while it's on, every later run starts with AI globally off and fails in ways that look unrelated. Deleting it in tests/e2e/config/global-setup.ts, next to the credentials seed, would prevent that.
AI assistance: Yes. Tool(s): Claude Code. Used for: comparing the cherry-pick to the original commit and checking for leftover AI Workspace references.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the Unlinked AccountsThe following contributors have not linked their GitHub and WordPress.org accounts: @claude. Contributors, please read how to link your accounts to ensure your work is properly credited in WordPress releases. If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
`ai_e2e_ai_disabled` is a stored option, so a run stopped while it was on left AI globally off for every later run, which then failed in specs unrelated to the Explorer. The global setup now switches it off before any spec runs. Suggested by @whyisjake in review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DM4Q4KoGRf8wxhcCQy1hfA Co-authored-by: Jeff Paul <jeff.paul@fueled.com>
|
@whyisjake thanks. Your suggestion is in as d997063: I checked it by turning the switch on and then running a spec that never touches it ( Generated by Claude Code |
|
Test PHP 7.4 WP 7.0.4 and Test PHP 8.0 WP trunk failed on d997063 before any test ran, and the failure isn't this PR's. Both jobs failed while wp-env built its PHP Docker image. The image is based on Debian bullseye. At This commit only changes Generated by Claude Code |
|
Tested and this works as expected, opening up for review. |
There was a problem hiding this comment.
Couple flags around what I'd consider AI slop but otherwise things test well.
Beyond those things, I have some suggestions on the UI:
- Currently there's no spacing between the main page header and the content below it:
-
Personal opinion but I don't love the fixed header as you scroll. Not sure if provides much value here as there's not much in that header. I'd suggest we keep that at the top instead of having it scroll with you.
-
There's a lot of whitespace now in the table. Can we show the Abilities descriptions in a column? That fills the space and provides some nice details:
- Not sure we need a
Refreshbutton in the header. Do Abilities change often enough that someone would want a button they can click, especially when they can just use the browser refresh button?
- Might be nice to move the help context menu into the new UI. Right now that sticks out a bit compared to how the rest of the page looks.
…d in the next commit) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for the review @dkotter. I opened stacked draft PR #1193 against this branch with the UI fixes. Merging it lands the commits here. It has three commits:
The Abilities Explorer e2e specs pass locally (17 passed). Two things I need your input on before I touch them:
You're right about the tests and the docblocks, so I'll do those in a separate follow-up pass:
AI assistance: Yes. Tool(s): Claude Code. Used for: the changes in #1193 and drafting this comment. |
|
@dkotter the follow-up on the tests and comments is up in two more PRs, alongside #1193:
All three pass the Explorer's PHPUnit and e2e tests locally. The open questions on the fixed header and the Help tab still stand. AI assistance: Yes. Tool(s): Claude Code. Used for: the changes in #1194 and #1196 and drafting this comment. |
…tton (#1193) Abilities Explorer: header spacing, description column, no Refresh button --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Darin Kotter <darin.kotter@gmail.com>
Abilities Explorer: trim the tests to the behavior they guard --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…1196) Abilities Explorer: trim the comments to the ones that explain why --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Darin Kotter <darin.kotter@gmail.com>
Sorry, typo on my part. I've fixed this myself so we're good here.
This requires some exploration/trying things out. I'm assuming there's a component in the Fine to leave as-is for now, was flagging as it would have been nice to tackle now instead of pushing off for later. |
The Abilities Explorer reached develop through #1181, which took the #1074 rewrite off this branch and removed its AI Workspace dependency (b6c78b5). The Explorer files take develop's version, including the review changes from #1193, #1194 and #1196, with the workspace's surface integration put back on top: the "Exposed in" field and filter, the row and policy surface actions, the response sequencer, the surface route, the list fields Ability_Handler adds from Tool_Policy, and their tests. Develop removed the Refresh button, so the list loads once and the sequencer no longer has a refresh to order against. The e2e test for a refresh answered after a remove is dropped with it. The list, navigation and runner specs this branch split out are dropped in favour of develop's single abilities-explorer.spec.js, and the field-extension plugin is replaced by develop's e2e_fields switch. The surface spec and its helpers stay. The e2e fixtures keep develop's always-on Explorer abilities and add the workspace's switched assistant fixtures and sequenced mock scenarios. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>


What?
Closes #1016
Closes #203
Brings @whyisjake's Abilities Explorer rewrite from #1074 (DataViews and REST) onto
develop. The work is his. #1074 was squash-merged intofeat/ai-workspaceas0c96aabb, stacked on the AI Workspace branch (#1004), which is not ondevelopand is in Future Release. This PR cherry-picks that commit with-x, so Jake stays its author. A follow-up commit removes the AI Workspace dependency, so the Explorer rewrite can ship in 1.5.0 without the Workspace.Props @whyisjake
Why?
The Explorer was the plugin's last admin screen on
WP_List_Table, vanilla JS and admin-ajax. Its hand-written search, sort and filters kept needing repair (#883, #641, #648, #344, #588). #1016 is in the 1.5.0 milestone, but #1074 landed on a branch that is not.#203 asked for a PHP hook to swap the
Ability_Tableclass. That table is removed here, so #203 is resolved by theai.abilitiesExplorer.fieldsJavaScript filter instead: a plugin that depends onwp-hookscan add read-only DataViews columns. Each row carries the ability'smeta, so the MCP column from #203 could look like this:Enqueueing and the full rules are in
docs/experiments/abilities-explorer.mdunder "Adding fields to the table". @raftaar1191, could you confirm this covers your use case?How?
Three commits change code:
Abilities Explorer: move to DataViews and REST (#1074): the cherry-pick, authored by Jake Spurlock. Conflict resolution only:Ability_Table.php,src/experiments/abilities-explorer/index.js,Ability_TableTest.phpandabilities-explorer.spec.js: kept Abilities Explorer: move to DataViews and REST #1074's deletions. I diffed each againstdevelopfirst. Nothingdevelopadded since the branch point is lost:index.jsonly gained Abilities Explorer: Make the input validation messages translatable #1146's translated validation messages, whichvalidate.tsalready has.Ability_Table.phpandAbility_TableTest.phphad nodevelopchanges..wp-env.test.json: added only./tests/e2e-plugins/abilities-explorer-field. Theai-provider-for-anthropiczip came from the Workspace branch and no test here needs it.Admin_Page.php,Ability_Handler.php,index.scss: took Abilities Explorer: move to DataViews and REST #1074's side.Ability_HandlerTest.php: kept Abilities Explorer: accept input properties that list several types #1073'stest_validate_input_validates_type_listnext to Abilities Explorer: move to DataViews and REST #1074'stest_validate_input_accepts_a_type_list, and changed Abilities Explorer: accept input properties that list several types #1073's expected message to Abilities Explorer: move to DataViews and REST #1074's wording (see below).Abilities Explorer: remove the AI Workspace dependency(mine). Details below.Abilities Explorer: adapt the tests to develop(mine). Details below.Two more commits add the screenshots below under
pr-screenshots/and remove them again, so the description can link them by commit. They leave no files behind.Removed for
develop(the AI Workspace dependency)WordPress\AI\Experiments\AI_Workspace\Tool_Policydoes not exist ondevelop, and the picked code would fatal on it. Removed:Tool_Policyimport and calls inREST/Abilities_Controller.phpandAbility_Handler.phpai/v1/abilities/surfaceroute and itsSURFACE_ROUTEandSURFACE_CHANGESconstantschange_surface(),policy_state(), and thepolicyandsequencefields of the list response. The sequence numbers existed only so a late list refresh could not undo a surface change. Refresh is disabled while a load is in flight, so list requests can't overlap.get_surface_reason_labels(),get_surface_reason_label()and thesurfaceReasonLabelslocalized settingconversational_surface,surface_reason,surface_reason_labelandowner_excluded, plusshow_in_restandshow_in_mcp. The last two only fed the "Exposed in" column's REST and MCP badges. Each row still carriesmeta.components/list/ExposedIn.tsx),SurfaceActions.tsxandPolicyToggle.tsxfields.tsx,NameCell.tsx,ListView.tsx,App.tsx,api.ts(changeSurface()and the response sequencer) andtypes.tstests/e2e/specs/experiments/abilities-explorer-surface.spec.jspayloadRowintests/e2e/utils/abilities-explorer.jstests/e2e-testing/e2e-testing.phpStreaming_Turn_Driverdocs/experiments/abilities-explorer.md.The code has no
class_exists()guards or dead branches. Theai.abilitiesExplorer.fieldsfilter stays, so the Workspace can add its column back from its own code.Adapted for
developwpai_features_enabled: falsethrough/wp/v2/settings. That setting is no longer registered, so the Explorer stayed reachable and the test failed (checked). AI is now globally off only through thewpai_features_enabledfilter. The E2E Testing plugin gets anai-e2e/v1/ai-disabledswitch that turns that filter on.develop.test_no_route_is_registered_as_an_abilityboots every experiment, and Markdown Feeds adds its feed to the global$wp_rewrite, whichWP_UnitTestCasedoesn't restore.Markdown_FeedsTest::test_singular_markdown_feed_url_is_not_redirectedthen failed later in the run. The test now restores$wp_rewrite->feeds.should be of type "string" or "object". Abilities Explorer: move to DataViews and REST #1074 uses"string, object"on both the server and the client validator, and treats an empty list or a non-string entry as no constraint, as the client does. I kept Abilities Explorer: move to DataViews and REST #1074's implementation so server and client agree, and updated Abilities Explorer: accept input properties that list several types #1073's test to the new message. Its other cases pass unchanged.Fixes on
developsince the branch pointgit log d0b54f07..origin/develop -- includes/Experiments/Abilities_Explorer src/experiments/abilities-explorer testslists 33 commits. Five touch files #1074 changes:ac5920c8, type lists): covered by Abilities Explorer: move to DataViews and REST #1074'sAbility_Handler::validate_property()andvalidate.ts. Its test is kept, adapted as above.c7159935, translatable validation messages): every user-facing string invalidate.tsgoes through__()orsprintf( __() )with theaitext domain. The old "AJAX request failed" string went away with admin-ajax.a8809576): adapted as above.develop'sAbilities_ExplorerTestsetup, without the global option, is kept.04e09f0f): its comment rename ine2e-testing.phpis kept.2ed5642f): its@since 1.4.0on Abilities Explorer: accept input properties that list several types #1073's test is kept.The other 28 commits touch none of the files #1074 changes.
REST routes
All three are registered only while the experiment is on, and none is registered as an ability:
ai/v1/abilitiesshow_in_restai/v1/abilities/item?name=ai/v1/abilities/invokeWP_Ability::execute(), so its own permission check still appliesThey share one permission check:
manage_optionswp_restnonce, checked in the callback itselfAbilities_ControllerTeststill covers each refusal on every route.Abilities_ExplorerTestcovers registration with the experiment on and off, and the navigation e2e spec covers the screen being unreachable with the experiment off and with AI globally off.Behavior changes worth knowing
is_admin()is false. The route loadswp-admin/includes/admin.phpfirst.is_admin()is true no longer appears.trace.ai_ability_explorer_invokeand theAbility_Tableclass are removed.s,orderbyor filter args land on the unfiltered list.WP_List_Table. They are always shown below 782px.Use of AI Tools
AI assistance: Yes
Tool(s): Claude Code
Model(s): Claude Opus 5.5
Used for: The cherry-pick's conflict resolution, removing the AI Workspace dependency, adapting the tests to
develop, running the checks, and drafting this description. #1074's own code was also written with Claude Code, per its disclosure.Testing Instructions
ai/content-classification,max_suggestions11 should report "must be at most 10", and 5 should validate.tools.php?page=ai-abilities-explorershould report that you are not allowed to access the page.Checks run
wp-env could not start in the environment I worked in: its network policy blocks wordpress.org and the Alpine package mirror the wp-env images build from. I ran the same tools directly instead, so CI on this PR is the authoritative run.
npm run build,npm run typecheck: pass.npm run lint:js: 0 errors. The one new warning (jsdoc/reject-function-type) is onrestRoute()intests/e2e/utils/abilities-explorer.js, from Abilities Explorer: move to DataViews and REST #1074.lint:php(composer run-script lint) andlint:php:stan(composer run-script phpstan), run on local PHP 8.3 rather than in the wp-env container: pass, PHPStan with no errors.test:php: run against the WordPress 7.1.3 PHPUnit suite on the SQLite database drop-in, not MySQL.tests/Integration/Includes/Experiments/Abilities_Explorer/: 76 tests, all pass.developin the same setup. They come from SQLite (table schema checks) and plugin path detection. No test fails only on this branch.test:e2e: run against WordPress 7.1.3 on PHP's built-in server with SQLite, Chromium, with the OpenAI and Google provider plugins taken from their GitHub repos.connector-approval.spec.js:23("Can use the Connector Approval Experiment", its.notice-warningnever appears), which fails the same way ondevelophere.max_suggestions11 reportsField "max_suggestions" must be at most 10, and 5 reports "Input is valid according to the schema".h1.Screenshots or screencast
Same site and abilities, WordPress 7.1.3. Before is
develop, after is this branch.max_suggestions11Changelog Entry
🤖 Generated with Claude Code
https://claude.ai/code/session_01DM4Q4KoGRf8wxhcCQy1hfA