feat: #3631526 Generate with core HTMX instead of Form API AJAX

Part of #3631526.

Depends on !4 (closed). This branch is built on !4 (closed) (less custom CSS and JS, assets named ai-media.admin.css / ai-media.admin.js), so the diff includes !4 (closed)'s two commits until !4 (closed) merges. Review the last commit.

Generate and Generate again now use core HTMX (\Drupal\Core\Htmx\Htmx, htmx 2) instead of Form API #ajax. Everything core media_library drives (Remove, Save, Save and select, Insert selected, the tabs) stays on core's Drupal AJAX. No core flow is forked or overridden.

HTMX design:

  • Generate: data-hx-post to media_library.ui with the Media Library state query (the same URL core's AJAX posts to), onlyMainContent(), hx-select and hx-target #media-library-add-form-wrapper, hx-swap outerHTML, hx-disabled-elt this.
  • First result: the server sends HX-Retarget: #media-library-wrapper, so the whole library (tabs included) is replaced by the add form, as core does after an upload. Validation errors swap only the add form, so the tabs stay.
  • Form build ID: core marks it for an out-of-band swap when the target is not a form, and htmx then removes it from the swapped markup. An #after_build drops that marker for this trigger only, so the new build ID arrives inside the form.
  • Focus: server-driven with autofocus (htmx focuses it after the swap): the description on an error, the added media list on a result (as core's AJAX does after an upload).
  • Announcements: HX-Trigger: aiMediaAnnounce with the message (assertive for errors), read by Drupal.announce.
  • Loading: core's own throbber markup from Drupal.theme('ajaxProgressThrobber'), so each admin theme paints it; the button is disabled while it runs.
  • Generate is type="button" (#submit_button FALSE): Enter in the name field no longer spends another generation.
  • Behaviors reattach through core's HTMX integration (htmx:drupal:load), including the dialog buttons and the new Save button's Drupal AJAX.
  • core_version_requirement is now ^11.3, where the Htmx class was introduced (change record). Drupal 12 moves to htmx 4; that will need a follow-up.

JS: 45 lines (throbber, three announcements). CSS unchanged at 16 lines.

Tested in Firefox, wide screens only (1440 and 1920), in Claro, Gin and Default Admin, Echo provider:

  • Featured-image style media field: Add media, SVG tab, Generate, Generate again (item replaced in place, description kept), keyboard Enter on Generate again, Remove (core AJAX) then Generate again, Save, Insert selected: pass.
  • Document generator with the read-only preview: pass. Insert Media in CKEditor 5 (Full HTML) with an SVG generated in the dialog: pass.
  • Empty description and a non-SVG answer: error in the form, focus on the description, announced: pass.
  • One request per click on every repeat, no duplicate handlers, form_build_id present after every swap, Enter in the name field sends nothing. No console errors.
  • axe-core 4.10.2 (WCAG 2.2 A/AA): 0 violations at every step. Best practice reports one heading-order on core's "Authored on" datetime h4, from core markup.
  • phpcs (Drupal, DrupalPractice), stylelint, cspell clean; eslint 0 errors.

Remaining tasks

  • Image generation NOT TESTED: the Echo provider has no text-to-image.
  • Mobile widths not tested (out of scope for this pass).

AI-Generated: Yes. Written with Claude Code (Claude Opus 5.5) under the Drupal AI policy; tested and reviewed by the author before submission.

Checkpoints:

  • File an issue
  • Addition/Change/Update/Fix
  • Testing to ensure no regression
  • Automated unit testing coverage
  • Automated functional testing coverage
  • UX/UI designer responsibilities
  • Readability
  • Accessibility
  • Performance
  • Security
  • Developer Documentation
  • User Guide Documentation
  • Reviewed by human
  • Code review by maintainers
  • Full testing and approval
  • Credit contributors
  • Review with the product owner
  • Release notes snippet
  • Release

Merge request reports

Loading
Loading