generate sized derivatives ahead of the request

A pass over the content at startup writes three widths per picture into a cache
outside the site root, named by the source's content hash and the width (ADR-0042).
Idempotent by construction: a rerun stats and skips, an edited picture takes a new
name, and nothing stale can be served under an old one. Restarting the evidence
site made 0 derivatives the second time, as it should.

Ahead of the request rather than during it, because resampling is felt and there is
no page cache yet to hide it. Outside the site root, because the engine reads that
directory and must not leave generated files in somebody's content git — a lost
cache costs one startup pass and no correctness.

Markup now carries the original as src, the derivatives as srcset closed by the
original at its own width, and width/height from the original — which retires most
of the latent row about the output floor; only a gallery's alt is still empty, and
a filename cannot supply that.

Two things the work itself decided:

`Fragment.Items` became `Fragment.Pictures`, ADR-0037's own revisit trigger. Items
had one consumer, so widening it beat adding a second list beside it.

"A browser can show it" and "we can resample it" are different questions, and
conflating them nearly deleted content: an SVG has no decoder here, so a single
predicate would have dropped SVGs from galleries silently. Undecodable and
unsupported pictures are now rendered as they are, without a size or a srcset.
This commit is contained in:
2026-08-01 02:23:36 +06:00
parent 8dc6746684
commit 7da2a58fd5
18 changed files with 502 additions and 59 deletions
+18 -4
View File
@@ -127,13 +127,27 @@ type Renderer struct {
// because markup belongs to the theme and a feature must not write any (ADR-0036).
type Partial func(name string, data Fragment) ([]byte, error)
// Fragment is what a fragment template receives (ADR-0037).
// Fragment is what a fragment template receives (ADR-0037, widened by ADR-0042).
type Fragment struct {
// Args are the call's key="value" pairs, exactly as written. Escaping is the template's.
Args map[string]string
// Items is a list the feature gathered rather than the author wrote — the filenames a gallery found.
// Kept apart from Args so a supplied value can never be mistaken for an authored one.
Items []string
// Pictures are what the engine gathered rather than the author wrote: one for a figure, many for a
// gallery, none when the call names nothing a picture. Kept apart from Args so a supplied value can never
// be mistaken for an authored one.
Pictures []Picture
}
// Picture is one image a fragment can render (ADR-0042).
type Picture struct {
// Src is the author's own file, relative to the bundle. A browser that ignores Srcset still gets the
// picture that was put there.
Src string
// Srcset offers the derivatives, closed by the original at its own width; empty when the picture is
// already small enough that no derivative was worth making.
Srcset string
// Width and Height are the original's intrinsic size, so a page can reserve the box before the bytes
// arrive. Zero when the file could not be read.
Width, Height int
}
// Origin tells a feature which bundle is being rendered, so a path in a call can resolve relative to it.
+8 -3
View File
@@ -1,6 +1,11 @@
{{define "figure" -}}
<figure>
{{- range .Pictures}}
<img src="{{.Src}}"{{if .Srcset}} srcset="{{.Srcset}}"{{end}}{{if .Width}} width="{{.Width}}" height="{{.Height}}"{{end}} alt="{{$.Args.alt}}">
{{- end}}
{{- if not .Pictures}}
<img src="{{.Args.src}}" alt="{{.Args.alt}}">
{{- end}}
{{- if .Args.caption}}
<figcaption>{{.Args.caption}}</figcaption>
{{- end}}
@@ -8,10 +13,10 @@
{{- end}}
{{define "gallery" -}}
{{if .Items -}}
{{if .Pictures -}}
<div class="gallery">
{{- range .Items}}
<figure><img src="{{.}}" alt=""></figure>
{{- range .Pictures}}
<figure><img src="{{.Src}}"{{if .Srcset}} srcset="{{.Srcset}}"{{end}}{{if .Width}} width="{{.Width}}" height="{{.Height}}"{{end}} alt=""></figure>
{{- end}}
</div>
{{- end}}