Skip to content

Slug Generation: fix silent no-op and stale applied state for edited slugs - #932

Merged
dkotter merged 3 commits into
WordPress:developfrom
ekamran:sftq/fix-slug-apply-normalization
Aug 12, 2026
Merged

Slug Generation: fix silent no-op and stale applied state for edited slugs#932
dkotter merged 3 commits into
WordPress:developfrom
ekamran:sftq/fix-slug-apply-normalization

Conversation

@ekamran

@ekamran ekamran commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Description

The Slug Generation experiment lets users hand-edit a suggested slug before applying it, in both the permalink modal and the pre-publish panel. Edits are normalized with cleanForSlug() on apply, and two problems came out of that:

  1. Input that normalizes to an empty slug (for example !!!) was silently dropped. The modal still closed as if the slug had been applied, with no feedback anywhere, and the panel button did nothing on click.
  2. The pre-publish panel compared the raw input against the stored slug. After applying an edited value like My Cool Slug the post correctly received my-cool-slug, but the button stayed on an enabled Apply forever and the Applied state was unreachable.

Before

Modal, input that cannot become a slug, Apply closes the modal with nothing applied and no feedback:

before-modal-invalid

Panel, edited slug is applied but the button never reflects it:

before-panel-custom

After

Modal stays open with the Apply button disabled and an explanation:

after-modal-invalid

Panel previews the normalized form and flips to a disabled Applied:

after-panel-custom

How it is fixed

Everything rides on existing core pieces. applySlug() now returns the slug it applied, or an empty string when nothing was applied. A small shared helper describes how the current input will be applied, and both surfaces use it the same way:

  • A help text under the field (the help prop of TextControl) previews the normalized form when it differs from the input, for example Will be applied as "my-cool-slug".
  • When the input cannot become a slug, the help explains why and Apply is disabled.
  • The Applied state in the panel is derived from the normalized form, so it is reachable for edited input, and the modal only closes when something was actually applied.

Percent-encoded slugs (how non-Latin suggestions are stored after sanitize_title()) are excluded from the preview, since cleanForSlug() does not account for octets and the preview would show a garbled form for a suggestion the user never edited. The underlying apply behaviour for those slugs is pre-existing and is now noted in the doc's Limitations section.

Testing

  • Three new e2e tests: blocked input keeps the modal open and the slug untouched even on a forced click, an edited slug shows the preview and applies the normalized form, and the panel blocks unusable input and flips to a disabled Applied. Full slug spec passes (8 tests).
  • Verified by mutation: reverting either fix makes the matching test fail.
  • Manually verified both surfaces in wp-env, including edge cases: empty input, spaces only, emoji only, script tags, percent-encoded input, literal percent text, Hindi text, re-editing after apply, selecting a radio after a custom edit, and regenerating with custom text typed.
  • npm run lint:js and the TypeScript check pass. No PHP changes.

The docs page is updated to match, including the Manual Testing steps and a Limitations entry.

Use of AI Tools

This PR was developed with the assistance of AI tools for local testing, code review and drafting, with all changes reviewed and verified by me.

Changelog Entry

Fixed - Slug Generation: applying a hand-edited slug now previews the normalized form, blocks input that cannot become a slug, and correctly shows the Applied state.

Open WordPress Playground Preview
…slugs

Hand-edited slugs are normalized with cleanForSlug() on apply, which
caused two problems in both the modal and the pre-publish panel:

1. Input that normalizes to an empty slug (for example "!!!") was
   silently dropped. The modal still closed as if the slug had been
   applied, with no feedback anywhere.
2. The pre-publish panel compared the raw input against the stored
   slug, so after applying an edited value like "My Cool Slug" the
   button stayed on an enabled "Apply" forever and the "Applied" state
   was unreachable.

Fix both at the source. applySlug() now returns the slug it applied,
or an empty string when nothing was applied. A shared helper describes
how the input will be applied, so both surfaces show the same help text
under the field: a preview of the normalized form when it differs, and
an explanation when the input cannot become a slug, with Apply disabled
in that case. The applied state is now derived from the normalized
form, and the modal only closes when something was actually applied.

Percent-encoded slugs (how non-Latin suggestions are stored after
sanitize_title()) are excluded from the preview, since cleanForSlug()
does not account for octets and the preview would advertise a garbled
form for a suggestion the user never edited. This is documented as a
limitation along with the underlying behaviour.
Add three tests to the slug generation spec:

1. Input that cannot become a slug blocks Apply with an explanation,
   and even a forced click leaves the modal open and the post slug
   untouched.
2. An edited slug shows the normalized preview and applies the
   normalized form from the modal.
3. The pre-publish panel blocks unusable input the same way, flips
   Apply to a disabled Applied after applying an edited slug, and the
   normalized form is what lands on the post.

Locators for the panel are scoped to the panel container so other
sidebar buttons cannot make them ambiguous.
@ekamran
ekamran requested a review from a team August 12, 2026 12:32
@ekamran
ekamran requested a review from jeffpaul as a code owner August 12, 2026 12:32
@github-actions

github-actions Bot commented Aug 12, 2026

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: ekamran <ekamran@git.wordpress.org>
Co-authored-by: dkotter <dkotter@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 Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.95%. Comparing base (4d52e02) to head (4bbdf70).

Additional details and impacted files
@@            Coverage Diff             @@
##             develop     #932   +/-   ##
==========================================
  Coverage      75.95%   75.95%           
  Complexity      2989     2989           
==========================================
  Files            124      124           
  Lines          11850    11850           
==========================================
  Hits            9001     9001           
  Misses          2849     2849           
Flag Coverage Δ
unit 75.95% <ø> (ø)

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 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Note that E2E tests aren't running right know due to WordPress 7.0.4 not being properly tagged on GitHub:

fatal: couldn't find remote ref 7.0.4

I've run those locally and they are all passing so going to merge this in

@dkotter dkotter added this to the 1.3.0 milestone Aug 12, 2026
@dkotter
dkotter merged commit d3a3d5e into WordPress:develop Aug 12, 2026
55 of 58 checks passed
@dkotter dkotter mentioned this pull request Aug 12, 2026
48 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

2 participants