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
+12 -4
View File
@@ -84,12 +84,20 @@ Every fragment receives the same two fields (ADR-0037):
| Field | Contents |
|---|---|
| `.Args` | the call's `key="value"` pairs, exactly as the author wrote them |
| `.Items` | a list the *engine* gathered, empty unless that shortcode gathers one |
| `.Pictures` | images the *engine* gathered: one for a figure, many for a gallery, none when the call names nothing it recognises (ADR-0042) |
Each picture carries:
| Field | Contents |
|---|---|
| `.Src` | the author's own file, relative to the bundle — always usable on its own |
| `.Srcset` | the generated widths, closed by the original at its own width; empty when nothing was worth generating, or when the format has no decoder |
| `.Width`, `.Height` | the original's intrinsic size, for reserving the box; zero when the file could not be read |
| Shortcode | Template | Receives |
|---|---|---|
| `{{< figure src="…" alt="…" caption="…" >}}` | `figure` | `.Args.src`, `.Args.alt`, `.Args.caption` |
| `{{< gallery >}}` | `gallery` | `.Items`the picture filenames beside the bundle, in filename order |
| `{{< gallery >}}` | `gallery` | `.Pictures`every picture beside the bundle, in filename order |
`{{< include file="…" >}}` has **no fragment**: an included file is content, so it renders as Markdown in
place and a theme has nothing to style about it (ADR-0038).
@@ -99,8 +107,8 @@ engine found. Arguments are escaped by `html/template` like any other data, in w
template puts them — which is what keeps an author's text out of the markup. A call whose template is
missing renders nothing and logs; it never fails the page.
A gallery entry is a bare filename, relative to the bundle, so a template writes it straight into `src`. It
carries no alt text or dimensions, because nothing in a filename supplies either.
A gallery entry has no alt text: nothing in a filename supplies one, and inventing it would be worse than an
empty `alt` on a picture the page has already introduced. A figure's alt is the author's, from `.Args.alt`.
The set is overlaid the same way as the page kinds: a site's `templates/shortcodes.html` is parsed after
the embedded one, so redefining `figure` replaces it and any fragment left alone is inherited. Argument