feat(history): concept heading and summary
The Prompt history page takes the approved mock's heading and summary surface (docs/concepts/workspace/app.js renderHistory a591-598, #249 F1): - Heading description is "Prompt history · one positive, one shared gallery"; the actions stay "← Prompt library", "Edit metadata" and button.send "Send to ComfyUI" (aria-label "Send prompt pair to ComfyUI: <title>"), on one row at 1600. Send is disabled only while the selected pair's roles are loading (D24). - The summary is article.surface > span.chip category, the positive and selected negative prompt blocks, then .card-meta "N tracked runs · N images · N negative variants". Tracked runs come from the recorded history window's recorded_run_count (hidden until a read completes, kept while a refresh is in flight); images and variants from the saved positive's image_count and variant_count. - The summary's copy/send actions are named after the prompt, as in the concept ("Copy positive prompt: <title>"); mountPromptBlocks gains setTitle so an in-place metadata edit renames them without a remount. - mountRecordedHistory gains an optional onRecordedCount callback. Selector mapping: - .library-surface (history summary) -> .library-history article.surface - .library-surface .library-chip -> article.surface > span.chip - "Copy|Send <role> prompt[ to ComfyUI]" in "Saved prompt texts" -> "...: <title>" - heading p.muted "One positive prompt. Reusable negative variants. One shared gallery." -> "Prompt history · one positive, one shared gallery" - Unchanged: region "Saved prompt texts", .library-variants, .library-shared-gallery, .recorded-history (still .library-surface). Tests: workspace-history "history heading actions stay on one row at 1600 and are enabled after load" and "summary meta uses recorded runs" (both failed before), a prompt-blocks setTitle unit, and the mapped selectors in workspace-visual, workspace-library-editor and the canonical comparison.
This commit is contained in:
@@ -35,7 +35,7 @@ export async function captureCanonicalState(page, testInfo, name, selector) {
|
||||
|
||||
export async function historyGeometry(page, production) {
|
||||
const selectors = production ? {
|
||||
surface: '.library-surface', timeline: '.library-branches', positive: '.library-positive-lane',
|
||||
surface: '.library-history article.surface', timeline: '.library-branches', positive: '.library-positive-lane',
|
||||
branch: '.library-variant-lane', label: '.library-variant-lane span', dot: '.library-variant-dot[aria-pressed="true"]',
|
||||
positiveLabel: '.library-positive-lane span',
|
||||
role: '[aria-label="Saved prompt texts"] .prompt-section',
|
||||
|
||||
@@ -721,3 +721,45 @@ test('completed page one stays bounded across mode changes until explicit output
|
||||
await page.keyboard.press('Escape');await retained();
|
||||
} finally {releaseNext();}
|
||||
});
|
||||
|
||||
// #249 PR F1: the concept heading (app.js a591–597) and summary surface (a598) with real counts (D24).
|
||||
const historyHeading=page=>page.locator('#workspace-main > .heading');
|
||||
const summary=page=>page.locator('#workspace-main .library-history article.surface');
|
||||
test('history heading actions stay on one row at 1600 and are enabled after load',async({page,request})=>{
|
||||
await page.setViewportSize({width:1600,height:1100});const f=page.fixture;
|
||||
let releaseDetail;const held=new Promise(resolve=>{releaseDetail=resolve;});
|
||||
await page.route(new RegExp(`/prompt_manager/library/${f.positive_id}\\?`),async route=>{await held;await route.continue().catch(()=>{});});
|
||||
try {
|
||||
await page.goto(`/prompt_manager/workspace#/library/${f.positive_id}`);
|
||||
const heading=historyHeading(page),send=heading.locator('.heading-actions > button.send');
|
||||
await expect(heading.locator('p.muted')).toHaveText('Prompt history · one positive, one shared gallery');
|
||||
// D24: disabled only while the selected pair's roles are loading.
|
||||
await expect(send).toBeDisabled();releaseDetail();
|
||||
await expect(recorded(page)).toBeVisible();await expect(send).toBeEnabled();
|
||||
} finally {releaseDetail();}
|
||||
const heading=historyHeading(page),title=await heading.locator('h1').textContent();
|
||||
const actions=heading.locator('.heading-actions > button');
|
||||
await expect(actions).toHaveCount(3);
|
||||
expect(await actions.evaluateAll(nodes=>nodes.map(node=>node.textContent.trim()))).toEqual(['← Prompt library','Edit metadata','Send to ComfyUI']);
|
||||
await expect(actions.nth(2)).toHaveClass('send');
|
||||
await expect(actions.nth(2)).toHaveAccessibleName(`Send prompt pair to ComfyUI: ${title}`);
|
||||
for(const action of [actions.nth(0),actions.nth(1)]) await expect(action).toBeEnabled();
|
||||
const boxes=await actions.evaluateAll(nodes=>nodes.map(node=>{const box=node.getBoundingClientRect();return {y:box.y,height:box.height};}));
|
||||
expect(new Set(boxes.map(box=>box.y)).size).toBe(1);
|
||||
const row=await heading.locator('.heading-actions').evaluate(node=>node.getBoundingClientRect().height);
|
||||
expect(row).toBe(boxes[0].height);
|
||||
});
|
||||
test('summary meta uses recorded runs',async({page,request})=>{
|
||||
const f=page.fixture;await open(page,f.positive_id);
|
||||
const detail=await(await request.get(`/prompt_manager/library/${f.positive_id}`)).json(),runs=(await historyRead(request,f.positive_id)).recorded_run_count;
|
||||
const surface=summary(page),title=await historyHeading(page).locator('h1').textContent();
|
||||
await expect(surface).toHaveCount(1);
|
||||
await expect(surface.locator(':scope > span.chip')).toHaveText(detail.positive.category || 'Uncategorized');
|
||||
const plural=(count,word)=>`${count} ${count===1?word:`${word}s`}`;
|
||||
await expect(surface.locator(':scope > .card-meta > span')).toHaveText([plural(runs,'tracked run'),plural(detail.positive.image_count,'image'),plural(detail.positive.variant_count,'negative variant')]);
|
||||
// The concept order: category chip, positive block, selected negative block, then the meta line.
|
||||
expect(await surface.evaluate(node=>[...node.querySelectorAll('.chip, .prompt-section, .card-meta')].filter(item=>item.checkVisibility()).map(item=>item.className))).toEqual(['chip','prompt-section positive','prompt-section negative','card-meta']);
|
||||
for(const name of ['Copy positive prompt','Send positive prompt to ComfyUI','Copy negative prompt','Send negative prompt to ComfyUI'])
|
||||
await expect(saved(page).getByRole('button',{name:`${name}: ${title}`,exact:true})).toBeVisible();
|
||||
await expect(surface.locator('.prompt-section').first()).toHaveCSS('border-top-width','1px');
|
||||
});
|
||||
|
||||
@@ -565,7 +565,7 @@ test('edit metadata stays on the page and re-reads the selected pair',async({pag
|
||||
await field(dialog,'Title').fill('Edited in place');await field(dialog,'Category').fill('Studio work');await button(dialog,'Save').click();await expect(dialog).toHaveCount(0);
|
||||
await expect(page.locator('#workspace-main h1')).toHaveText('Edited in place');await expect(page.locator('#workspace-main h1')).toBeFocused();
|
||||
await expect(toast(page)).toHaveText('Saved.');
|
||||
await expect(page).toHaveURL(new RegExp(`#/library/${f.positive_id}$`));await expect(historyRegion(page).locator('.library-surface .library-chip').first()).toHaveText('Studio work');
|
||||
await expect(page).toHaveURL(new RegExp(`#/library/${f.positive_id}$`));await expect(historyRegion(page).locator('article.surface > span.chip').first()).toHaveText('Studio work');
|
||||
await expect(pressedVariant(page)).toHaveAccessibleName('Select negative variant 2');
|
||||
await expect.poll(()=>pairReads.length).toBeGreaterThan(before);
|
||||
await expect(headingActions(page).locator('button.send')).toHaveAccessibleName('Send prompt pair to ComfyUI: Edited in place');
|
||||
|
||||
@@ -101,7 +101,8 @@ for (const [width, height] of [[1600, 1100], [390, 844]]) for (const theme of ['
|
||||
const roles = page.getByRole('region', { name: 'Saved prompt texts', exact: true });
|
||||
const positive = roles.getByRole('region', { name: 'Positive prompt', exact: true });
|
||||
expect(await positive.evaluate(node => node.nextElementSibling?.getAttribute('aria-label'))).toBe('Negative prompt');
|
||||
for (const name of ['Copy positive prompt', 'Send positive prompt to ComfyUI', 'Copy negative prompt', 'Send negative prompt to ComfyUI']) await expect(roles.getByRole('button', { name, exact: true })).toBeVisible();
|
||||
const title = await page.locator('#workspace-main > .heading h1').textContent();
|
||||
for (const name of ['Copy positive prompt', 'Send positive prompt to ComfyUI', 'Copy negative prompt', 'Send negative prompt to ComfyUI']) await expect(roles.getByRole('button', { name: `${name}: ${title}`, exact: true })).toBeVisible();
|
||||
// As in the concept, the selected pair's send sits in the history heading, not among the saved texts.
|
||||
await expect(roles.getByRole('button', { name: 'Send prompt pair', exact: true })).toHaveCount(0);
|
||||
await expect(page.locator('#workspace-main > .heading button.send')).toBeEnabled();
|
||||
@@ -110,7 +111,7 @@ for (const [width, height] of [[1600, 1100], [390, 844]]) for (const theme of ['
|
||||
await expect(page.getByRole('complementary', { name: 'Selected image details', exact: true })).toContainText('canonical-alt.png');
|
||||
await page.locator('.library-shared-gallery .media-card.selected .image-open').click();
|
||||
await expect(page.locator('dialog#viewer-dialog')).toBeVisible();await page.keyboard.press('Escape');
|
||||
for (const [name, selector] of [['saved', '.library-surface'], ['branches', '.library-variants'], ['gallery', '.library-shared-gallery'], ['chronology', '.recorded-history']]) {
|
||||
for (const [name, selector] of [['saved', '.library-history article.surface'], ['branches', '.library-variants'], ['gallery', '.library-shared-gallery'], ['chronology', '.recorded-history']]) {
|
||||
await captureCanonicalState(page, testInfo, `production-${name}-${width}-${theme}`, selector);
|
||||
}
|
||||
const run = page.locator(`button[data-run-id="${fixture.canonical_run}"][data-anchor-id="10"]`);
|
||||
@@ -541,7 +542,7 @@ test('Library visual evidence keeps natural cards and returns from real history
|
||||
await noOverflow(page);
|
||||
await page.getByRole('button',{name:'Explore history',exact:true}).first().click();
|
||||
await expect(page.getByRole('region',{name:'Prompt history',exact:true})).toBeVisible();
|
||||
await expect(page.locator('#workspace-main > .heading p.muted')).toHaveText('One positive prompt. Reusable negative variants. One shared gallery.');
|
||||
await expect(page.locator('#workspace-main > .heading p.muted')).toHaveText('Prompt history · one positive, one shared gallery');
|
||||
await page.getByRole('button',{name:'← Prompt library',exact:true}).click();
|
||||
await expect(page.locator('article[data-prompt-id]')).toHaveCount(6);
|
||||
await expect(page.locator('#workspace-main > .heading p.muted')).toHaveText('One positive. Reusable negatives. Every experiment stays connected.');
|
||||
@@ -643,7 +644,7 @@ test('embedded history gallery keeps the history heading and disposes its layout
|
||||
for (const layout of ['List','Masonry','Grid']) {
|
||||
await page.getByRole('button',{name:layout,exact:true}).click();await orderedMedia(page);
|
||||
await expect(page.locator('h1')).toHaveText(title);
|
||||
await expect(page.locator('#workspace-main > .heading p.muted')).toHaveText('One positive prompt. Reusable negative variants. One shared gallery.');
|
||||
await expect(page.locator('#workspace-main > .heading p.muted')).toHaveText('Prompt history · one positive, one shared gallery');
|
||||
}
|
||||
await attachEvidence(page,testInfo,`after-images-embedded-history-${width}`,false);
|
||||
await page.getByRole('button',{name:'← Prompt library',exact:true}).click();
|
||||
|
||||
@@ -152,6 +152,21 @@ test('a title names each action after its prompt (concept "Copy positive prompt:
|
||||
assert.deepEqual(labels,['Positive prompt','Copy positive prompt: Alpine observatory','Send positive prompt to ComfyUI: Alpine observatory']);
|
||||
}finally{scope.dispose();f.restore();}
|
||||
});
|
||||
test('setTitle renames the actions in place and keeps the last prompts and state',()=>{
|
||||
const f=dom(),scope=createScope(),container=new f.Element('div');
|
||||
try{
|
||||
const view=module.mountPromptBlocks(container,{parentScope:scope,sender:{open:()=>{}},title:'Old name',roles:['positive'],prompts:{positive:record('p')}});
|
||||
view.setState({prompts:{positive:record('kept')},actionsEnabled:false});
|
||||
const labels=()=>f.walk(container,node=>node.attributes['aria-label']).map(node=>node.attributes['aria-label']);
|
||||
const rendered=container.children[0];view.setTitle('Old name');assert.equal(container.children[0],rendered,'an unchanged title does not re-render');
|
||||
view.setTitle('New name');
|
||||
assert.deepEqual(labels(),['Positive prompt','Copy positive prompt: New name','Send positive prompt to ComfyUI: New name']);
|
||||
assert.ok(f.walk(container,node=>node.textContent==='kept').length);
|
||||
assert.ok(f.walk(container,node=>node.dataset.copyRole==='positive').every(node=>node.disabled===true));
|
||||
view.setTitle('');assert.deepEqual(labels(),['Positive prompt','Copy positive prompt','Send positive prompt to ComfyUI']);
|
||||
scope.dispose();view.setTitle('After dispose');
|
||||
}finally{scope.dispose();f.restore();}
|
||||
});
|
||||
const shape=node=>({tag:node.tagName,className:node.className,...(node.attributes['aria-label']?{label:node.attributes['aria-label']}:{}),
|
||||
...(node.children.length?{children:node.children.filter(child=>child.tagName!=='svg').map(shape)}:{text:node.textContent})});
|
||||
test('prompt block emits concept markup without a source line',()=>{
|
||||
|
||||
@@ -29,7 +29,7 @@ function place(parent, node, before) {
|
||||
else parent.insertBefore(node, before);
|
||||
}
|
||||
|
||||
export function mountRecordedHistory(container, {api, positiveId, parentScope, sender, visit = {}, onParentRevalidate}) {
|
||||
export function mountRecordedHistory(container, {api, positiveId, parentScope, sender, visit = {}, onParentRevalidate, onRecordedCount}) {
|
||||
const scope = createScope(), dispose = () => scope.dispose();
|
||||
if (parentScope.signal.aborted) {dispose();return {dispose};}
|
||||
parentScope.signal.addEventListener('abort', dispose, {once:true});
|
||||
@@ -61,6 +61,9 @@ export function mountRecordedHistory(container, {api, positiveId, parentScope, s
|
||||
const controller = createHistoryController({api, positiveId, restore:visit, onChange:next => {
|
||||
render(next);runView?.setState(next);
|
||||
if (next.parent !== activeParent) {activeParent = next.parent;onParentRevalidate?.(next.parent);}
|
||||
// The history summary's "N tracked runs" (#249 D24): a completed window read's count; a read in flight keeps the last.
|
||||
const window = next.window;
|
||||
if (!window.loading) onRecordedCount?.(window.history_epoch && !window.error ? window.recorded_run_count : null);
|
||||
}});
|
||||
state = controller.state;
|
||||
runView = mountRecordedRun(container, {api, controller, sender, parentScope:scope, onRestoreFocus:restoreMarker});
|
||||
|
||||
@@ -416,8 +416,8 @@ export function mountLibrary(main,api,parentScope,{positiveId}={}) {
|
||||
function mountHistory() {
|
||||
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: ← Prompt library, Edit metadata, then the selected pair's Send to ComfyUI.
|
||||
headingDescription(main,history,'One positive prompt. Reusable negative variants. One shared gallery.');
|
||||
// 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 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;actions.append(back,edit,send);
|
||||
@@ -460,8 +460,11 @@ export function mountLibrary(main,api,parentScope,{positiveId}={}) {
|
||||
if(state.error) {const retry=createButton('Retry prompt history');contentScope.listen(retry,'click',()=>loader.load(positiveId));body.append(retry);}return;
|
||||
}
|
||||
detail=state.detail;const content=contentScope;
|
||||
const summary=el('article',undefined,'library-surface');const categoryChip=el('span','','library-chip');summary.append(categoryChip);
|
||||
// The concept summary (app.js a598): category chip, positive and selected negative blocks, then the counts (D24).
|
||||
const summary=el('article',undefined,'surface');const categoryChip=el('span','','chip');summary.append(categoryChip);
|
||||
const promptContainer=el('div');promptContainer.setAttribute('role','region');promptContainer.setAttribute('aria-label','Saved prompt texts');summary.append(promptContainer);
|
||||
const meta=el('div',undefined,'card-meta'),runsMeta=el('span'),imagesMeta=el('span'),variantsMeta=el('span');runsMeta.hidden=true;
|
||||
meta.append(runsMeta,imagesMeta,variantsMeta);summary.append(meta);
|
||||
const parentStatus=el('p','','library-parent-status muted');parentStatus.hidden=true;parentStatus.setAttribute('role','status');
|
||||
const retryParent=createButton('Retry saved Library details'),reloadSaved=createButton('Reload saved Library view');retryParent.hidden=reloadSaved.hidden=true;summary.append(parentStatus,retryParent,reloadSaved);
|
||||
const ownerText=detail.positive.text;let savedReadState='current',parentRequest,parentGeneration=0;
|
||||
@@ -546,9 +549,11 @@ export function mountLibrary(main,api,parentScope,{positiveId}={}) {
|
||||
const archivedCount=Number.isSafeInteger(detail.positive.archived_variant_count)?detail.positive.archived_variant_count:0;
|
||||
showArchived.textContent=`Show archived (${archivedCount})`;showArchived.setAttribute('aria-pressed',String(archivedShown));
|
||||
};
|
||||
const updateMeta=()=>{imagesMeta.textContent=countOf(detail.positive.image_count ?? 0,'image');variantsMeta.textContent=countOf(detail.positive.variant_count ?? 0,'negative variant');};
|
||||
updateParent=()=>{
|
||||
main.querySelector('h1').textContent=displayTitle(detail.positive);categoryChip.textContent=detail.positive.category || 'Uncategorized';
|
||||
send.setAttribute('aria-label',`Send prompt pair to ComfyUI: ${displayTitle(detail.positive)}`);
|
||||
const name=displayTitle(detail.positive);
|
||||
main.querySelector('h1').textContent=name;categoryChip.textContent=detail.positive.category || 'Uncategorized';updateMeta();
|
||||
send.setAttribute('aria-label',`Send prompt pair to ComfyUI: ${name}`);blocks?.setTitle(name);
|
||||
};
|
||||
let activePairId;
|
||||
updateParent();updateBranches(state);
|
||||
@@ -589,7 +594,7 @@ export function mountLibrary(main,api,parentScope,{positiveId}={}) {
|
||||
if(current.negative_text!==variant.negative_text || current.explicit_empty!==variant.explicit_empty) {changed=true;return variant;}
|
||||
return {...variant,...current};
|
||||
});
|
||||
detail={...detail,positive:fresh.positive,variants};categoryChip.textContent=fresh.positive.category || 'Uncategorized';
|
||||
detail={...detail,positive:fresh.positive,variants};categoryChip.textContent=fresh.positive.category || 'Uncategorized';updateMeta();
|
||||
savedReadState=changed || unobserved || additional?'stale':'current';
|
||||
parentStatus.textContent=changed?'A saved negative changed identity. This view retains its previous selection and gallery. Reload the saved Library view to inspect current data.':unobserved?(fresh.variants_has_more?'Some loaded saved negatives were not observed in this bounded revalidation page. Reload the saved Library view to validate them.':'Some loaded saved negatives were not present in the revalidation result. This view retains its previous selection and gallery.'):additional?'Saved Library choices changed. This view retains its previous selection and gallery. Reload the saved Library view to inspect current choices.':'Saved Library details revalidated.';
|
||||
reloadSaved.hidden=savedReadState==='current';
|
||||
@@ -610,9 +615,9 @@ export function mountLibrary(main,api,parentScope,{positiveId}={}) {
|
||||
if(!historyVisits.has(positiveId)) historyVisits.set(positiveId,{});
|
||||
mountRecordedHistory(recorded,{api,positiveId,parentScope:content,sender,visit:historyVisits.get(positiveId),onParentRevalidate:parent=>{
|
||||
if(content.signal.aborted) return;parentGeneration++;parentRequest?.abort();setParentState(parent);
|
||||
}});
|
||||
},onRecordedCount:count=>{runsMeta.hidden=count===null;runsMeta.textContent=count===null?'':countOf(count,'tracked run');}});
|
||||
// The heading carries the pair send, as in the concept; the saved texts keep their per-role actions.
|
||||
blocks=mountPromptBlocks(promptContainer,{prompts:libraryPromptRoles(detail),sender,parentScope:content,includePairAction:false});
|
||||
blocks=mountPromptBlocks(promptContainer,{prompts:libraryPromptRoles(detail),sender,parentScope:content,includePairAction:false,title:displayTitle(detail.positive)});
|
||||
function select(pairId,{force=false}={}) {
|
||||
if(savedReadState!=='current' || (!force && galleryScope && activePairId===pairId)) return;
|
||||
if(pairId!==null && !recordFor(pairId)) pairId=null;
|
||||
|
||||
@@ -38,8 +38,8 @@ export function mountPromptBlocks(container, {prompts, sender, parentScope, role
|
||||
const named = label => contextLabel ? `${contextLabel} ${label.toLowerCase()} prompt` : `${label} prompt`;
|
||||
const action = role => contextLabel ? `${contextLabel[0].toLowerCase()}${contextLabel.slice(1)} ${role} prompt` : `${role} prompt`;
|
||||
// The concept names a role's copy and send after the prompt they act on (app.js a298–302): "…: <title>".
|
||||
const subject = title ? `: ${title}` : '';
|
||||
if (parentScope.signal.aborted) return {update() {}, setState() {}, dispose() {}};
|
||||
let subject = title ? `: ${title}` : '';
|
||||
if (parentScope.signal.aborted) return {update() {}, setState() {}, setTitle() {}, dispose() {}};
|
||||
const scope = childScope(parentScope);
|
||||
container.classList.add('prompt-inspector');
|
||||
scope.listen(container, 'click', async event => {
|
||||
@@ -66,9 +66,10 @@ export function mountPromptBlocks(container, {prompts, sender, parentScope, role
|
||||
}
|
||||
if (event.target.closest('[data-retry-prompts]')) onRetry?.();
|
||||
});
|
||||
let roles = {}, actionsEnabled = true;
|
||||
let roles = {}, actionsEnabled = true, current = {prompts};
|
||||
function render(state) {
|
||||
if (scope.signal.aborted) return;
|
||||
current = state;
|
||||
roles = {};
|
||||
actionsEnabled = state.actionsEnabled !== false;
|
||||
container.replaceChildren();
|
||||
@@ -119,5 +120,10 @@ export function mountPromptBlocks(container, {prompts, sender, parentScope, role
|
||||
}
|
||||
}
|
||||
render({prompts});
|
||||
return {update:prompts => render({prompts}), setState:render, dispose:() => scope.dispose()};
|
||||
// A saved title edited in place renames the actions without a remount (the history summary, #249 F1).
|
||||
const setTitle = next => {
|
||||
const value = next ? `: ${next}` : '';
|
||||
if (value !== subject) {subject = value;render(current);}
|
||||
};
|
||||
return {update:prompts => render({prompts}), setState:render, setTitle, dispose:() => scope.dispose()};
|
||||
}
|
||||
|
||||
@@ -214,6 +214,9 @@ pre.prompt-text { font: inherit; font-size: 12px; line-height: 1.75; white-space
|
||||
.prompt-card .prompt-text { display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; overflow: hidden; /* D23 */ }
|
||||
/* #249 D23: the prompt blocks' mount point is transparent, so the section sits in `.card-content` as the concept's does. */
|
||||
.card-content > .prompt-inspector { display: contents; /* D23 */ }
|
||||
/* #249 D24: the history summary's prompt blocks sit in `article.surface` as the concept's do (app.js a598); the mount
|
||||
point keeps its "Saved prompt texts" region name. */
|
||||
.library-history article.surface > .prompt-inspector { display: contents; /* D24 */ }
|
||||
/* #249 D23: a real title can be long or one unbroken word; it wraps inside the title row (the toolkit's buttons never
|
||||
wrap), while the category chip keeps whole words beside it and wraps only a name wider than its share of the row. */
|
||||
.card-title button { min-width: 0; white-space: normal; overflow-wrap: anywhere; /* D23 */ }
|
||||
|
||||
Reference in New Issue
Block a user