feat(history): drop the shared gallery's Linked images caption (D47 ruling)

The concept's "Shared gallery" heading is the gallery's only heading: the
embedded collection's "Linked images" heading and "N loaded · N linked
images" count repeated it. mountImages gains caption: false and a
focusTarget; only the history page's shared gallery passes them, so the
Images page and the All images and Repeat run dialogs keep their caption.
The layout row and the selected-image side panel stay (TESTING.md D47).
This commit is contained in:
Vito Sansevero
2026-10-21 03:12:25 -07:00
parent dc99d6aefe
commit bc7747db7b
5 changed files with 40 additions and 10 deletions
+9 -3
View File
@@ -838,9 +838,15 @@ Recorded deviations on this page:
star wherever the read returns `favorite`, no checkbox or selection bar (Images only,
#259), the image opens the Viewer. A saved variant with no images shows the concept's
`.empty` "Saved variant · no runs yet." beside the grid (the concept puts it inside
`.image-grid`, which only holds positioned cards here). The embedded collection keeps
production's layout row, "Linked images" caption with "N loaded · N linked images"
and the selected-image side panel, which the concept's gallery does not show. With no
`.image-grid`, which only holds positioned cards here). The concept's "Shared gallery"
heading is the gallery's only heading and names its region: the embedded collection's
"Linked images" heading and "N loaded · N linked images" count are removed, since they
repeated it (maintainer ruling, 2026-10-21). Two production pieces stay, which the
concept's gallery does not show: the layout row (Grid/List/Masonry and Thumbnail size),
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).
+16
View File
@@ -969,6 +969,17 @@ test('shared gallery uses concept cards, image opens the viewer, empty copy',asy
await expect(heading.locator(':scope > span.small')).toHaveText('No negative · same positive prompt');
await expect(section.locator('.empty')).toHaveText('Saved variant · no runs yet.');await expect(section.locator('.empty')).toBeVisible();
await expect(section.locator('article[data-image-key]')).toHaveCount(0);
// D47 (maintainer ruling 2026-10-21): the concept's "Shared gallery" heading is the gallery's only heading, so the
// collection's "Linked images" heading and its "N loaded · N linked images" count are gone; the layout row and the
// selected-image side panel stay.
await expect(section.locator('.section-caption')).toHaveCount(0);
await expect(section.getByRole('heading',{name:'Linked images',exact:true})).toHaveCount(0);
await expect(section.getByText(/ loaded · /)).toHaveCount(0);
// Outside the side panel (whose own heading names the selected image), the gallery has one section heading.
expect(await section.evaluate(node=>[...node.querySelectorAll('h1, h2')].filter(item=>!item.closest('aside')).map(item=>item.textContent))).toEqual(['Shared gallery']);
await expect(section.getByRole('group',{name:'Image layout',exact:true})).toBeVisible();
await expect(section.getByRole('combobox',{name:'Thumbnail size',exact:true})).toBeVisible();
await expect(section.getByRole('complementary',{name:'Selected image details',exact:true})).toBeVisible();
// As in the concept, the gallery follows the variants panel; recorded executions (#209) stay below it.
const order=await page.locator('.library-history').evaluate(node=>[...node.querySelectorAll('section[aria-label]')].map(item=>item.getAttribute('aria-label')).filter(name=>['Saved negative variants','Shared gallery','Recorded executions'].includes(name)));
expect(order).toEqual(['Saved negative variants','Shared gallery','Recorded executions']);
@@ -997,6 +1008,11 @@ 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.
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 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});
await expect.poll(()=>page.evaluate(()=>document.documentElement.scrollWidth-document.documentElement.clientWidth)).toBeLessThanOrEqual(1);
@@ -708,7 +708,8 @@ test('archive hides a pair, Show archived finds it and Restore keeps it selected
// #249 D8 ruling (a): the Library-embedded gallery has no select checkbox and no selection bar.
await expect(page.getByRole('region',{name:'Shared gallery',exact:true}).locator('.image-overlays input[type="checkbox"]')).toHaveCount(0);
await expect(page.locator('#bulkbar, .bulkbar')).toHaveCount(0);
await expect(page.getByRole('region',{name:'Shared gallery',exact:true}).getByText('1 loaded · 1 linked image',{exact:true})).toBeVisible();
// D47: the archived pair's gallery is named by its "Shared gallery" heading, with no separate loaded count.
await expect(page.getByRole('region',{name:'Shared gallery',exact:true}).locator('.panel-heading .small')).toHaveText(/ · Archived · same positive prompt$/);
expect(galleryReads.at(-1).get('archive')).toBe('archived');expect(galleryReads.at(-1).get('pair_id')).toBe(String(f.pair_id));
await expect(headingActions(page).getByRole('button',{name:/^Send prompt pair to ComfyUI/})).toBeEnabled();
const writes=page.writes.length;
+10 -4
View File
@@ -96,7 +96,11 @@ function deliverFavorite(key, favorite) {
for (const target of embeddedFavoriteTargets) target(key, favorite);
}
export function mountImages(main, api, parentScope, {embedded = false, fetchPage, emptyText} = {}) {
/**
* `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.
*/
export function mountImages(main, api, parentScope, {embedded = false, fetchPage, emptyText, caption: showCaption = true, focusTarget = null} = {}) {
const scope = createScope();
const dispose = () => scope.dispose();
parentScope.signal.addEventListener('abort', dispose, {once:true});
@@ -194,7 +198,7 @@ export function mountImages(main, api, parentScope, {embedded = false, fetchPage
collection.setAttribute('aria-label', 'Images');
// The concept's caption replaces the old "All images" heading; the embedded gallery keeps its own heading.
const caption = el('div', undefined, 'section-caption');
const collectionHeading = embedded ? el('h2', 'Linked images') : null;
const collectionHeading = embedded && showCaption ? el('h2', 'Linked images') : null;
if (collectionHeading) { collectionHeading.tabIndex = -1; caption.append(collectionHeading); }
const count = el('span', 'Loading images…');
if (!embedded) count.id = 'result-count';
@@ -202,7 +206,7 @@ export function mountImages(main, api, parentScope, {embedded = false, fetchPage
captionDay.hidden = true;
caption.append(count);
if (!embedded) caption.append(captionDay);
const focusFallback = collectionHeading || heading?.h1 || main;
const focusFallback = collectionHeading || focusTarget || heading?.h1 || main;
const grid = el('div', undefined, 'image-grid');
grid.setAttribute('role', 'list'); grid.setAttribute('aria-label', 'Image collection');
const continuation = el('div', undefined, 'infinite-continuation');
@@ -217,7 +221,9 @@ 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;
collection.append(caption, grid);
// D47: the history page's shared gallery is named by its own "Shared gallery" heading, so it has no caption.
if (showCaption) collection.append(caption);
collection.append(grid);
if (showsEmpty) collection.append(empty);
collection.append(continuation);
// After a Load more or Retry press, the index of the first image that batch appends (concept: focus it).
+3 -2
View File
@@ -616,7 +616,8 @@ export function mountLibrary(main,api,parentScope,{positiveId}={}) {
// The concept's shared gallery (app.js a616-621): its heading names the selected variant; the embedded
// collection renders the PR C media cards. Recorded executions (#209) stay below it. /* D47 */
const context=el('span','','small');context.setAttribute('role','status');
const galleryHeading=el('div',undefined,'panel-heading');galleryHeading.append(el('h2','Shared gallery'),context);
const galleryTitle=el('h2','Shared gallery');galleryTitle.tabIndex=-1;
const galleryHeading=el('div',undefined,'panel-heading');galleryHeading.append(galleryTitle,context);
const gallery=el('section',undefined,'history-gallery');gallery.setAttribute('aria-label','Shared gallery');gallery.append(galleryHeading);
const images=el('div',undefined,'library-linked-images');gallery.append(images);
const recorded=el('div',undefined,'library-recorded-container');
@@ -690,7 +691,7 @@ export function mountLibrary(main,api,parentScope,{positiveId}={}) {
updateGalleryContext();
galleryScope?.dispose();galleryScope=createScope();images.replaceChildren();
// An archived pair's images are read in the archived scope; the gallery stays authoritative.
mountImages(images,api,galleryScope,{embedded:true,emptyText:pairId===null?undefined:'Saved variant · no runs yet.',fetchPage:({parameters,signal})=>{const query=new URLSearchParams(parameters);if(pairId!==null) query.set('pair_id',String(pairId));if(archived) query.set('archive','archived');return api.get(`/prompt_manager/library/${positiveId}/images?${query}`,{signal});}});
mountImages(images,api,galleryScope,{embedded:true,caption:false,focusTarget:galleryTitle,emptyText:pairId===null?undefined:'Saved variant · no runs yet.',fetchPage:({parameters,signal})=>{const query=new URLSearchParams(parameters);if(pairId!==null) query.set('pair_id',String(pairId));if(archived) query.set('archive','archived');return api.get(`/prompt_manager/library/${positiveId}/images?${query}`,{signal});}});
updateSend();updateCompare();
}
/** A metadata receipt names no pair, so the page re-reads its own selection through the exact-ID route. */