# Image optimization inside the media modal

The modal adapter changes WordPress's existing content region to its `Iframe`
view. It reuses the original single-image and pending-recovery PHP renderers and
POST handlers. An explicit `ksf_media_modal=1` flag survives form actions and
success redirects; normal pages and batch processing keep their existing flow.

The embedded document has its own stylesheet, loaded after core admin styles:
20px padding on every side replaces toolbar clearance and asymmetric `.wrap`
margins. It is not loaded on ordinary admin pages. The modal stylesheet also
applies core's full-size iframe layout to the grid's `edit-attachment-frame`,
which does not have the `media-frame` class used by core's iframe selectors.

`Image_Optimization_Modal_Interface` defines the service contract. `Plugin`
receives it through constructor injection and its container factory, then calls
`register()` from `admin_init()`. Service construction does not register hooks;
the plugin entry point only connects WordPress hooks to the plugin dispatcher.

Before leaving Attachment Details, the adapter saves focused text edits and
waits for outstanding WordPress attachment saves. Return reads the **same**
attachment model held by the selection, then reconstructs the original content
region. This refreshes full-size and derivative URLs, MIME, bytes and dimensions
without replacing the editor's selection, insertion callbacks or display settings.
Closing also refreshes the library model; reopening an editor frame performs a
second read in case a previously accepted PHP operation completed after closing.

## Automated coverage

`npm run test:modal` executes the shipped bundle in jsdom using the actual
WordPress media controllers, models, views and Backbone disposal lifecycle.
Set `KSF_TESTS_WP_ROOT` if WordPress is not the plugin's containing installation.
The committed HTML fixture contains only the core templates needed for these
tests. HTTP responses and the unused audio/upload services are substituted.

The suite covers the Gutenberg Select frame, Classic Post frame and Media Library
EditAttachments frame, selected-model identity, derivative URLs, text preservation,
outstanding saves, duplicate clicks/submits, failed saves and refresh retries,
repeated iframe navigation, helper-error presentation, canceled restoration,
close/reopen, Escape and modifier-click fallback.

`ImageOptimizationModalTest` covers real PHP rendering and optimize/restore,
embedding across redirects, defaults, submitted settings, helper failure and
rollback, WordPress-edited images, recovery forms, dependency ordering, malformed
flags and retained nonce/capability/attachment permissions. It uses the disposable
database configured by `tests/wp-tests-config.php`.

`ImageOptimizationModalRegistrationTest` covers interface autoloading, container
caching, registration through `Plugin::admin_init()` and media asset availability
after that registration. It also retains the existing attachment-link filter and
the ordering of embedded rendering after the original POST handlers.

```bash
npm run build
npm run test:modal
npm test

# Full PHP + installed-builder + JavaScript regression pass, on disposable MySQL 8:
ddev exec -d /var/www/html/wp-content/plugins/kind-site-framework bash tests/run.sh
```

Do not run PHPUnit processes concurrently against the same test database. To run
the PHP suite on the separate MariaDB service, use the database configuration
described in `readme.md`, overriding `KSF_TESTS_DB_HOST` appropriately.

## Opt-in Chromium check on the local site

```bash
node tests/Live/modal-browser.cjs
```

Requires the existing DDEV site, development dependencies, Chromium/Playwright,
the plugin's built assets and access to both editor types. `KSF_CHROMIUM` can
override the browser executable. The script creates its own temporary user,
draft page and three attachments, then removes them in `finally`. It does not
change editor settings, image-tool settings, or existing content.

The browser checks real iframe GET/POST requests, missing admin navigation,
defaults, return with a WebP selection, recently edited attachment fields and
actual insertion through the Gutenberg image block and Classic Add Media button.
Unsaved editor content is checked before insertion as well.
The configured image tool is used for the ordinary form submission. If unavailable,
the script verifies the real error page, then converts only its own attachment
through a separate CLI process with a substituted helper response. This fallback
checks browser refresh after confirmed publication; it does not claim a successful
browser POST through a real helper. Each result records `helperSucceeded` so these
two paths remain distinguishable. PHPUnit independently covers the successful
POST/redirect lifecycle with real image publication and substituted helper HTTP.

Authentication cookies remain in memory. Failed browser runs save a screenshot to
`/tmp/ksf-modal-browser-failure.png`. If the process is forcibly terminated, run
`ddev exec wp eval-file /var/www/html/wp-content/plugins/kind-site-framework/tests/Live/modal.php cleanup`
before another attempt. Fixture state is kept only in the container's `/tmp`.

## Execution record

2026-10-24, WordPress 7.0.1 in the existing DDEV installation:

- MySQL 8.0.35: 382 main PHP tests, 6,906 assertions; passed.
- MariaDB 10.11: 382 main PHP tests, 6,906 assertions; passed.
- Installed Elementor/GreenShift integration suite: 2 tests, 52 assertions; passed.
- JavaScript in DDEV: 18 existing batch tests, 16 modal tests and 17 Jest editor
  tests; all passed. The modal test entry runs directly with Node's test API so
  that both the host and DDEV Node versions support the same command.
- Chromium: Gutenberg image-block insertion, real Classic Add Media insertion and
  Media Library grid return passed. Recently typed alt/caption/description and
  unsaved editor content were preserved. No JavaScript page errors were observed.
- The configured helper was unavailable in all three browser cases. Real form
  submissions displayed their errors; WebP refresh/insertion used the documented
  CLI-only helper substitution. Success POSTs, redirects and rollback were covered
  independently by PHP tests.
- Build, modal TypeScript lint and modal SCSS lint passed. The build retains the
  existing Sass `@import` deprecation warnings in unrelated stylesheets.

The existing Reviewed Jest test for multiple names separated by commas was also
updated to assert the current shared-prefix/card markup introduced before this
change. Its production component was not modified.

2026-10-26, service-contract and admin-registration correction:

- MySQL 8.0.35: 384 main PHP tests, 6,919 assertions; passed, including the two
  new container/admin-dispatcher registration tests.
- Installed Elementor/GreenShift suite: 2 tests, 52 assertions; passed.
- JavaScript: 18 batch, 16 modal and 17 Jest editor tests; all passed.
- Chromium: Gutenberg, Classic Editor and Media Library grid scenarios passed
  after moving registration to `Plugin::admin_init()`. The helper remained
  unavailable; the documented CLI-only conversion fallback was used again.
