feat(media): align visual checks and variant UI with merged design

This commit is contained in:
Florian Sylvain committed 2026-10-13 02:35:20 +02:00
1 parent e6746f2be7
commit 160c0a49ce
23 files changed
+606 -99

No files matched your search

+2 -3
View File
@@ -330,12 +330,11 @@ Feature: Image variants settings screens, list and add/edit slide-over
And its footer is 64 px high with "Cancel" and a primary "Add variant", and the status text is empty
And focus moves to the Name field
Scenario: Add variant opens a full page on mobile
Scenario: Add variant opens a bottom sheet on mobile
Given the viewport is below 640 px
When the user activates "Add variant"
Then a full-screen page opens with a 56 px header holding an arrow-left back button and the title "Add variant", no x button
Then a tall bottom sheet opens as specified in features/variants_visual_conformance.feature, "Add variant is a tall bottom sheet on mobile", superseding the former full-page layout
And the form is followed by the Preview section, stacked, separated by a top border
And the footer is 72 px high with "Cancel" and "Add variant" sharing the width
Scenario: Edit opens with the stored values
When the user chooses "Edit" on the row "hero"
+2 -2
View File
@@ -54,7 +54,7 @@ Mock navigator.clipboard. No test freezes the system date (this screen shows no
recompute; deleting the last row shows Empty; failures 404 (reloads the list) / 500 -> toast.
14. **Reference card** — the 4 rows with badge words and icons, the exact JSON block text, the note;
absent in Loading/Empty/Error/Forbidden; one column below 640.
15. **Slide-over open/close** — desktop slide-over vs mobile full page (back arrow, no x), titles
15. **Slide-over open/close** — desktop slide-over vs mobile bottom sheet (grabber, x; full-page back arrow superseded by features/variants_visual_conformance.feature), titles
"Add variant" / "Edit variant: hero", focus on Name, pristine close without dialog, dirty close
via Cancel/x/back/Escape opens "Discard changes?" with the right text, Keep editing / Discard
changes behaviours, focus return to the opener.
@@ -135,7 +135,7 @@ Preview sample: north-wall.jpg, 4032 × 3024, 3.9 MB.
`minmax(0,1.1fr) 104px 76px 72px 68px 92px 92px 132px 44px`, 36 px header, 48 px rows; cards 12 px
padding with the name+switch line, the spec line, then count+menu; slide-over 960 px with a
440 px form column; compare box 3:2, chips 8 px from the top corners, handle 32 px (44 px on
mobile) centred on a 2 px divider; footer 64 px (72 px on mobile).
mobile) centred on a 2 px divider; footer 64 px (mobile sheet footer: see features/variants_visual_conformance.feature).
- [ ] Not drawn, no visual state: toggle failures, the purge/delete failure toasts, the 409-free
status toasts, the 422 mapping, the clipboard states "Copied" (add only if the board draws it: it
does not, so Vitest only), singular footer.
@@ -0,0 +1,109 @@
Feature: Variants and Media screens match the merged design: visual setups, and the variant form as a bottom sheet on mobile
As the maintainer of RenewCMS
I want the visual check to cover every state of the image variants screens, and the variant form to follow the overlay pattern
So that the SPA matches the merged design on desktop and mobile, in light and dark
Background:
Given the spec of record is docs/design/screens/image-variants.md, docs/design/screens/media.md, docs/design/screens/media-picker.md, docs/design/states.md, docs/design/routes.md and the overlay pattern of docs/design/components.md
And this slice is SPA only: no Go file, route or API contract changes
And the behaviours of features/image_variants_media_screens.feature, features/image_variants_screens.feature and features/image_variants_editor_picker.feature stay as they are except where this file says so
And the merged board is "RenewCMS Screens Back-office.dc.html" and web/visual/check.ts already points to it, with state names in the form "V1a · Loaded"
And every new English string has a French entry in web/src/i18n/messages.ts, the page uses Nuxt UI components and design tokens only, and no comment is added to any file
And the session is the owner "Marta Alves" unless a scenario says otherwise
Rule: Every state of the six groups has a visual setup that passes
Scenario Outline: A group of the merged board is fully covered
Given the group "<group>" of the merged board lists the states "<states>"
When "task visual:check -- image-variants" runs
Then each of those states has a setup in web/visual/states.ts keyed by its exact board name
And each capture prints PASS at desktop and mobile, in light and dark, within 10 %
And no /v1 call is left unmocked and no size mismatch is reported
Examples:
| group | states |
| Variants list | V1a to V1l |
| Add edit variant | V2a to V2k |
| Media library | V3a to V3f |
| Image detail | V4a to V4h |
| Cache panel | V5a to V5j |
| Editor picker | V6a to V6e |
Scenario: Setups reuse the board's own sample data
Then each setup mocks /v1 with the titles, names, sizes, counts and variant rows drawn in the board frame
Scenario: Clock-sensitive states are skipped, not tuned
Given a state whose rendering depends on the current date or time
Then its setup carries skip: 'clock-sensitive', the check prints SKIP and no Vitest spec freezes the system date
Scenario: No visual exception is added without the user's approval
Given a capture cannot get within 10 %
Then the setup or the SPA is fixed
And no "exception" entry is added unless the user approved it explicitly (approvedBy)
Scenario: The gate is hard
When any capture exceeds 10 %, a state has no setup, or a /v1 call is unmocked
Then "task visual:check -- image-variants" exits non-zero
Rule: The Media library V3 states render the existing variants filter and total-size sort
The filter, the sort and their request parameters already exist. Media code changes only if a V3 capture fails the check.
Scenario Outline: The V3 states render the filter and the sort
Given the 12-image fixture of features/image_variants_media_screens.feature
When the setup of "<state>" is loaded on /media
Then the SPA shows "<visible>"
And GET /v1/media/ carries "<request>"
Examples:
| state | visible | request |
| V3a · Grid with badges | "N variants" badges on cards with cached files, none on cards without, "All" selected | no variants parameter |
| V3b · Sort menu | sort menu open, "Largest total size (original + variants)" last | n/a |
| V3c · Filter: With variants, sorted by total size | "With variants" selected, meta lines "total · original + variants" | variants=with&sort=total_size |
| V3d · Filter: Without variants | "Without" selected, no variants badge | variants=without |
| V3e · Filter: nothing to show | "With variants" selected, empty cache, "No images with variants" | variants=with |
Scenario: Existing filter and sort behaviour is unchanged
Then the URL, request and meta-line behaviour of "The variants filter" and "Sorting by total size" in features/image_variants_media_screens.feature still holds
Rule: The variant form follows the overlay pattern
Scenario: Add variant is a right slide-over on desktop
Given the viewport is 640 px wide or more
When the admin activates "Add variant"
Then the form opens as a slide-over 960 px wide at the right over the scrim, full height, with the list behind it
And the header has the title and a close button (x icon, accessible name "Close")
And the form is on the left and the live preview on the right
Scenario: Add variant is a tall bottom sheet on mobile
Given the viewport is below 640 px
When the admin activates "Add variant"
Then the form opens as a bottom sheet flush to the bottom, full width, 92 % of the viewport height
And a grabber (36 × 4, --ui-border-accented) is drawn at the top, with a close x button (44 px) in the header
And it is not a full page: the list stays visible above the sheet behind the scrim
And the Preview section is stacked under the form and the body scrolls
And the footer is sticky with "Cancel" and the primary action sharing the width, 44 px high
Scenario: Edit uses the same container
When the admin chooses "Edit" on the row "hero"
Then the form opens in the same container as Add variant at the current viewport and the title is "Edit variant: hero"
Scenario Outline: Dismissing the mobile sheet
Given the viewport is below 640 px
When the user <gesture>
Then <result>
Examples:
| gesture | result |
| closes a pristine form with x or Cancel | the sheet closes with no dialog and focus returns to the opener |
| swipes down or taps the scrim, pristine | the sheet closes |
| closes a dirty form (x, Cancel, Escape, swipe, scrim) | the "Discard changes?" dialog opens and the sheet stays |
| any dismissal while Saving | nothing closes |
Scenario: Mobile no longer shows a back arrow
Given the viewport is below 640 px
Then no arrow-left back button is in the form header
Scenario: Focus
Then focus is trapped in the sheet or slide-over and returns to the opener on close, and on open it moves to the Name field in create mode
@@ -0,0 +1,35 @@
# QA: Variants and Media visual conformance, variant form overlay
## Implementation note for coder (informational, not a QA assertion)
SPA only, no Go change. web/visual/check.ts already points to the merged Back-office board and uses "V1a · Loaded" names, so only web/visual/states.ts keys and setups and VariantFormSlideover.vue change. `GET /v1/media/` already accepts `variants=with|without` and `sort=total_size` (api/handlers/media/mediaHandler.go parseListQuery), and the filter and sort already exist in MediaToolbar.vue, MediaVariantsFilter.vue and MediaView.vue: reuse, do not rebuild; touch Media code only if a V3 capture fails. The variant form currently uses USlideover side="right" at every width with a mobile back arrow; the mobile case becomes a bottom sheet at 92 % height with a grabber, close x and sticky footer. The Media detail panel is out of scope. The French catalogue gets entries for any new string. Supersedes the "Add variant opens a full page on mobile" scenario of features/image_variants_screens.feature. Never add a visual `exception` without asking the user.
## Automated (Vitest + Vue Test Utils, `task test:web`)
Reuse `web/src/views/__tests__/variantsTestKit.ts` and `mediaTestKit.ts`. Mock useApi's `request` per case. Mock `matchMedia` for the 640 px breakpoint. No test freezes the system date.
1. **Mobile variant form is a bottom sheet** (below 640 px): bottom sheet, not full page; grabber, close x with a 44 px target, 92 % height class, sticky footer; no arrow-left back button; the list is still in the DOM behind the scrim.
2. **Desktop variant form is a right slide-over** (640 px and up): side right, 960 px max width class, close x with accessible name "Close", form and preview side by side.
3. **Edit uses the same container**: mount in edit mode at both widths and check the container and the title "Edit variant: hero".
4. **Dismissal**: pristine, Cancel, x, Escape and the scrim close with no dialog. Dirty, each opens "Discard changes?" and the sheet stays. Saving, nothing closes. Focus returns to the opener.
5. **Media library regression**: the existing MediaVariantsLibrary spec still passes unchanged.
6. **Visual setup guard** (optional): every state name of the six groups in check.ts has an entry in web/visual/states.ts.
## Visual (`task visual:check -- image-variants [state]`, desktop 1440×900 and mobile 390×844, light and dark)
Reference: the merged board and exact values in `docs/design/source/AppFrame.dc.html`, `VariantsFrame.dc.html` and `MediaFrame.dc.html`. The gate is hard: every non-skipped capture is within 10 % and the command exits 0. Setups use the board's sample data. No state is currently known to be clock-sensitive; if coder finds one, mark it `skip: 'clock-sensitive'` and list it in the report.
- [ ] Variants list: V1a Loaded, V1b Loading, V1c Empty, V1d Error loading, V1e Forbidden, V1f Row menu, V1g Row action in progress, V1h Purge confirm, V1i Delete confirm, V1j Delete in progress, V1k Request reference, V1l Entry: sidebar
- [ ] Add edit variant: V2a Create: pristine, V2b Create: dirty, V2c Field errors, V2d No dimension, V2e Name already taken, V2f Saving, V2g Save error, V2h Edit: pristine, V2i Edit: dirty, V2j Unsaved changes, V2k PNG: no quality (mobile captures show the bottom sheet)
- [ ] Media library: V3a Grid with badges, V3b Sort menu, V3c Filter: With variants, sorted by total size, V3d Filter: Without variants, V3e Filter: nothing to show, V3f Delete confirm
- [ ] Image detail: V4a Partly generated, V4b Nothing generated yet, V4c Generating, V4d Generation failed, V4e No variants defined, V4f Source is an SVG, V4h Preview dialog
- [ ] Cache panel: V5a Loaded, V5b Loading, V5c Empty cache, V5d Purging, V5e Purge done, V5f Purge failed, V5g Error loading, V5h Purge all: confirm, V5i Purge all: confirm, typed, V5j Purge unused: confirm
- [ ] Editor picker: V6a Variant select, V6b Select open, V6c No variants defined, V6d Variant deleted, V6e Variant disabled
- [ ] Final: the whole run ends with every capture at PASS or SKIP and exit code 0
## Manual checks (screenshots cannot cover them)
- On a 390 px viewport, the sheet opens from the bottom, swipes down to close, shows the grabber, and the sticky footer stays above the on-screen keyboard.
- Keyboard: Tab order in the sheet and the slide-over, Escape behaviour, focus return to "Add variant" or the row's Edit button.
- Open and close motion, and the preview divider keyboard handling inside the sheet.
- Hover and focus-visible on the segmented filter and the sort menu in the Media library.