fix(thumbnails): say when the thumbnail settings could not be read

Like Appearance's other server rows, the Thumbnails rows show 'Server
settings could not be read.' while the read failed, instead of only
staying disabled. Firefox exposed the gap: its JPEG XL note arrived
first and changed the row notes the page workflow counted.
This commit is contained in:
Vito Sansevero
2026-10-24 14:08:12 -07:00
parent 2d0e962f1d
commit 2ed085f8d7
3 changed files with 29 additions and 13 deletions
+5 -3
View File
@@ -650,14 +650,16 @@ test('server settings that cannot be read say so in their rows', async ({ page }
? route.fulfill({ status: 500, contentType: 'application/json', body: JSON.stringify({ success: false, error: 'Injected failure' }) })
: route.continue()));
await page.goto(`${workspace}#/system/appearance`);
for (const id of ['open-mode', 'timeout']) {
// #317: the Thumbnails rows are server settings too.
for (const id of ['open-mode', 'timeout', 'thumbnail-format', 'thumbnail-quality', 'thumbnail-edge']) {
const row = page.locator('#workspace-main .setting-row').filter({ has: page.locator(`#${id}`) });
await expect(row.locator('p.small')).toHaveText('Server settings could not be read.');
await expect(row.locator('p.small').first()).toHaveText('Server settings could not be read.');
await expect(page.locator(`#${id}`)).toBeDisabled();
}
// The browser rows are this browser's, and work as before.
await expect(page.locator('#default-view')).toBeEnabled();
await expect(page.locator('#workspace-main .setting-row p.small')).toHaveCount(2);
await expect(page.locator('#workspace-main .setting-row p.small', { hasText: 'Server settings could not be read.' })).toHaveCount(5);
await expect(page.locator('#workspace-main section.surface').first().locator('.setting-row p.small')).toHaveCount(2);
});
test('stale revision shows the conflict status', async ({ page, request }) => {
+14 -2
View File
@@ -67,7 +67,7 @@ const HOST = { thumbnail_format: 'webp', thumbnail_quality: 80, thumbnail_edge:
const NO_JXL = { webp: { available: true }, jpeg: { available: true },
jxl: { available: false, encoder: null, reason: 'No JPEG XL encoder found. Install pillow-jxl-plugin.' } };
async function mounted({ host = HOST, browserJxl = true, writeHost = async () => ({ success: true, persisted: true, revision: 'sha256:two' }) } = {}) {
async function mounted({ host = HOST, browserJxl = true, hostFails = false, writeHost = async () => ({ success: true, persisted: true, revision: 'sha256:two' }) } = {}) {
const { mountThumbnailSettings } = await thumbnailsModule;
const { createSystemDrafts } = await stateModule;
const { createScope } = await coreModule;
@@ -75,7 +75,7 @@ async function mounted({ host = HOST, browserJxl = true, writeHost = async () =>
let hostValues = null;
const drafts = createSystemDrafts({
readBrowser: () => ({}), readRuntime: () => ({}),
readHost: async () => ({ values: hostValues, revision: 'sha256:one' }),
readHost: async () => { if (hostFails) throw new Error('offline'); return { values: hostValues, revision: 'sha256:one' }; },
writeBrowser: async () => ({ persisted: true }), writeHost, writeRuntime: async () => ({}),
});
const scope = createScope();
@@ -181,3 +181,15 @@ test('Convert outdated opens Thumbnail maintenance on its convert scope; a forma
assert.equal(reviews.length, 1, 'nothing starts by itself');
} finally { scope.dispose(); assert.equal(dom.listenerCount(), 0); dom.restore(); }
});
test('rows whose server settings could not be read say so, like Appearance’s host rows', async () => {
const { HOST_UNAVAILABLE } = await import('../../web/js/workspace/system-appearance.mjs');
const { dom, drafts, scope, surface, byId } = await mounted({ hostFails: true });
const notes = () => surface.children.slice(2, 5).map(row => row.children[0].children.slice(2).map(note => note.textContent));
try {
assert.deepEqual(notes(), [[], [], []]);
assert.equal(await drafts.reloadHost(), false);
assert.deepEqual(notes(), [[HOST_UNAVAILABLE], [HOST_UNAVAILABLE], [HOST_UNAVAILABLE]]);
for (const id of ['thumbnail-format', 'thumbnail-quality', 'thumbnail-edge']) assert.equal(byId(id).disabled, true, id);
} finally { scope.dispose(); assert.equal(dom.listenerCount(), 0); dom.restore(); }
});
+10 -8
View File
@@ -14,11 +14,13 @@
* automatic. After a save that changed the format, its line says existing previews keep their old format until
* converted.
*
* Host rows stay disabled until the server settings were read, like Appearance's.
* Host rows stay disabled until the server settings were read, like Appearance's, and say "Server settings could not
* be read." while a read failed.
*/
import { settingRow, operationRow } from './system-controls.mjs';
import { detectJxlSupport } from './thumbnail-support.mjs';
import { openThumbnailReview } from './thumbnail-review.mjs';
import { HOST_UNAVAILABLE } from './system-appearance.mjs';
export const FORMAT_OPTIONS = Object.freeze([['webp', 'WebP'], ['jxl', 'JPEG XL'], ['jpeg', 'JPEG']]);
export const QUALITY_LIMIT = Object.freeze({ min: 1, max: 100 });
@@ -84,7 +86,6 @@ export function mountThumbnailSettings(container, { scope, drafts, api, activity
let browserJxl;
let savedFormat;
const format = controls[0];
const notes = [];
const repaint = () => {
const encoders = drafts.get('host', 'thumbnail_encoders');
for (const { input, key } of controls) {
@@ -95,12 +96,13 @@ export function mountThumbnailSettings(container, { scope, drafts, api, activity
const jxl = Array.from(format.input.children).find(option => option.value === 'jxl');
const unavailable = encoders?.jxl?.available === false;
if (jxl) jxl.disabled = unavailable;
for (const note of notes.splice(0)) note.remove();
const lines = [unavailable ? encoders.jxl.reason : null, browserJxl === false ? BROWSER_NO_JXL : null];
for (const line of lines.filter(Boolean)) {
const note = el('p', line, 'small');
format.text.append(note);
notes.push(note);
const failed = drafts.hostState?.() === 'failed';
for (const row of controls) {
for (const note of row.notes ?? []) note.remove();
const lines = [failed && drafts.get('host', row.key) === undefined ? HOST_UNAVAILABLE : null];
if (row === format) lines.push(unavailable ? encoders.jxl.reason : null, browserJxl === false ? BROWSER_NO_JXL : null);
row.notes = lines.filter(Boolean).map(line => el('p', line, 'small'));
row.text.append(...row.notes);
}
// A format save (not the first read) leaves the existing previews outdated: say so, never convert.
const saved = drafts.saved('host').thumbnail_format;