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-posttomedia_library.uiwith the Media Library state query (the same URL core's AJAX posts to),onlyMainContent(),hx-selectandhx-target#media-library-add-form-wrapper,hx-swapouterHTML,hx-disabled-eltthis. - 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_builddrops 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: aiMediaAnnouncewith the message (assertive for errors), read byDrupal.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_buttonFALSE): 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_requirementis 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_idpresent 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-orderon core's "Authored on" datetimeh4, 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