Skip to content

Prototype : Implement slug generation feature with UI and tests - #897

Open
milindmore22 wants to merge 11 commits into
WordPress:developfrom
milindmore22:feat/suggest-permalink
Open

Prototype : Implement slug generation feature with UI and tests#897
milindmore22 wants to merge 11 commits into
WordPress:developfrom
milindmore22:feat/suggest-permalink

Conversation

@milindmore22

@milindmore22 milindmore22 commented Jul 24, 2026

Copy link
Copy Markdown

What?

Closes #876

This PR introduces the Slug Generation experimental feature (ai/slug-generation ability & experiment). It provides AI-assisted permalink suggestions directly inside the WordPress Block Editor based on post title, content, and additional context.

Open Questions

Pre-publish panel, sidebar-next-to-slug, or both? See above, this affects where the ability gets wired into the editor UI. [I think starting with just the sidebar-next-to-slug would suffice]

Both entry points (sidebar popover + pre-publish panel) are currently supported in prototype.

Should slug suggestions consider existing permalinks on the site to avoid near-duplicate slugs, or is that out of scope for v1?

WordPress core natively enforces slug uniqueness on post save, not sure if we should cover this in Slug generation

Does this warrant a WP-CLI command for bulk slug review/regeneration on existing content, similar to the alt-text bulk command?

Not covered in prototype, but we can cover if required.

Why?

Creating concise, keyword-rich, and SEO-friendly permalink slugs often requires extra effort from content creators. This feature streamlines the publishing workflow by automatically suggesting URL-safe slugs within the permalink inspector popover and the pre-publish panel.

How?

  • Abilities API Integration:

    • Implemented WordPress\AI\Abilities\Slug_Generation\Slug_Generation extending Abstract_Ability.
    • Added system instructions (system-instruction.php) enforcing URL-safe, lowercase, hyphenated slug generation.
    • Sanitizes and validates outputs using sanitize_title().
  • Experiment Framework:

    • Added WordPress\AI\Experiments\Slug_Generation\Slug_Generation under Experiment_Category::EDITOR.
    • Localized configuration settings (minimum content length requirement, number of suggestions).
  • Block Editor UI Components:

    • Permalink Inspector Button: Injects a "Generate Slug" button into the Block Editor permalink popover (.editor-post-url).
    • Interactive Modal (SlugGenerationModal): Renders a modal interface allowing users to preview, manually edit, select, or regenerate slug options.
    • Pre-Publish Panel (SlugPrePublishPanel): Adds a PluginPrePublishPanel sidebar card to review or change the post slug prior to publishing.
  • Automated Tests:

    • Added PHP integration test coverage (Slug_GenerationTest.php) for ability execution, argument validation, and permissions.
    • Updated E2E helper routines (helpers.ts) for reliable REST API save synchronization during bulk toggle tests.

Use of AI Tools

AI assistance: Yes
Tool(s): Antigravity AI
Model(s): Gemini 3.6 Flash
Used for: Initial implementation of ability and experiment classes, React UI components, unit/integration tests, and code documentation. Final changes reviewed and verified by human developer.

Testing Instructions

  1. Ensure global AI features and an AI Connector with text generation capabilities are active.
  2. Navigate to Settings > AI and enable the Slug Generation experiment under Editor Experiments.
  3. Create a new post in the Block Editor and add a post title and content (at least 250 characters).
  4. Click on the Permalink / URL field in the document sidebar to open the permalink popover.
  5. Click Generate Slug. Verify the modal pops up with 3 generated slug suggestions.
  6. Select a suggested slug (or edit it in the input field) and click Insert. Verify the post permalink updates.
  7. Click Publish to open the pre-publish panel. Verify the Suggested Slugs pre-publish panel appears and allows applying suggestions.

Screenshots or screencast

Screen.Recording.2026-07-24.at.4.59.49.PM.2.mov

Changelog Entry

Feature : Slug generation feature to suggest SEO-friendly permalinks in the Block Editor.

Open WordPress Playground Preview

@milindmore22
milindmore22 requested a review from a team July 24, 2026 11:46
@github-actions

