fix(tagging): keep focus on the instructions select after a pick (#431)

A pick re-renders the dialog's fields; the select it was made with gets
focus back. The one-off note describes the textarea while it shows.
This commit is contained in:
Vito Sansevero
2026-10-31 18:52:23 -07:00
parent 4ff55e155e
commit 8520a10bd8
3 changed files with 14 additions and 4 deletions
+6 -2
View File
@@ -4,7 +4,7 @@
import { startCoverage, saveCoverage } from '../../tools/vendor/browser-coverage.mjs';
import { test, expect } from './fixtures.mjs';
import { saveImageTags } from './concept-inventory.mjs';
import { taggingInventory } from './tagging-canonical-comparison.mjs';
import { FIXTURE_INSTRUCTIONS_ID, taggingInventory } from './tagging-canonical-comparison.mjs';
const images = '/prompt_manager/workspace#/images';
const MODULE = '/prompt_manager/js/workspace/image-tagging.mjs';
@@ -878,9 +878,13 @@ test('the saved instructions picker fills, edits one-off and resets per engine (
await expect(pick).toHaveAttribute('id', 'image-tag-instructions-pick');
await expect(text).toHaveValue('Describe the image');
await expect(note).toBeHidden();
// A keyboard pick (the select focused) keeps focus on the select the fields are re-rendered with.
await pick.focus();
await pick.selectOption('builtin');
await expect(text).toHaveValue(builtin);
await expect(pick).toBeFocused();
await text.fill('Only the sky.');
await expect(text).toHaveAttribute('aria-describedby', 'image-tag-instructions-pick-note');
await expect(pick.locator('option:checked')).toHaveText('Edited for this run');
await expect(pick.locator('option[value="edited"]')).toBeDisabled();
await expect(note).toHaveText('Edits apply to this run only. Manage saved instructions in System › Auto-tagging.');
@@ -896,7 +900,7 @@ test('the saved instructions picker fills, edits one-off and resets per engine (
await text.fill('Edited again.');
await model(page).selectOption('hf');
await expect(text).toHaveValue('Describe the image');
await expect(pick).toHaveValue('d35c71be0a1e');
await expect(pick).toHaveValue(FIXTURE_INSTRUCTIONS_ID);
await model(page).selectOption('wd14-vit');
await expect(pick).toHaveCount(0);
await expect(text).toHaveCount(0);
-2
View File
@@ -243,8 +243,6 @@ async function readDefaults(api, signal) {
defaults: {
general_threshold: unit(value.wd14_general_threshold) ? value.wd14_general_threshold : DEFAULTS.general_threshold,
character_threshold: unit(value.wd14_character_threshold) ? value.wd14_character_threshold : DEFAULTS.character_threshold,
// #431: the instructions come from the saved entries (`choices`), not `default_prompt`.
instructions: DEFAULTS.instructions,
suggestion_limit: limit(value.suggestion_limit) ? value.suggestion_limit : DEFAULTS.suggestion_limit,
},
models: object(value.models) ? value.models : null,
+8
View File
@@ -150,6 +150,10 @@ export function renderModelPanel({scope, controller, heading = 'Auto-tag this im
}
pick.value = state.instructionsEdited ? EDITED_VALUE : state.instructionId;
editedNote.hidden = !state.instructionsEdited;
// The note describes the textarea while it shows.
const area = inputs.get('instructions');
if (state.instructionsEdited) area?.setAttribute('aria-describedby', editedNote.id);
else area?.removeAttribute('aria-describedby');
}
function renderOptions(state) {
@@ -174,6 +178,7 @@ export function renderModelPanel({scope, controller, heading = 'Auto-tag this im
inputs.set('instructions', area);
rows.push(field('Tagging instructions', area, OPTION_IDS.instructions));
editedNote = el('p', EDITED_NOTE, 'small instructions-edited');
editedNote.id = `${pickId}-note`;
rows.push(editedNote);
fillPick(state);
}
@@ -195,7 +200,10 @@ export function renderModelPanel({scope, controller, heading = 'Auto-tag this im
shownModel = state.model;
shownOptions = state.optionsRevision;
select.value = state.model;
// A pick re-renders the fields; keyboard focus stays on the select it was made with.
const picking = pick !== null && document.activeElement === pick;
renderOptions(state);
if (picking) pick?.focus();
} else if (pick && state.instructionsEdited !== shownEdited) {
// Typing never re-renders the fields (I1): only the select and the note follow an edit.
fillPick(state);