Skip to content

feat(editor): split voice Language dropdown into Language + Accent selects#567

Open
galbus wants to merge 5 commits into
mainfrom
claude/quizzical-bose-b528b8
Open

feat(editor): split voice Language dropdown into Language + Accent selects#567
galbus wants to merge 5 commits into
mainfrom
claude/quizzical-bose-b528b8

Conversation

@galbus

@galbus galbus commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Summary

First slice of the dashboard-parity voice filters (Language / Accent / Native / Type / Gender): split the combined "English (American)" Language dropdown into a Language (name) select + an Accent select, in both editors.

  • Block editor (voice-section.js + helpers.js): Language lists each distinct language name; a conditional Accent select lists the accents for the chosen name and carries the language code. Both derive from the stored beyondwords_language_code — no new persisted state. Picking a name auto-selects its first accent, which stores the resolved code and seeds that language's default body voice (existing behaviour).
  • Classic editor (class-select-voice.php + classic-metabox.js + class-assets.php): mirrored. The new name select is client-side only (no name attribute); the Accent select keeps the beyondwords_language_code id/name, so save() and the submitted form are unchanged. Slim language rows are localized so the Accent rebuild needs no extra API round-trip. The project-default flow now selects the name + accent for the default code.
  • The Accent select is hidden when a language offers a single accent (nothing to choose) while still submitting that accent's code; with no language chosen it renders a single empty option so the field always posts ('' = no language).

Persistence is unchanged: only beyondwords_language_code (resolved code) + beyondwords_body_voice_id are stored. Model/Voice behaviour is untouched.

Why

Review feedback: the voice list is too long — we're replicating the BeyondWords dashboard's voice filters ([Language] [Accent] [Native] [Type] [Gender]). Language + Accent is the first vertical slice; Native/Type/Gender follow on separate branches.

Test plan

  • PHPUnit: full suite passes — 645 tests / 2082 assertions — incl. rewritten element() assertions (79-name select, 14 English accents, en_US/en_GB option positions) and new unit tests for language_names() / accents_for_name() / find_language_by_code() / languages_for_script().
  • Cypress classic-editor/select-voice.cy.js: 12/12 passing, incl. two new tests — name change auto-selects the first accent + hides Accent for single-accent languages (Welsh); accent change re-fetches voices and seeds the default voice (en_GB → Ollie/Legacy).
  • Cypress block-editor/select-voice.cy.js: 7/7 passing (full publish flow across Post/Page/CPT with the split selects), incl. a new hide-Accent-for-single-accent-language test.
  • phpcs clean on changed PHP; eslint + prettier clean on changed JS; npm run build compiles.

🤖 Generated with Claude Code

…lects

The combined "English (American)" Language dropdown becomes a Language
(name) select plus an Accent select in both editors — the first slice of
replicating the BeyondWords dashboard voice filters (Language / Accent /
Native / Type / Gender).

- Block editor: Language lists each distinct language name; a
  conditional Accent select lists the accents for the chosen name and
  carries the language code. Both derive from the stored
  beyondwords_language_code — no new persisted state. Picking a name
  auto-selects its first accent, which stores the resolved code and
  seeds that language's default body voice.
- Classic editor: mirrored. The name select is client-side only; the
  Accent select keeps the beyondwords_language_code id/name so save()
  and the submitted form are unchanged. Slim language rows are localized
  for the Accent rebuild; the project-default flow selects name + accent
  for the default code.
- The Accent select hides when a language offers a single accent while
  still submitting that accent's code; with no language chosen a single
  empty option keeps the field posting ('' = no language).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown

✅ WordPress Plugin Check Report

✅ Status: Passed

📊 Report

All checks passed! No errors or warnings found.


🤖 Generated by WordPress Plugin Check Action • Learn more about Plugin Check

galbus and others added 4 commits July 8, 2026 20:55
…nt split

The settings-panel block spec also exercises the Voice section and still
asserted the combined "English (American)" label. Assert the split
Language (English) + Accent (American) selects instead.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…en selects

When the Language or Accent changed, the block editor kept rendering
the previous language's Model + Voice dropdowns (from the stale store)
with the loading Spinner wedged above them. Now, while the voices
resolve, the Model + Voice fields are hidden and the Spinner takes
their place, so it never appears between two select fields, matching
the classic editor (which already clears Model/Voice and shows its
loader at the bottom).

The fields are hidden via a CSS class rather than unmounted, so the
select keeps its DOM identity across the resolve and does not detach
mid-interaction when the fetch completes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a Native select above the Model dropdown in both editors. It
narrows the Voice list to voices native to the selected language
(their primary language) and defaults to native-only; "All" adds the
multilingual (non-native) voices back. This is the main lever for the
"voice list is too long" feedback.

- Filtering is client-side over the fetched voices (the API returns
  primary + secondary), mirrored across the block editor
  (voice-section.js + helpers.js), the classic editor
  (classic-metabox.js) and the PHP server-render
  (class-select-voice.php).
- The selected voice is always kept, so toggling the filter never
  drops it; on load the filter opens on "All" when the saved voice is
  not native to the language (older posts, multilingual defaults) so it
  stays visible.
- Type and Gender filters are intentionally out of scope for now.

Tests: a non-native voice (Klaus, German-primary) is added to the mock;
block + classic Cypress cover default-native hiding it and "All"
revealing it, plus the saved-non-native seed; PHPUnit covers the native
helpers and the "All" seed. Coverage 91.03%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ntrol

The Spinner now stands in for whatever is still loading below the last
resolved control, and never sits between two select fields:
  - while the languages load, only a Spinner shows;
  - Language + Accent + Native show while the voices resolve, with the
    Spinner in place of the Model + Voice group;
  - once the voices resolve, Model + Voice show and the Spinner clears.

Model and Voice come from the same voices fetch, so they resolve
together (picking a Model filters the loaded list client-side).

Uses hasFinishedResolution (monotonic false->true) instead of
isResolving, which flips on and off and left a one-frame window where
stale Model/Voice options flashed. The Model + Voice group is kept
mounted and hidden with an inline style, so the <select> keeps its DOM
identity (no detach mid-interaction) and the hide can't lose a CSS
specificity battle with the component's own styles. Matches the classic
editor, which already clears Model/Voice and shows its loader in place.

Post meta is unchanged: only the language code and voice id are stored;
Language name, Accent, Model and the Native filter are all derived from
those on load, so nothing else needs persisting.

Tests: block + classic Cypress delay the voices response and assert the
Spinner/loader replaces Model + Voice while they resolve.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant