fix(history): name the recorded run's current-file prompts apart from its frozen fields

Now that the inspector resolves more saved files, the Recorded run dialog can
hold two regions named "Positive prompt" (and two of each Copy/Send action):
the frozen run field and the inline current file's prompt. Assistive
technology could not tell them apart, and the real-host chronology test read
two <pre> elements.

Prompt blocks take an optional context label. The inline current file inside
a recorded run names its regions "Current file positive prompt" and its
actions "Copy/Send current file positive prompt"; the visible headings stay
as in the concept. The real-host test now also scopes its frozen-field reads
to the "Frozen run prompt texts" section, and a disposable-server workflow
pins the distinct names with a resolved current file.
This commit is contained in:
Vito Sansevero
2026-10-19 16:07:06 -07:00
parent c65b2d5449
commit 1237a34f25
6 changed files with 46 additions and 9 deletions
+20
View File
@@ -458,6 +458,26 @@ test('frozen Effective pair uses the shared acknowledged sender and Escape close
} finally {await host.evaluate(()=>window.historyHost.dispose());await host.close();}
});
test('the inline current file names its prompts apart from the frozen run fields',async({page})=>{
// The current file resolves its own prompts, as a real host's saved PNG does, so the dialog holds two prompt texts per role.
await page.route(url=>url.pathname.endsWith('/inspection'),async route=>{const response=await route.fetch();const body=await response.json();
body.prompts={positive:{status:'resolved',text:'current file positive',source:'graph-inferred'},negative:{status:'resolved',text:'current file negative',source:'graph-inferred'}};
await route.fulfill({response,json:body});});
await open(page,page.fixture.detail_id);await marker(page,page.fixture.runs.shared).click();await expect(dialog(page).locator('[data-receipt]')).toHaveCount(5);
await dialog(page).getByRole('button',{name:'Raw',exact:true}).click();
const frozen=dialog(page).getByRole('region',{name:'Frozen run prompt texts',exact:true});
const before=await exact(frozen.getByRole('region',{name:'Positive prompt',exact:true}).locator('pre'));
await dialog(page).locator('[data-receipt]').first().getByRole('button',{name:'View current file',exact:true}).click();
await expect(page.getByRole('dialog',{name:'Image preview',exact:true})).toBeVisible();await page.keyboard.press('Escape');await expect(dialog(page)).toBeVisible();
const current=dialog(page).getByRole('region',{name:'Current reported file',exact:true});await expect(current).toBeVisible();
await expect(current.getByRole('region',{name:'Current file positive prompt',exact:true}).locator('pre')).toHaveText('current file positive');
await expect(current.getByRole('region',{name:'Current file negative prompt',exact:true}).locator('pre')).toHaveText('current file negative');
await expect(current.getByRole('button',{name:'Copy current file positive prompt',exact:true})).toBeVisible();
for(const role of ['Positive','Negative'])await expect(dialog(page).getByRole('region',{name:`${role} prompt`,exact:true})).toHaveCount(1);
await expect(current.getByRole('region',{name:/^(Positive|Negative) prompt$/})).toHaveCount(0);
expect(await exact(dialog(page).getByRole('region',{name:'Positive prompt',exact:true}).locator('pre'))).toBe(before);
});
test('pagehide tears down dialogs and persisted reentry revalidates before enabling recorded actions',async({page})=>{
await open(page,page.fixture.detail_id);await marker(page,page.fixture.runs.shared).click();await expect(dialog(page).locator('[data-receipt]')).toHaveCount(5);
await dialog(page).locator('[data-receipt]').first().getByRole('button',{name:'View current file',exact:true}).click();await expect(page.getByRole('dialog',{name:'Image preview',exact:true})).toBeVisible();
+1 -1
View File
@@ -243,7 +243,7 @@ test.describe('public chronological history with the actual acknowledged host br
const frozen = attempts[0].context;
for (const mode of ['Raw', 'Effective']) {
await run.getByRole('button', { name: mode, exact: true }).click();
for (const [role, value] of [['Positive', frozen.positive[mode.toLowerCase()]], ['Negative', frozen.negative[mode.toLowerCase()]]]) expect(await run.getByRole('region', { name: `${role} prompt`, exact: true }).locator('pre').evaluate(node => node.textContent)).toBe(value);
for (const [role, value] of [['Positive', frozen.positive[mode.toLowerCase()]], ['Negative', frozen.negative[mode.toLowerCase()]]]) expect(await run.getByRole('region', { name: 'Frozen run prompt texts', exact: true }).getByRole('region', { name: `${role} prompt`, exact: true }).locator('pre').evaluate(node => node.textContent)).toBe(value);
await page.evaluate(async id => {const { app } = await import('/scripts/app.js');const node = app.graph.getNodeById(id);node.widgets.find(w => w.name === 'text').value = 'Before send';window.chronologyEffects = { queue: 0, load: 0 };window.chronologyOriginals = {};for (const [method, key] of [['queuePrompt', 'queue'], ['loadGraphData', 'load']]) {const original = app[method];window.chronologyOriginals[method] = original;app[method] = function(...args) {window.chronologyEffects[key]++;return original.apply(this, args);};}}, destination);
try {
await run.getByRole('button', { name: 'Send prompt pair', exact: true }).click();const sender = workspace.getByRole('dialog', { name: 'Send to ComfyUI', exact: true });
+13
View File
@@ -130,3 +130,16 @@ test('class names in status lines render as text, never markup',()=>{
assert.equal(f.walk(container,node=>Object.hasOwn(node,'innerHTML')).length,0);
}finally{scope.dispose();f.restore();}
});
test('a context label names nested prompt regions and actions apart from the frozen blocks while the visible heading stays',()=>{
const f=dom(),scope=createScope(),container=new f.Element('div');
try{
module.mountPromptBlocks(container,{parentScope:scope,sender:{open:()=>{}},contextLabel:'Current file',prompts:{positive:record('p'),negative:record('n')}});
const labels=f.walk(container,node=>node.attributes['aria-label']).map(node=>node.attributes['aria-label']);
for(const name of ['Current file positive prompt','Current file negative prompt','Copy current file positive prompt','Send current file positive prompt','Copy current file negative prompt','Send current file negative prompt']) assert.ok(labels.includes(name),name);
assert.equal(labels.filter(name=>/^(Positive|Negative) prompt$/.test(name)).length,0);
assert.deepEqual(f.walk(container,node=>node.tagName==='h3').map(node=>node.textContent),['Positive prompt','Negative prompt']);
const plain=new f.Element('div');module.mountPromptBlocks(plain,{parentScope:scope,sender:{open:()=>{}},prompts:{positive:record('p')}});
assert.ok(f.walk(plain,node=>node.attributes['aria-label']==='Positive prompt').length);
}finally{scope.dispose();f.restore();}
});
+2 -2
View File
@@ -85,7 +85,7 @@ function formatBytes(value) {
return value === null || value === undefined ? null : value >= 1048576 ? `${(value / 1048576).toFixed(1)} MB` : `${Math.max(1, Math.round(value / 1024))} KB`;
}
export function mountFileInspector(container, {sender, parentScope, inline = false, onPreview, onRetry, onRefresh, onInteraction = () => {}}) {
export function mountFileInspector(container, {sender, parentScope, inline = false, promptContext = '', onPreview, onRetry, onRefresh, onInteraction = () => {}}) {
if (parentScope.signal.aborted) return {setState() {}, dispose() {}};
const scope = childScope(parentScope);
let contentScope;
@@ -145,7 +145,7 @@ export function mountFileInspector(container, {sender, parentScope, inline = fal
}
if (chips.childNodes.length) inner.append(chips);
const prompts = el('div'); inner.append(prompts);
const blocks = mountPromptBlocks(prompts, {sender, parentScope:contentScope, includePairAction:false});
const blocks = mountPromptBlocks(prompts, {sender, parentScope:contentScope, includePairAction:false, contextLabel:promptContext});
blocks.setState({prompts:record.prompts}); contentScope.cleanup(blocks.dispose);
if (library?.status === 'linked') {
const vocabulary = el('div', undefined, 'file-vocabulary');
+9 -5
View File
@@ -41,7 +41,11 @@ function childScope(parentScope) {
return scope;
}
export function mountPromptBlocks(container, {prompts, sender, parentScope, roles:visibleRoles = ['positive', 'negative'], onRetry, includePairAction = true}) {
export function mountPromptBlocks(container, {prompts, sender, parentScope, roles:visibleRoles = ['positive', 'negative'], onRetry, includePairAction = true, contextLabel = ''}) {
// A nested set of blocks (the current file inside a recorded run) names its regions and actions with a
// context so assistive technology never meets two identically named prompt regions in one dialog.
const named = role => contextLabel ? `${contextLabel} ${role} prompt` : `${role} prompt`;
const action = role => contextLabel ? `${contextLabel[0].toLowerCase()}${contextLabel.slice(1)} ${role} prompt` : `${role} prompt`;
if (parentScope.signal.aborted) return {update() {}, setState() {}, dispose() {}};
const scope = childScope(parentScope);
container.classList.add('prompt-inspector');
@@ -87,7 +91,7 @@ export function mountPromptBlocks(container, {prompts, sender, parentScope, role
const title = `${role[0].toUpperCase() + role.slice(1)} prompt`;
const record = state.prompts?.[role];
const block = el('section', undefined, `prompt-block prompt-${role}`);
block.setAttribute('aria-label', title);
block.setAttribute('aria-label', contextLabel ? named(role) : title);
const heading = el('div', undefined, 'prompt-block-heading');
heading.append(el('h3', title));
block.append(heading);
@@ -101,12 +105,12 @@ export function mountPromptBlocks(container, {prompts, sender, parentScope, role
block.append(text);
}
const actions = el('div', undefined, 'prompt-actions');
const send = createSendButton(`Send ${role} prompt`);
const send = createSendButton(`Send ${action(role)}`);
send.dataset.sendRole = role;
send.disabled = !actionsEnabled;
const copy = createButton('', 'ca-icon-button');
copy.setAttribute('aria-label', `Copy ${role} prompt`);
copy.title = `Copy ${role} prompt`;
copy.setAttribute('aria-label', `Copy ${action(role)}`);
copy.title = `Copy ${action(role)}`;
copy.append(createWorkspaceIcon('copy'));
copy.dataset.copyRole = role;
copy.disabled = !actionsEnabled;
+1 -1
View File
@@ -68,7 +68,7 @@ export function mountRecordedRun(container, {api, controller, sender, parentScop
const receiptNodes = new Map();
const evidenceValues = new Map();
const blocks = mountPromptBlocks(prompts, {sender, parentScope:local});
const inspector = mountFileInspector(inspectorContainer, {sender, parentScope:local, inline:true, onRetry:() => inspection.retry(), onPreview:() => showViewer(), onRefresh:refreshCurrent});
const inspector = mountFileInspector(inspectorContainer, {sender, parentScope:local, inline:true, promptContext:'Current file', onRetry:() => inspection.retry(), onPreview:() => showViewer(), onRefresh:refreshCurrent});
function selectedReceipt() {
const selected = latest.selection.receipt;
return selected && latest.outputs.items.find(item => occurrenceKey(item.occurrence) === occurrenceKey(selected));