diff --git a/docs/e2e/feature-validation.tsv b/docs/e2e/feature-validation.tsv index eca98484c..98e59b07d 100644 --- a/docs/e2e/feature-validation.tsv +++ b/docs/e2e/feature-validation.tsv @@ -83,7 +83,7 @@ MEDIA-004 Media metadata editing As a media manager, I want to rename and tag me MEDIA-005 Media replace, delete, restore, and purge As a media manager, I want to replace or remove assets safely so media library stays accurate. POST /media/:id/replace overwrites bytes and updates the visible asset filename to the uploaded replacement name, DELETE soft-deletes active assets or purges trashed assets, POST /restore restores soft-deleted assets, and viewer/grid/trash actions reflect capabilities. Replacing with wrong type; deleting asset used on published page; purge force; restore after purge; storage delete failure; upload queue overlay after upload; mobile context menu overlap with expanded sidebars. replace requires media.replace; delete/purge requires media.delete; upload validation applies to replacement; route params decoded; Trash folder distinguishes restore vs purge actions. server/handlers/cms/media.ts; src/admin/pages/media/components/ReplaceFileDialog; src/admin/pages/media/components/MediaViewerWindow; src/admin/pages/media/components/MediaGrid; src/ui/components/ContextMenu Public pages may retain published references until republish/asset unavailable handling; replacement keeps one asset record but adopts replacement filename. Happy: upload image, replace it, verify viewer reflects replacement, soft-delete from All files, restore from Trash, delete again, purge from Trash. Error: unsupported replacement. Boundary: restore soft-deleted asset. Invalid: missing file. Permission: replace/delete caps separate. Performance: replacement progress. Mobile: at 390px, Replace file dialog, media context-menu Delete, Trash navigation, and Restore remain usable without horizontal overflow. MEDIA-005 replace/delete/restore/purge Playwright regression passed 2026-06-22; mobile replace/trash-restore and context-menu hit-test regression passed 2026-06-23; unsupported replacement, asset-in-use/public reference behavior, confirmation/safety review, and permission variants remain future expansion 0 None MEDIA-005 promoted in `media.e2e.ts`: the desktop test signs in fresh, uploads disposable PNG, opens viewer, replaces file through Replace file dialog with a second PNG, verifies replacement filename and Replaced detail, closes viewer, deletes active asset from Media item options, verifies it leaves All files, opens Trash, restores it, verifies it leaves Trash and returns to All files, deletes it again, opens Trash, purges it from Media item options, and verifies Trash is empty. The mobile regression switches to 390x844, clears stored layout, uploads a disposable PNG, closes the Uploads floating window, opens the viewer, verifies mobile containment, opens Replace file, verifies the shared dialog is contained and the replacement picker is reachable, replaces the binary, verifies the replaced viewer remains contained, closes it, opens the item options menu by keyboard, deletes the asset, opens Trash, restores it, returns to All files, and verifies no page-level horizontal overflow. DEF-20260623-MEDIA005-01 closed: the point-anchored shared ContextMenu rendered inside the media canvas with viewport coordinates, so at 390px its clamped menu overlapped the expanded Folders sidebar while the sidebar intercepted pointer events; fixed by portaling the shared ContextMenu to document.body and covering that contract in `contextMenu.test.tsx`. Verification: focused `bun test src/__tests__/ui/contextMenu.test.tsx` passed 6/6 after first failing on the portal regression; focused `bun run test:e2e -- --project=e2e tests/e2e/media.e2e.ts -g "keeps replace and trash restore"` passed 2/2 including setup; full `bun run test:e2e -- --project=e2e tests/e2e/media.e2e.ts` passed 10/10 including setup. Run logs: docs/e2e/runs/2026-06-22-media005-replace-delete-restore.md; docs/e2e/runs/2026-06-23-media005-mobile-lifecycle.md. 2026-06-23 MEDIA-006 Media storage adapters and migration As an operator, I want to choose storage backends and understand migration state so media storage can move safely. Media Storage panel loads from GET /media/storage, lists each media asset role with its elected adapter, defaults every role to Local disk (built-in), shows Local sharp ladder (built-in) when no variant delegate is elected, shows empty adapter/delegate states when no plugin storage surfaces are installed, exposes external adapter verification only for external adapters, and shows migration controls only when original/variant backlog exists. Adapter unavailable; migration crash/retry; tab close cancel; partial migration; invalid delegate; no plugin adapters installed; no migration backlog on local-disk install; mobile panel overflow or clipped close controls. Elect body role original/variant/avatar/font/plugin-pack and adapterId; delegate body string/null; migration body role and toAdapterId; storage.elect gates panel/list/elect/delegate/verify; storage.migrate gates migration stream. server/handlers/cms/mediaStorageAdmin.ts; server/handlers/cms/mediaStorageMigration.ts; src/admin/pages/media/components/MediaSidebar; src/admin/pages/media/components/MediaStoragePanel; src/core/plugins/mediaStorageRegistry.ts External adapters and delegates require plugin fixtures; local disk exists by default and clean installs have no migration backlog. Happy: open Storage panel and verify local disk/local sharp defaults plus empty adapter/delegate states. Error: external verify failure. Boundary: no backlog hides migration. Invalid: unknown adapter/delegate. Permission: storage.elect/storage.migrate caps. Performance: storage snapshot loads quickly. Mobile: at 390px, storage panel opens, every built-in role combobox is reachable, delegate and empty adapter states can be scrolled into view, close control works, and no horizontal overflow appears. MEDIA-006 built-in storage panel Playwright regression passed 2026-06-22; mobile storage panel layout regression passed 2026-06-23; external adapter verify/elect, migration backlog/progress, delegate plugin election, and storage.migrate permission variants remain future expansion 0 None MEDIA-006 promoted in `media.e2e.ts`: the desktop test clears persisted media layout, signs in, opens `/admin/media`, opens the Storage panel, waits for `GET /admin/api/cms/media/storage`, verifies Backend per role is visible, every role combobox is `Local disk (built-in)`, Variant delegate is `Local sharp ladder (built-in)`, no delegate plugins and no external storage adapters empty states render, and `Test connection`/`Migrate` actions are absent on a clean local-disk install with no backlog. The mobile regression switches to 390x844, clears stored layout, opens the Storage panel, verifies the panel is viewport-contained without page-level horizontal overflow, verifies every built-in role combobox is visible and set to Local disk, scrolls to the Variant delegate and external-adapter empty states, verifies no `Test connection` or `Migrate` actions are present, closes the panel through the panel close control, and verifies the Storage rail action is reachable again. The first focused run exposed a test-selector issue only: both the rail toggle and panel header intentionally share the accessible name `Close Storage panel`, so the test now uses the existing `panel-close-media-storage` test id for the specific header close button. Verification: focused `bun run test:e2e -- --project=e2e tests/e2e/media.e2e.ts -g "built-in storage panel usable"` passed 2/2 including setup; full `bun run test:e2e -- --project=e2e tests/e2e/media.e2e.ts` passed 11/11 including setup. Run logs: docs/e2e/runs/2026-06-22-media006-storage-panel.md; docs/e2e/runs/2026-06-23-media006-mobile-storage-panel.md. 2026-06-23 MEDIA-007 Image variants and public presentation As a visitor/admin, I want media variants and public URLs to serve correctly so images load efficiently. Image variant worker/protocol produces raster variants where applicable, media presentation maps asset public paths and variant paths for editor/published use, signed/storage URLs are redirected by public media routing, and SVG uploads are sanitized before bytes are stored and served. Variant worker failure; SVG unsafe content; original missing; external signed URL expiry; media batch resolution; raster too small for variants; SVG made empty by sanitizer. Variant protocol validates sizes/ids; SVG sanitizer strips script blocks, foreignObject, event handlers, javascript: URLs, and style blocks; public route serves trusted MIME based on server-chosen extension. server/handlers/cms/imageVariantWorker.ts; server/handlers/cms/mediaVariants.ts; server/publish/mediaPresentation.ts; server/handlers/cms/svgSanitize.ts; server/handlers/cms/mediaUpload.ts; tests/e2e/media.e2e.ts Some raster variants may be generated lazily or skipped for small images; SVGs intentionally do not get raster variant ladders. Happy: upload unsafe SVG, verify asset appears, fetch served /uploads SVG, verify safe geometry remains and script/foreignObject/event/javascript/style vectors are stripped. Error: missing variant fallback. Boundary: SVG asset. Invalid: sanitizer-empty SVG rejected. Permission: public read for published assets, admin caps for manage. Performance: variant generation bounded. Mobile: responsive attrs correct. MEDIA-007 unsafe SVG sanitizer/public media Playwright regression passed 2026-06-22; raster variant ladder/srcset, missing variant fallback, external signed URLs, media batch resolution, and responsive attrs remain future expansion 0 None MEDIA-007 promoted in `media.e2e.ts`: test signs in, uploads an unsafe SVG through the Media workspace, verifies the asset tile appears, reads the tile preview /uploads URL, fetches the served SVG through Playwright's request context, verifies Content-Type is image/svg+xml, verifies safe geometry remains, and verifies