Copy link
Copy Markdown

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 props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: milindmore22 <milindmore22@git.wordpress.org>
Co-authored-by: yogeshbhutkar <yogeshbhutkar@git.wordpress.org>
Co-authored-by: jeffpaul <jeffpaul@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@codecov

codecov Bot commented Jul 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 84.94624% with 28 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.83%. Comparing base (abcd7c9) to head (0530904).
⚠️ Report is 8 commits behind head on develop.

Files with missing lines Patch % Lines
...udes/Abilities/Slug_Generation/Slug_Generation.php 81.11% 27 Missing ⚠️
...s/Abilities/Slug_Generation/system-instruction.php 85.71% 1 Missing ⚠️
Additional details and impacted files
@@              Coverage Diff              @@
##             develop     #897      +/-   ##
=============================================
+ Coverage      79.68%   79.83%   +0.15%     
- Complexity      2460     2512      +52     
=============================================
  Files            104      107       +3     
  Lines           9955    10178     +223     
=============================================
+ Hits            7933     8126     +193     
- Misses          2022     2052      +30     
Flag Coverage Δ
unit 79.83% <84.94%> (+0.15%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@dkotter dkotter left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overall this looks good, tested well. I've left a handful of comments but let me know if there's questions on any of those. In addition, we'll want to ensure we add proper documentation that matches all of our other experiment docs.

Comment on lines +31 to +33
protected function guideline_categories(): array {
return array( 'site', 'copy' );
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Open to thoughts here but not sure if generating a slug benefits from these guidelines. May be fine to just not pass these in

$prompt_input .= "\n\n<additional-context>{$context}</additional-context>";
}

$number_of_suggestions = (int) apply_filters( 'wpai_slug_generation_number_of_suggestions', (int) $args['number_of_suggestions'] );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We should add a filter docblock here

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Also, we should probably have some validation here after the filter runs. What if someone uses the filter and doesn't return a number, or returns something like 1000 or 0? Ideally we set a minimum and maximum (1 to 10?) and ensure this is always an integer between those two

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If we do the above, we can also update the schema with minimum and maximum values

);
}

// Parse the output lines into clean, sanitized WordPress slugs.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Any reason to approach it this way instead of using structured outputs? Typically that will work better than just hoping the LLM returns things in the structure we want and avoids us having to do this type of parsing

- Output exactly %d suggestions, one per line.
- Do not include any markdown, bullets, numbering, or formatting.
- Output only the raw slug text. Respond directly without preamble. Do not wrap the output in quotes. Do not add closing remarks or follow-up questions.',
$wpai_slug_num

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would we want a line in here asking it to match the language of the title given? We do this for other abilities to ensure the content returned matches the language of the content passed in

return;
}

// Enqueue frontend scripts, styles, and pass localized configuration settings to window.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This claims "frontend" but this only loads in the admin

aria-labelledby={ labelId }
>
{ suggestions.map( ( slug, index ) => (
<Button

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Should these be buttons? Seems like RadioControl or ToggleGroupControl will be better for accessibility


<TextControl
label={ __( 'Selected slug', 'ai' ) }
value={ selectedSlug }

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

There's a bug here on regeneration. On initial generation, seems this input is set to the first slug suggestion in the modal. If I regenerate in the modal, no slug is selected and this input shows the previous first slug

* Helper to fetch localized settings passed from PHP to the global window object.
*/
const getSettings = (): SlugGenerationData => {
const settings = ( window as any ).aiSlugGenerationData ?? {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We've been trying to move away from ever use as any. I think we can move this to the types file to avoid doing this and update this across all three files

findAndAttach();

// Create observer to listen for sidebar renders/toggles and popover display.
observer = new MutationObserver( () => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So I understand why we're doing this but I think this could cause some performance issues, as this is constantly running. Could we maybe add debouncing or narrowing the observed subtree to just the sidebar or gating on a store selector for the URL popover being open? If there's a way to narrow the scope here so this isn't constantly firing for any and all changes, that would be great

'supports' => array( 'editor' ),
)
);
set_current_screen( 'no_title_cpt' );

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We need to ensure we clean up the screen in tearDown to avoid this leaking into other tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New Experiment: Suggest permalink slugs

2 participants