PosterWall: fetch posters through poster-fetch.js

PosterWall carried its own copy of the poster fetch, so the fallback-page
guard added to usePosterPrints did not reach it: a missing .webp answered
200 text/html by a host was still handed to the print dialog as the image.
It now uses fetchPosterRaster and fetchPosterSource. A thrown .webp fetch
is still a miss that falls through to the source, and a missing source
still reads "poster not found (<status>)".
This commit is contained in:
Doxa Maps 2026-09-29 04:05:09 +00:00
parent 4fc4b8e9c8
commit 33299253b0

View file

@ -46,6 +46,7 @@ import { tStatic } from '../../i18n/index.js'
// custom elements that install no i18n plugin, and an unguarded useI18n() throws on the way in.
const T = (key, en) => tStatic(key) || en
import { useShadowStyles } from '@map/composables/useShadowStyles.js'
import { fetchPosterRaster, fetchPosterSource } from '@map/packet/poster-fetch.js'
import { usePosterPrints } from '@map/composables/usePosterPrints.js'
import { useOptionalI18n, useOptionalT } from '@map/composables/useOptionalT.js'
// Export-modal geometry. A fixed;inset:0 scrim covers only the CONTENT PANE in the cap-5 shell
@ -387,10 +388,10 @@ async function exportPoster(item) {
// which aborts every .webp at the network layer to force the default-drop
// condition, went from PASS on the old wall to FAIL here with 0 .html requests.
// The wall this component was extracted from already guarded it; the extraction
// did not carry the guard across.
let res = null
try { res = await fetch(`${base.value}/posters/${item.id}.webp`) } catch { /* treated as a miss */ }
if (!res || !res.ok) {
// did not carry the guard across. The guard now lives in poster-fetch.js, which also
// takes a host's 200 fallback page (text/html for a missing .webp) as a miss.
const raster = await fetchPosterRaster(`${base.value}/posters/${item.id}.webp`)
if (!raster) {
// ★ NO PRE-RENDERED IMAGE? GENERATE IT. The 378 .webp posters were 41 MB in
// every clone, shipped to every reader whether or not they opened one — and a
// publish was already rejected once at a 74 MB asset tree. The poster HTML is
@ -400,10 +401,10 @@ async function exportPoster(item) {
// ship nothing. A 404 used to end here as "poster not found".
exportState.value = { ...exportState.value, phase: 'render',
message: 'generating the poster', pct: 45 }
const htmlRes = await fetch(`${base.value}/posters/${item.id}.html`)
if (!htmlRes.ok) throw new Error(`poster not found (${htmlRes.status})`)
const source = await fetchPosterSource(`${base.value}/posters/${item.id}.html`)
if (!source.html) throw new Error(`poster not found (${source.status})`)
const { rasterPoster, looksRendered } = await import('@map/packet/poster-raster.js')
const blob = await rasterPoster(await htmlRes.text(), { scale: 2 })
const blob = await rasterPoster(source.html, { scale: 2 })
// Fail LOUDLY rather than hand over a black rectangle: an SVG in an <img> is
// an isolated document, so a missing stylesheet makes every fill fall back to
// black with no error at all.
@ -411,7 +412,7 @@ async function exportPoster(item) {
await deliverPoster(blob, item)
return
}
await deliverPoster(await res.blob(), item)
await deliverPoster(raster, item)
} catch (e) {
exportState.value = { label: item.label, kind: 'Poster', phase: 'error',
message: String(e?.message || e).slice(0, 140), pct: 0 }