Compare commits

...
Author SHA1 Message Date
Vito Sansevero 5ec3178195 Merge remote-tracking branch 'origin/main' into fix/398-select-width 2026-10-27 18:19:27 -07:00
Vito Sansevero 00d31614e8 fix(system): bound Existing tags by its row at phone width (#398 review) 2026-10-27 17:54:08 -07:00
Vito Sansevero c3ef047341 fix(system): Tagging selects show their whole chosen option at 390 px (#398)
The concept's phone cap (120px) cut Existing tags ("Replace after review").
#tag-existing now never shrinks for its row text and keeps its natural width
at phone width, beside the text. The comparison expects selectsFit for every
select, and a new test walks every option of both selects at 390 and 320 px
in both themes. TESTING.md D132 records the decision.
2026-10-27 17:42:13 -07:00
3 changed files with 44 additions and 22 deletions
+10 -6
View File
@@ -3200,12 +3200,16 @@ the per-section hand-off panel, was retired by S12 (#329): every section is nati
again"; an Unload that found nothing loaded only reads the status again. At phone width again"; an Unload that found nothing loaded only reads the status again. At phone width
the concept's 120px select cap cuts every engine label, so `#tag-model` alone keeps its the concept's 120px select cap cuts every engine label, so `#tag-model` alone keeps its
natural width there and its row wraps the select under the text (`workspace.css` natural width there and its row wraps the select under the text (`workspace.css`
`/* D132 */`); Existing tags stays as the concept draws it, cut at 390 as there. The `/* D132 */`). The concept's cap cuts Existing tags too ("Replace after review"); by the
comparison maps `selectsFit` only for the engine select and measures each row's maintainer's decision (#398) that parity is not kept: `#tag-existing` never shrinks for
`flexWrap` and whether its control sits under the text. Pinned by `auto-tagging matches its row text and at phone width keeps its natural width beside the text (its row does
the concept` (both themes and sizes), `auto-tagging at 390 wraps only the engine row`, not wrap; the text column narrows). 1600 is as the concept draws it. The comparison
`auto-tagging engine select fits at 320 wide` and the mutation check (it includes a expects `selectsFit` true for every select and measures each row's `flexWrap` and
wrapped row) (system-tagging-concept.spec), and the model, Download, Unload and Preview whether its control sits under the text. Pinned by `auto-tagging matches the concept`
(both themes and sizes), `auto-tagging at 390 wraps only the engine row`, `auto-tagging
selects fit every option at 390/320 wide` (every option of both selects, both themes, no
page scroll) and the mutation check (a wrapped row, a squeezed engine and Existing tags
select) (system-tagging-concept.spec), and the model, Download, Unload and Preview
tests of workspace-system-tagging.spec. tests of workspace-system-tagging.spec.
- **D133** (Auto-tagging, S11): a "Suggestions per image" row (`#tag-suggestions`, - **D133** (Auto-tagging, S11): a "Suggestions per image" row (`#tag-suggestions`,
number 1–100, host key `autotag_suggestion_limit`) follows the two confidence rows: number 1–100, host key `autotag_suggestion_limit`) follows the two confidence rows:
+30 -15
View File
@@ -127,9 +127,9 @@ function mapTaggingConcept(reference, { template, phone }) {
actions: { ...reference.actions, buttons: [preview, ['Download again', ''], ['Unload', '']] }, actions: { ...reference.actions, buttons: [preview, ['Download again', ''], ['Unload', '']] },
// D134: the prompt-tag batch, reviewed first. // D134: the prompt-tag batch, reviewed first.
operations: [{ tree: ['div', 'div.row-actions'], title: 'Tag output images', description: BATCH, actions: [['Review', '']] }], operations: [{ tree: ['div', 'div.row-actions'], title: 'Tag output images', description: BATCH, actions: [['Review', '']] }],
// D132: the engine select shows its whole chosen option at every width (at 390 the concept's 120px cap cuts it; // D132 (#398): every select shows its whole chosen option at every width (at 390 the concept's 120px cap cuts
// `#tag-model` alone may grow there). Existing tags keeps the concept's own fit, cut or not. // them; production lets each grow past it).
selectsFit: reference.selectsFit.map((fit, index) => (index === 0 ? true : fit)), selectsFit: reference.selectsFit.map(() => true),
}; };
} }
@@ -168,16 +168,28 @@ for (const theme of THEMES) for (const [width, height] of SIZES) {
}); });
} }
// D132 below the concept's smallest size: on a 320px phone the engine select still shows its whole option. // D132 (#398) at phone width and below the concept's smallest size: every option of every select, once chosen, shows
for (const theme of THEMES) test(`auto-tagging engine select fits at 320 wide ${theme}`, async ({ page }) => { // whole, with no page scroll.
await page.setViewportSize({ width: 320, height: 700 }); for (const theme of THEMES) for (const width of [390, 320]) {
const production = await productionTagging(page, theme); test(`auto-tagging selects fit every option at ${width} wide ${theme}`, async ({ page }, testInfo) => {
for (const model of ['hf', 'gguf', 'wd14-swinv2']) { await page.setViewportSize({ width, height: 844 });
await page.locator('#workspace-main #tag-model').selectOption(model); await productionTagging(page, theme);
expect((await page.evaluate(measureTagging, '#workspace-main')).selectsFit[0], model).toBe(true); await page.locator('#workspace-main #tag-existing').selectOption('replace_after_review');
} await page.locator('#workspace-main section.surface').screenshot({ path: testInfo.outputPath(`production-tagging-selects-${theme}-${width}.png`) });
expect(production.pageScroll).toBe(false); const selects = page.locator('#workspace-main section.surface > .setting-row > select');
}); await expect(selects).toHaveCount(2);
for (let index = 0; index < 2; index += 1) {
const select = selects.nth(index);
const values = await select.evaluate(node => [...node.options].map(option => option.value));
for (const value of values) {
await select.selectOption(value);
const measured = await page.evaluate(measureTagging, '#workspace-main');
expect(measured.selectsFit, `${await select.getAttribute('id')}=${value}`).toEqual([true, true]);
expect(measured.pageScroll, value).toBe(false);
}
}
});
}
test('auto-tagging comparison detects mutations', async ({ page, request }) => { test('auto-tagging comparison detects mutations', async ({ page, request }) => {
await page.setViewportSize({ width: 1600, height: 1100 }); await page.setViewportSize({ width: 1600, height: 1100 });
@@ -222,6 +234,9 @@ test('auto-tagging comparison detects mutations', async ({ page, request }) => {
const undoSqueeze = await mutate(page, { css: '#workspace-main #tag-model { max-width: 60px !important; min-width: 0 !important; }' }); const undoSqueeze = await mutate(page, { css: '#workspace-main #tag-model { max-width: 60px !important; min-width: 0 !important; }' });
expect(await paths()).toEqual(expect.arrayContaining(['selectsFit.0'])); expect(await paths()).toEqual(expect.arrayContaining(['selectsFit.0']));
await undoSqueeze(); await undoSqueeze();
const undoSqueezeExisting = await mutate(page, { css: '#workspace-main #tag-existing { max-width: 60px !important; min-width: 0 !important; }' });
expect(await paths()).toEqual(expect.arrayContaining(['selectsFit.1']));
await undoSqueezeExisting();
// A row that wraps where the concept's does not. // A row that wraps where the concept's does not.
const undoWrap = await mutate(page, { css: '#workspace-main .setting-row:has(> #tag-existing) { flex-wrap: wrap; }' }); const undoWrap = await mutate(page, { css: '#workspace-main .setting-row:has(> #tag-existing) { flex-wrap: wrap; }' });
expect(await paths()).toEqual(['rows.1.flexWrap']); expect(await paths()).toEqual(['rows.1.flexWrap']);
@@ -233,14 +248,14 @@ test('auto-tagging comparison detects mutations', async ({ page, request }) => {
expect(diffInventory(expected, await measure())).toEqual([]); expect(diffInventory(expected, await measure())).toEqual([]);
}); });
// D132 at phone width, unmapped: only the engine row wraps; Existing tags keeps the concept's cut select. // D132 at phone width: only the engine row wraps; Existing tags grows beside its text instead (#398).
test('auto-tagging at 390 wraps only the engine row', async ({ page, request }) => { test('auto-tagging at 390 wraps only the engine row', async ({ page, request }) => {
await page.setViewportSize({ width: 390, height: 844 }); await page.setViewportSize({ width: 390, height: 844 });
const concept = await conceptTagging(page, 'dark'); const concept = await conceptTagging(page, 'dark');
const production = await productionTagging(page, 'dark'); const production = await productionTagging(page, 'dark');
expect(diffInventory(mapTaggingConcept(concept, await savedTemplate(request, page)), production)).toEqual([]); expect(diffInventory(mapTaggingConcept(concept, await savedTemplate(request, page)), production)).toEqual([]);
expect(production.rows.map(row => row.flexWrap)).toEqual(['wrap', 'nowrap', 'nowrap', 'nowrap', 'nowrap']); expect(production.rows.map(row => row.flexWrap)).toEqual(['wrap', 'nowrap', 'nowrap', 'nowrap', 'nowrap']);
expect(production.selectsFit[1]).toBe(concept.selectsFit[1]); expect(production.selectsFit).toEqual([true, true]);
}); });
/** Runs in the page: the open review dialog: its title, body and the concept's tokens. */ /** Runs in the page: the open review dialog: its title, body and the concept's tokens. */
+4 -1
View File
@@ -433,10 +433,13 @@ pre.prompt-text { font: inherit; font-size: 12px; line-height: 1.75; margin-bloc
} }
/* D132: at phone width the concept's 120px cap cuts every engine label ("WD14 SwinV2 · ~400 MB"); the Tagging model /* D132: at phone width the concept's 120px cap cuts every engine label ("WD14 SwinV2 · ~400 MB"); the Tagging model
select alone keeps its natural width there (its widest engine label) and, with no room beside the row text, wraps select alone keeps its natural width there (its widest engine label) and, with no room beside the row text, wraps
under it. */ under it. Existing tags (#398) never shrinks for its row text and, past the cap, keeps its natural width beside the
text ("Replace after review"), bounded by its row like the engine select. */
.setting-row #tag-existing { flex-shrink: 0; /* D132 */ }
@media (max-width: 700px) { @media (max-width: 700px) {
#workspace-main .setting-row:has(> #tag-model) { flex-wrap: wrap; /* D132 */ } #workspace-main .setting-row:has(> #tag-model) { flex-wrap: wrap; /* D132 */ }
#workspace-main .setting-row #tag-model { flex-shrink: 0; max-width: 100% !important; /* D132 */ } #workspace-main .setting-row #tag-model { flex-shrink: 0; max-width: 100% !important; /* D132 */ }
#workspace-main .setting-row #tag-existing { max-width: 100% !important; /* D132 */ }
} }
/* D109: System › Diagnostics & logs' added Lines field sits in the concept's toolbar like its Level select, and a value /* D109: System › Diagnostics & logs' added Lines field sits in the concept's toolbar like its Level select, and a value