fix(history): let the cover ask for JPEG XL when this browser decodes it

Firefox can decode JPEG XL without listing image/jxl in Accept. When the
workspace's probe decodes JPEG XL, it adds jxl=1 to the History cover URL.
The server serves a .jxl preview when that flag is set or when Accept
lists image/jxl. In every other case it falls back as before. The flag
accepts only 0 or 1, and the response stays no-store.
This commit is contained in:
Vito Sansevero
2026-10-24 16:02:43 -07:00
parent 7c82fdb180
commit af0482a12d
5 changed files with 71 additions and 5 deletions
+4 -2
View File
@@ -2360,8 +2360,10 @@ Thumbnail format and settings (#317, `py/api/thumbnail_settings.py`,
preview `<img>` that fails to load falls back to the original (the legacy gallery
already does). A video whose preview it cannot show keeps its own element (Images) or
its "Video preview" label (Library), never a broken image. History's cover URL names
no format, so the server serves a `.jxl` preview only when the request's `Accept`
lists `image/jxl`; otherwise the file's preview in another format, else the original
no format, so the server serves a `.jxl` preview only when the URL carries `jxl=1`
(added by the workspace where its probe decoded JPEG XL: Firefox may not list it in
Accept; any value but `0` or `1` is a 400) or the request's `Accept` lists
`image/jxl`; otherwise the file's preview in another format, else the original
image or a video's placeholder. Appearance says when this browser cannot show JPEG XL thumbnails.
Overview (`system-overview.mjs`) reads `GET /prompt_manager/system/overview` once per
+13 -1
View File
@@ -143,6 +143,18 @@ def _query(request: web.Request) -> None:
raise ValueError("Duplicate history query parameter")
def _jxl_flag(request: web.Request) -> bool:
"""The cover's ``jxl`` flag (#317): ``1`` when the workspace decoded a
JPEG XL image (a browser may not list ``image/jxl`` in Accept), ``0`` or
absent otherwise; anything else is a bad request."""
value = request.query.get("jxl")
if value is None:
return False
if value not in ("0", "1"):
raise ValueError("Invalid jxl flag")
return value == "1"
def _page_limit(request: web.Request) -> int:
limit = _decimal(request.query.get("limit", "40"), minimum=1)
if limit > 100:
@@ -795,7 +807,7 @@ class HistoryRoutesMixin:
media_options = {
"ensure_preview": lambda *preview: needed.append(preview),
"placeholder": self._video_placeholder_bytes(),
"accepts_jxl": accepts_jxl(request),
"accepts_jxl": _jxl_flag(request) or accepts_jxl(request),
}
prepared = await execute()
if needed:
+19
View File
@@ -40,3 +40,22 @@ test('stale inspection echoes use the canonical recovery vocabulary and identity
}
});
test('the recorded output inspector cover is its preview, not the original (#249)',()=>{const a=adapter(),target=media('2',1);const record=a.toRecord(envelope(target),target,extractGenerationFacts);assert.deepEqual(record.preview,{url:target.thumbnail_url});assert.notEqual(record.preview.url,record.download.url);});
// #317 M3 follow-up: Firefox may decode JPEG XL without listing image/jxl in Accept, so the cover URL says so itself.
test('the recorded output cover asks for JPEG XL only where this browser decodes it',async()=>{
const support=await import('../../web/js/workspace/thumbnail-support.mjs');
const a=adapter(),target=media('2',1);
const probe=ok=>({createImage:()=>({set src(_){queueMicrotask(()=>ok?this.onload():this.onerror());}})});
try {
support.resetJxlSupport();
assert.equal(a.toRecord(envelope(target),target,extractGenerationFacts).preview.url,target.thumbnail_url,'unknown: no flag');
await support.detectJxlSupport(probe(true));
const flagged=new URL(a.toRecord(envelope(target),target,extractGenerationFacts).preview.url,'http://localhost/');
assert.equal(flagged.searchParams.get('jxl'),'1');
assert.equal(flagged.searchParams.getAll('jxl').length,1);
assert.equal(`${flagged.pathname}?${new URLSearchParams([...flagged.searchParams].filter(([k])=>k!=='jxl'))}`,target.thumbnail_url);
support.resetJxlSupport();
await support.detectJxlSupport(probe(false));
assert.equal(a.toRecord(envelope(target),target,extractGenerationFacts).preview.url,target.thumbnail_url,'no support: no flag');
} finally {support.resetJxlSupport();}
});
+30
View File
@@ -1238,6 +1238,36 @@ class HistoryOutputAPITests(ImageAPITestCase):
self.assertEqual(response.headers["Content-Type"], "image/jxl")
self.assertEqual(body, jxl.read_bytes())
async def test_the_jxl_flag_selects_the_jxl_cover_without_accept(self):
# M3 follow-up: a browser that decodes JPEG XL but does not list it in
# Accept (Firefox) says so with jxl=1 on the cover URL.
make_png(self.output_dir / "image.png")
folder = self.output_dir / "thumbnails"
folder.mkdir(exist_ok=True)
jxl = folder / "image_thumb.jxl"
jxl.write_bytes(b"\xff\x0a jxl preview")
start = self.capture([self.ref()])
descriptor = await self.media(start)
from py.api import thumbnail_encode
from py.config import PromptManagerConfig
with (
patch.object(PromptManagerConfig, "THUMBNAIL_FORMAT", "jxl"),
patch.object(thumbnail_encode, "detect_jxl_encoder", return_value="cjxl"),
):
url = descriptor["thumbnail_url"]
response = await self.current(url + "&jxl=1", headers={"Accept": "*/*"})
self.assertEqual(response.headers["Content-Type"], "image/jxl")
self.assertEqual(response.headers["Cache-Control"], "no-store")
self.assertEqual(await response.read(), jxl.read_bytes())
response = await self.current(url + "&jxl=0", headers={"Accept": "*/*"})
self.assertEqual(response.headers["Content-Type"], "image/png")
await response.read()
for bad in ("&jxl=true", "&jxl=", "&jxl=1&jxl=1", "&jxl=2"):
response = await self.current(url + bad)
self.assertEqual(response.status, 400, bad)
await response.read()
async def test_a_video_jxl_cover_is_the_placeholder_without_jxl_support(self):
(self.output_dir / "clip.webm").write_bytes(b"AV1 stream no decoder here")
response, body, _jxl = await self._jxl_cover("clip.webm", "image/*,*/*")
+5 -2
View File
@@ -1,4 +1,5 @@
import {projectInspectionPayload} from './inspection-state.mjs';
import {jxlSupport} from './thumbnail-support.mjs';
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
const uuid = value => typeof value === 'string' && /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/.test(value);
@@ -65,8 +66,10 @@ export function toHistoryInspectionRecord(result, target, extractFacts) {
const identity = {kind:'reported-location', key:target.location_key, path:target.reference.path_style === 'windows' ? path.replaceAll('\\', '/') : path,
occurrence:{...target.occurrence}, history_epoch:target.history_epoch, current_scope:target.current_scope, reference:{...target.reference}};
const record = projectInspectionPayload(result, identity, {url:urlFor(target, 'url', 'media'), filename:target.filename}, extractFacts);
// #249: the inspector's cover is the recorded output's preview (made by the server on first view).
record.preview = {url:urlFor(target, 'thumbnail_url', 'thumbnail')};
// #249: the inspector's cover is the recorded output's preview (made by the server on first view). #317: its URL
// names no format, so where this browser decoded JPEG XL it says so (`jxl=1`); the server also reads Accept.
const cover = urlFor(target, 'thumbnail_url', 'thumbnail');
record.preview = {url:jxlSupport() === true ? `${cover}&jxl=1` : cover};
return record;
}
export const reportedLocationAdapter = Object.freeze({