Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
739c9fef5c | ||
|
|
715e381a78 |
@@ -2928,6 +2928,14 @@ concept's danger "Archive…" maps to "Archive variant…" followed by a "Delete
|
||||
Tests: `tests/browser/workspace-library-delete.spec.mjs` (#481 cases), and `workspace-library-editor.spec.mjs` (the
|
||||
hand-off and the comparisons) and `library-editor-visual.spec.mjs` with the renamed button. JS only: no restart.
|
||||
|
||||
### Prompt page heading on one row (#524)
|
||||
|
||||
**D164** (beyond the concept, which never has four heading actions beside a long title): from 951px, the concept's
|
||||
sidebar breakpoint, the prompt page's heading actions keep one row and the title takes the remaining width, clamped to
|
||||
two lines with its full text in the DOM and in `title`. Below 951px the concept's wrapping and stacked heading is
|
||||
unchanged. A short title lays out as the concept's, so the history concept comparison is unaffected. A deleted prompt's
|
||||
page has no "Prompt history · …" description: it is emptied and hidden while the gone message shows.
|
||||
|
||||
### Delete review and prompt-page polish (#512)
|
||||
|
||||
Found in the live test of #505/#507/#509. **D163** (beyond the concept, maintainer ruling 2026-10-29: delete was hard to
|
||||
|
||||
@@ -529,6 +529,60 @@ test('a deleted prompt\'s page says it no longer exists, with the way back and n
|
||||
await expect(page).toHaveURL(/#\/library$/);
|
||||
});
|
||||
|
||||
// #524 (live test of #522): a long title clamps beside the heading actions, which stay on one row at desktop widths.
|
||||
const LONG_TITLE='masterpiece, high quality, very_awa, newest, absurdres, highres, depth_of_field, detailed eyes, realistic eyes, fake eyelashes, black eyes, white_nails, 1girl, solo, black bob_cut, upper body';
|
||||
async function openLongTitle(page,request,width) {
|
||||
const dual=byText(await items(request),'Dual role portrait');
|
||||
await page.route(new RegExp(`/prompt_manager/library/${dual.id}\\?`),async route=>{
|
||||
const response=await route.fetch(),json=await response.json();
|
||||
await route.fulfill({response,json:{...json,positive:{...json.positive,title:LONG_TITLE}}});
|
||||
});
|
||||
await page.setViewportSize({width,height:900});
|
||||
await page.goto(`/prompt_manager/workspace#/library/${dual.id}`);
|
||||
await expect(page.locator('#workspace-main h1')).toHaveText(LONG_TITLE);
|
||||
expect(await page.evaluate(()=>document.documentElement.scrollWidth<=document.documentElement.clientWidth)).toBe(true);
|
||||
return page.locator('#workspace-main .heading-actions > button, #workspace-main .heading-actions > .more-menu > button');
|
||||
}
|
||||
for(const width of [1440,951]) test(`a long title clamps and the heading actions stay on one row at ${width}px (#524)`,async({page,request})=>{
|
||||
const actions=await openLongTitle(page,request,width);
|
||||
await expect(actions).toHaveText(['← Prompt library','Edit metadata','Send to ComfyUI','⋯']);
|
||||
const tops=await actions.evaluateAll(nodes=>nodes.map(node=>Math.round(node.getBoundingClientRect().top)));
|
||||
expect(new Set(tops).size).toBe(1);
|
||||
const h1=page.locator('#workspace-main h1');
|
||||
const shape=await h1.evaluate(node=>({scroll:node.scrollHeight,client:node.clientHeight,line:parseFloat(getComputedStyle(node).lineHeight),title:node.title}));
|
||||
expect(shape.scroll).toBeGreaterThan(shape.client);
|
||||
expect(shape.client).toBeLessThanOrEqual(Math.ceil(shape.line*2));
|
||||
// The clamp hides pixels only: the whole title stays the text and shows on hover.
|
||||
expect(shape.title).toBe(LONG_TITLE);
|
||||
// The buttons do not shrink: no label is cut or broken onto a second line.
|
||||
const broken=await actions.evaluateAll(nodes=>nodes.filter(node=>node.scrollWidth>node.clientWidth||node.getBoundingClientRect().height>44).length);
|
||||
expect(broken).toBe(0);
|
||||
const [h1Box,firstBox]=[await h1.boundingBox(),await actions.first().boundingBox()];
|
||||
expect(h1Box.x+h1Box.width).toBeLessThanOrEqual(firstBox.x);
|
||||
});
|
||||
|
||||
test('a long title at 390px keeps the concept\'s stacked heading, not clamped, inside the viewport (#524)',async({page,request})=>{
|
||||
const actions=await openLongTitle(page,request,390);
|
||||
const h1=await page.locator('#workspace-main h1').evaluate(node=>({scroll:node.scrollHeight,client:node.clientHeight}));
|
||||
expect(h1.scroll).toBeLessThanOrEqual(h1.client);
|
||||
const right=await actions.evaluateAll(nodes=>Math.max(...nodes.map(node=>node.getBoundingClientRect().right)));
|
||||
expect(right).toBeLessThanOrEqual(390);
|
||||
});
|
||||
|
||||
test('a deleted prompt\'s page has no history subtitle, and a page that loads shows it (#524)',async({page,request})=>{
|
||||
const list=await items(request),dual=byText(list,'Dual role portrait'),dog=byText(list,'Dog landscape');
|
||||
const description=page.locator('#workspace-main > .heading p.muted');
|
||||
await deleteFromPage(page,dual.id);
|
||||
await choice(page).getByRole('radio',{name:'Delete permanently, with their saved pairs'}).check();
|
||||
await review(page).getByRole('button',{name:'Delete 1 prompt',exact:true}).click();
|
||||
await expect(page).toHaveURL(/#\/library$/);
|
||||
await page.goto(`/prompt_manager/workspace#/library/${dual.id}`);
|
||||
await expect(page.locator('#workspace-main .library-status')).toHaveText('This prompt no longer exists. It may have been deleted.');
|
||||
await expect(description).toBeHidden();await expect(description).toHaveText('');
|
||||
await page.goto(`/prompt_manager/workspace#/library/${dog.id}`);
|
||||
await expect(description).toHaveText('Prompt history · one positive, one shared gallery');await expect(description).toBeVisible();
|
||||
});
|
||||
|
||||
test('the archived shelf\'s heading count and its list agree under a remembered search (#512 item 5)',async({page,request})=>{
|
||||
const all=await items(request);
|
||||
const dual=byText(all,'Dual role portrait'),dog=byText(all,'Dog landscape');
|
||||
|
||||
@@ -23,6 +23,7 @@ import {createMoreMenu} from './more-menu.mjs';
|
||||
let shelfVisit=null;
|
||||
// #512: what a prompt page says when its prompt no longer exists (404).
|
||||
const PROMPT_GONE='This prompt no longer exists. It may have been deleted.';
|
||||
const HISTORY_DESCRIPTION='Prompt history · one positive, one shared gallery';
|
||||
// Every confirmed receipt advances this; a shelf mounted before it never restores its cached stream.
|
||||
let writeGeneration=0, focusHeadingOnMount=false, savedParentId=null;
|
||||
// The pair one reconciled save names, taken once by the history it opens (the concept selects it after a save, app.js 986).
|
||||
@@ -62,8 +63,9 @@ function headingActions(main,fallback) {
|
||||
}
|
||||
/** The shell heading's description (concept `p.muted`); a view mounted without one shows it at the top of `fallback`. */
|
||||
function headingDescription(main,fallback,text) {
|
||||
const description=pageHeading(main)?.description;
|
||||
if(description) description.textContent=text;else fallback.prepend(el('p',text,'muted'));
|
||||
let description=pageHeading(main)?.description;
|
||||
if(description) description.textContent=text;else {description=el('p',text,'muted');fallback.prepend(description);}
|
||||
return description;
|
||||
}
|
||||
|
||||
/** A confirmed receipt makes cached shelf items, cursor and measured heights stale; filters and anchor survive. */
|
||||
@@ -488,7 +490,9 @@ export function mountLibrary(main,api,parentScope,{positiveId,savedState=null,on
|
||||
const history=el('section',undefined,'library-history');history.setAttribute('aria-label','Prompt history');main.append(history);
|
||||
const body=el('div');history.append(body);
|
||||
// Concept heading (app.js a591–597): ← Prompt library, Edit metadata, then the selected pair's Send to ComfyUI.
|
||||
headingDescription(main,history,'Prompt history · one positive, one shared gallery');
|
||||
const description=headingDescription(main,history,HISTORY_DESCRIPTION);
|
||||
// #524 D164: at desktop widths a long title clamps beside the actions.
|
||||
pageHeading(main)?.heading.classList.add('prompt-page-heading');
|
||||
const actions=headingActions(main,history);
|
||||
const back=createButton('← Prompt library'),edit=createButton('Edit metadata'),send=createButton('','send');
|
||||
send.append(createWorkspaceIcon('send'),el('span','Send to ComfyUI'));send.disabled=true;
|
||||
@@ -584,7 +588,9 @@ export function mountLibrary(main,api,parentScope,{positiveId,savedState=null,on
|
||||
// Edit metadata and ⋯ act on a loaded prompt only (review M2).
|
||||
const hadFocus=[edit,moreMenu.button].includes(document.activeElement);
|
||||
const loaded=!state.loading && !state.error;edit.disabled=!loaded;moreMenu.setDisabled(!loaded);
|
||||
if(state.error?.status===404) {
|
||||
// #524: a prompt that no longer exists has no history to describe.
|
||||
const gone=state.error?.status===404;description.textContent=gone?'':HISTORY_DESCRIPTION;description.hidden=gone;
|
||||
if(gone) {
|
||||
const message=el('p',PROMPT_GONE,'library-status');message.setAttribute('role','status');body.append(message);
|
||||
const open=createButton('Open the Prompt library');contentScope.listen(open,'click',()=>{window.location.hash='#/library';});body.append(open);
|
||||
// A focused control that was just disabled hands focus to the way back (review M3).
|
||||
@@ -741,7 +747,7 @@ export function mountLibrary(main,api,parentScope,{positiveId,savedState=null,on
|
||||
const updateMeta=()=>{imagesMeta.textContent=countOf(detail.positive.image_count ?? 0,'image');variantsMeta.textContent=countOf(detail.positive.variant_count ?? 0,'negative variant');};
|
||||
updateParent=()=>{
|
||||
const name=displayTitle(detail.positive);
|
||||
main.querySelector('h1').textContent=name;categoryChip.textContent=detail.positive.category || 'Uncategorized';updateMeta();
|
||||
const h1=main.querySelector('h1');h1.textContent=name;h1.title=name;categoryChip.textContent=detail.positive.category || 'Uncategorized';updateMeta();
|
||||
send.setAttribute('aria-label',`Send prompt pair to ComfyUI: ${name}`);blocks?.setTitle(name);
|
||||
};
|
||||
let activePairId;
|
||||
|
||||
@@ -493,3 +493,15 @@ pre.prompt-text { font: inherit; font-size: 12px; line-height: 1.75; margin-bloc
|
||||
}
|
||||
.more-menu-list[hidden] { display: none; }
|
||||
.more-menu-list > button { justify-content: flex-start; text-align: left; width: 100%; white-space: nowrap; }
|
||||
|
||||
/* D164 (#524): the prompt page has four heading actions and titles of any length, which the concept never shows. From
|
||||
the concept's sidebar breakpoint up the actions keep one row and the title takes what is left, clamped to two lines
|
||||
(its full text stays in the DOM and in `title`). A short title lays out exactly as the concept's. */
|
||||
@media (min-width: 951px) {
|
||||
.heading.prompt-page-heading > div:first-child { min-width: 0; }
|
||||
.heading.prompt-page-heading > .heading-actions { flex: 0 0 auto; }
|
||||
.heading.prompt-page-heading h1 {
|
||||
display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
|
||||
overflow: hidden; overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user