diff --git a/TESTING.md b/TESTING.md index aa432c7..d3a3ad1 100644 --- a/TESTING.md +++ b/TESTING.md @@ -846,10 +846,13 @@ Recorded deviations on this page: because the legacy gallery let people change layout and density and nothing else on the page offers it, and the selected-image side panel, the one shared inspector the Images page uses (spec §3.3), so an image's details stay reachable without opening the viewer. - The All images and Repeat run dialogs keep their collection caption. With no - saved variant the page shows every image of the positive under "All images for this - positive, including unassigned images. …" and no empty copy (a state the concept - never reaches). + The same ruling covers the page's two media dialogs, All images for this positive and + Repeat run: as in the concept (app.js a1282-1304), each is named by its own dialog + title, with no "Linked images" heading or "N loaded · N linked images" count, and a + stranded focus returns to the dialog's close button (maintainer ruling, 2026-10-21). + The Images page keeps its caption. With no saved variant the page shows every image + of the positive under "All images for this positive, including unassigned images. …" + and no empty copy (a state the concept never reaches). The run dot reads `GET /prompt_manager/library/{positive_id}/history/pairs/{pair_id}/newest` (see "Chronological Library history"), and each lane's "N runs" is the pair's @@ -874,7 +877,8 @@ Images collection, so its height is not compared). D25: each compared block moun production class or margin. Behaviors are compared in both documents: a red dot changes the negative block and the gallery, Compare toggles `aria-pressed`, All images opens a dialog with the positive's four images, the run dot opens a dialog (D26: Recorded run -where the concept shows Repeat run), the heading send previews the selected pair, and +where the concept shows Repeat run), neither dialog has a collection caption (D47), the +heading send previews the selected pair, and ← Prompt library navigates. `history comparison detects mutations` injects `.ca-tools .variant-dot { width: 20px }` and removes Compare negatives. The older `history-canonical-comparison.mjs` stays for `workspace-visual.spec.mjs`'s recorded diff --git a/tests/browser/history-concept.spec.mjs b/tests/browser/history-concept.spec.mjs index 604da17..ebd6d4f 100644 --- a/tests/browser/history-concept.spec.mjs +++ b/tests/browser/history-concept.spec.mjs @@ -144,13 +144,15 @@ const negativeText=page=>page.locator('article.surface .prompt-section.negative const galleryOf=async page=>({context:await page.locator('.history-gallery .panel-heading .small').textContent(), cards:await galleryCards(page).locator('.image-open').evaluateAll(nodes=>nodes.map(node=>node.getAttribute('aria-label')))}); -/** A dialog the page opens: its accessible name and how many media cards it shows once loaded; then closed. */ +/** A dialog the page opens: its accessible name, how many media cards it shows once loaded and its captions; then closed. */ async function dialogOf(page,trigger,{name,cards}) { await trigger.click(); const dialog=page.getByRole('dialog',{name,exact:true}); await expect(dialog).toBeVisible(); if(cards)await expect(dialog.locator('.media-card')).toHaveCount(cards); - const result=cards?{name,cards:await dialog.locator('.media-card').count()}:{name}; + // D47: neither document's media dialog carries a collection caption ("Linked images", "N loaded · …"). + const captions=await dialog.locator('.section-caption').count(); + const result=cards?{name,cards:await dialog.locator('.media-card').count(),captions}:{name,captions}; await page.keyboard.press('Escape');await expect(dialog).toHaveCount(0); return result; } diff --git a/tests/browser/workspace-history.spec.mjs b/tests/browser/workspace-history.spec.mjs index 054b977..875ea7e 100644 --- a/tests/browser/workspace-history.spec.mjs +++ b/tests/browser/workspace-history.spec.mjs @@ -832,6 +832,11 @@ test('run dot appears only for pairs with more than one run and opens its run',a await clean.click();const repeat=modalDialog(page,'Repeat run');await expect(repeat).toBeVisible(); await expect(repeat.locator('#modal-body > p.small')).toHaveText('Same positive and negative, another execution.'); await expect(repeat.locator('#modal-body > .spaced-15 article[data-image-key]')).toHaveCount(1); + // D47: as in the concept (a1291-1304), the dialog title names the image; no collection heading or count repeats it. + await expect(repeat.locator('.section-caption')).toHaveCount(0); + await expect(repeat.getByRole('heading',{name:'Linked images',exact:true})).toHaveCount(0); + await expect(repeat.getByText(/ loaded · /)).toHaveCount(0); + expect(await repeat.evaluate(node=>[...node.querySelectorAll('h1, h2')].filter(item=>!item.closest('aside')).map(item=>item.textContent))).toEqual(['Repeat run']); expect(reads.at(-1).get('pair_id')).toBe(String(f.clean_pair));expect(reads.at(-1).get('limit')).toBe('1'); await page.keyboard.press('Escape');await expect(repeat).toHaveCount(0);await expect(clean).toBeFocused(); }); @@ -1008,10 +1013,15 @@ test('shared gallery uses concept cards, image opens the viewer, empty copy',asy await page.keyboard.press('Escape');await expect(viewer).toBeHidden();await expect(openButton).toBeFocused(); await expect(section.locator(':scope > .panel-heading')).toHaveCSS('justify-content','space-between'); await expect(section).toHaveCSS('margin-top','24px'); - // The D47 ruling covers the shared gallery only: the All images dialog's collection keeps its own caption. + // D47 (maintainer ruling 2026-10-21) covers the history page's dialogs too: the All images dialog is named by its + // own title, with no "Linked images" heading or "N loaded · N linked images" count, as the concept's (a1282-1290). await variantsPanel(page).getByRole('button',{name:'All images for this positive',exact:true}).click(); const all=page.getByRole('dialog',{name:'All images for this positive',exact:true}); - await expect(all.locator('.section-caption')).toContainText(/ loaded · /); + await expect(all.locator('article[data-image-key]').first()).toBeVisible(); + await expect(all.locator('.section-caption')).toHaveCount(0); + await expect(all.getByRole('heading',{name:'Linked images',exact:true})).toHaveCount(0); + await expect(all.getByText(/ loaded · /)).toHaveCount(0); + expect(await all.evaluate(node=>[...node.querySelectorAll('h1, h2')].filter(item=>!item.closest('aside')).map(item=>item.textContent))).toEqual(['All images for this positive']); await page.keyboard.press('Escape');await expect(all).toHaveCount(0); // At 390px the heading stays inside the gallery and the page never scrolls sideways. await page.setViewportSize({width:390,height:844}); diff --git a/web/js/workspace/images.mjs b/web/js/workspace/images.mjs index 2e7923d..c840de5 100644 --- a/web/js/workspace/images.mjs +++ b/web/js/workspace/images.mjs @@ -97,8 +97,9 @@ function deliverFavorite(key, favorite) { } /** - * `caption: false` (the history page's shared gallery, D47) leaves out the embedded collection's "Linked images" heading - * and count, whose page heading already names it; `focusTarget` then takes focus where that heading did. + * `caption: false` (the history page's shared gallery and its All images and Repeat run dialogs, D47) leaves out the + * embedded collection's "Linked images" heading and count, whose page heading or dialog title already names it; + * `focusTarget` then takes focus where that heading did. */ export function mountImages(main, api, parentScope, {embedded = false, fetchPage, emptyText, caption: showCaption = true, focusTarget = null} = {}) { const scope = createScope(); @@ -221,7 +222,7 @@ export function mountImages(main, api, parentScope, {embedded = false, fetchPage const showsEmpty = !embedded || typeof emptyText === 'string'; const empty = el('div', embedded ? emptyText : 'No matching images. Try another search or category.', 'empty'); empty.hidden = true; - // D47: the history page's shared gallery is named by its own "Shared gallery" heading, so it has no caption. + // D47: the history page's shared gallery and dialogs are named by their own heading or title, so they have no caption. if (showCaption) collection.append(caption); collection.append(grid); if (showsEmpty) collection.append(empty); diff --git a/web/js/workspace/library.mjs b/web/js/workspace/library.mjs index af17ffb..e8e43a3 100644 --- a/web/js/workspace/library.mjs +++ b/web/js/workspace/library.mjs @@ -765,7 +765,9 @@ export function mountLibrary(main,api,parentScope,{positiveId}={}) { const dialogScope=createScope();imagesDialogScope=dialogScope; const grid=el('div',undefined,className); openContentDialog({scope:content,title,nodes:intro?[el('p',intro,'small'),grid]:[grid],trigger,onClose:()=>{dialogScope.dispose();if(imagesDialogScope===dialogScope) imagesDialogScope=null;}}); - mountImages(grid,api,dialogScope,{embedded:true,fetchPage}); + // D47: the dialog's own title names its images, as in the concept (a1282-1304), so the collection has no + // "Linked images" heading or count; a stranded focus returns to the dialog's close button. + mountImages(grid,api,dialogScope,{embedded:true,caption:false,focusTarget:grid.closest('dialog')?.querySelector('.dialog-header > button') ?? null,fetchPage}); } const imagesPage=(id,query,signal)=>api.get(`/prompt_manager/library/${id}/images?${query}`,{signal}); /** Concept "All images for this positive" (a1282-1290): every image of the positive, Load more bounded. */