Skip to content

fix(images): make the fallbackFormat rule per-source-format - #60

Closed
dmurko wants to merge 2 commits into
mainfrom
fix/fallbackformat-per-source-format
Closed

fix(images): make the fallbackFormat rule per-source-format#60
dmurko wants to merge 2 commits into
mainfrom
fix/fallbackformat-per-source-format

Conversation

@dmurko

@dmurko dmurko commented Aug 4, 2026

Copy link
Copy Markdown
Member

The scaffold said "Do NOT set fallbackFormat=\"webp\"" unconditionally. That rule generalizes from a .png repro and is wrong for .webp sources, where following it ships a multi-megabyte PNG fallback.

Ground truth

node_modules/astro/components/Picture.astro — verified in both astro@6.4.8 (the scaffold's pin) and astro@7.1.3:

let resultFallbackFormat = fallbackFormat ?? defaultFallbackFormat; // 'png'
if (!fallbackFormat && isESMImportedImage(clonedSrc) &&
    specialFormatsFallback.includes(clonedSrc.format)) {            // ['gif','svg','jpg','jpeg']
  resultFallbackFormat = clonedSrc.format;
}

specialFormatsFallback is ['gif','svg','jpg','jpeg']. webp is not on it, so a .webp source with no fallbackFormat falls through to defaultFallbackFormat = 'png' and silently transcodes the <img> fallback to PNG — for photographic content routinely several megabytes, larger than the source it replaced.

The corrected rule

Source fallbackFormat Why
.jpg / .jpeg / .gif / .svg omit already fall back to themselves
.png omit setting it → no output format matches the source → build fails with ENOENT … dist/_astro/<name>.png
.webp set fallbackFormat="webp" otherwise the fallback becomes PNG

scaffold-sync contradicted itself

The 0.1.2 migration-note sample output told users to apply formats={['webp']} fallbackFormat="webp" — which the "Manual edits" section ~160 lines later forbids outright. A site following the printed output got the build-breaking combination on .png sources. Both now state the per-source-format rule.

Enforcement, not just documentation

Added the check to skills/code-review, keyed to the source file's extension rather than flagged blanket. A rule that lives only in CLAUDE.md is how the image config drifted in the first place — the imageService: "compile" recommendation survived there for months after the config moved to "custom".

Also: corrected a clamping claim

The right-sizing section said only widths are clamped, which implies an oversized width prop upscales into a bigger, softer file. It does not — every resize() branch in astro/dist/assets/services/sharp.js passes withoutEnlargement: true. Nothing upscales, so a soft image always means the source master is too small, and the fix is a bigger source rather than a bigger number.

Scope

Docs-only — no scaffold file or plugin version change, following 4078700's precedent for the analogous stale-guidance fix. Verified the scaffold still builds clean and emits real WebP at 320w/640w.

Found while applying #57 to site-sarifgroup (site-sarifgroup#7).

🤖 Generated with Claude Code

dmurko and others added 2 commits August 4, 2026 14:59
The scaffold said "**Do NOT set `fallbackFormat="webp"`**" unconditionally.
That rule generalizes from a `.png` repro and is wrong for `.webp` sources.

Ground truth, `node_modules/astro/components/Picture.astro` (verified in
both astro@6.4.8 and astro@7.1.3):

    let resultFallbackFormat = fallbackFormat ?? defaultFallbackFormat; // 'png'
    if (!fallbackFormat && isESMImportedImage(clonedSrc) &&
        specialFormatsFallback.includes(clonedSrc.format)) {
      resultFallbackFormat = clonedSrc.format;
    }

`specialFormatsFallback` is `['gif','svg','jpg','jpeg']`. **`webp` is not
on it**, so a `.webp` source with no `fallbackFormat` silently transcodes
its `<img>` fallback to PNG — for photographic content routinely several
megabytes, larger than the source it replaced. Following the scaffold's
blanket rule is what causes that.

So the rule is per-source-format:
- `.jpg`/`.jpeg`/`.gif`/`.svg` -> omit (they fall back to themselves)
- `.png`                       -> omit (setting it fails the build with
                                  ENOENT dist/_astro/<name>.png)
- `.webp`                      -> SET fallbackFormat="webp"

`skills/scaffold-sync/SKILL.md` also contradicted itself: the 0.1.2
migration-note sample output told users to apply
`formats={['webp']} fallbackFormat="webp"`, which the "Manual edits"
section two hundred lines later forbids outright. Both now state the
per-source-format rule.

Added the check to `skills/code-review` so it is enforced rather than
only documented — a rule that lives solely in CLAUDE.md is how the image
config drifted before.

Also corrected the right-sizing section's clamping claim. It said only
`widths` are clamped, which implies an oversized `width` upscales. It
does not: every `resize()` branch in
`astro/dist/assets/services/sharp.js` passes `withoutEnlargement: true`.
Nothing upscales, so a soft image means the source master is too small.

Docs-only; no scaffold file or version change (follows 4078700).
Verified the scaffold still builds and emits real WebP at 320w/640w.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant