# The image library

> Upload, describe and optimise images, one commit each: why alt text is not a property of a file, and what the AVIF conversion measures first.

Source: https://editor.astrocraftthemes.com/docs/images/

`/admin/images/` is a view of `src/assets/library/`, read fresh on every request. Every action on it is a commit on your branch — none of them is a push.

## The listing

A request-time directory read, not the build's glob. That distinction is the difference between a working library and a confusing one: in a production bundle the glob is frozen at build time, so an upload would be invisible and a delete a ghost until the next rebuild.

A file the build has never seen is marked **pending** with a badge on its tile, and its dimensions are read directly from the file. Once your site rebuilds, the badge goes.

Bytes are served through the CMS's own asset endpoint, which revalidates on modified-time and size — necessary because **Optimise** rewrites a file's bytes under an unchanged name.

## Upload

Drop files or use the button. The stored extension comes from the **content type**, never from the filename, so a `.jpg` that is really a PNG is stored as what it is. A filename that sanitises to nothing is refused; a collision suffixes from `-2`.

The filename is the security boundary here, and it is a pure function with its own tests — there is no path in an upload, only a name.

## Alt text is not a property of a file

This is the one modelling decision on the screen worth understanding, because it explains why the alt field behaves the way it does.

The same photograph means different things in different entries. So a description is written **per use**, not once per file — and the modal's alt field writes **both**: it sets a default in `src/assets/library/_descriptions.json` and rewrites every existing use of that file, in one commit.

Precedence: the first described use wins, and the default fills in behind it. Empty removes the default and writes `![](…)`, which is the markdown spelling of _decorative_ — the audit keeps flagging it, which is correct, because decorative is a claim somebody has to make on purpose.

Describe is the one verb that is **not** batched. One sentence across different photographs would be wrong.

The sidecar file starts with an underscore, which keeps it out of every listing by construction — the directory read, the glob pattern and the reference scan all skip it.

## Optimise

Converts to AVIF **when the encode actually wins**. The saving is measured first; a bigger result is thrown away, and an over-wide image falls back to resizing in place. A file earns a row in the Optimise tab when the saving is at least a tenth of the file **and** at least 4 KB.

A conversion renames the file to `.avif` and **rewrites every reference to it in the same commit**, moving its default description with it. That is why every optimise button arms before it fires: the conversion touches other people's entries.

Quality is `images.avifQuality` in [`src/admin.config.ts`](/docs/configuration/), defaulting to 50 — roughly visually lossless for photography on AVIF's scale, which is not JPEG's.

## The reference scan

The library reports which files are used and which are not, by searching your tracked files for the guarded `assets/library/<file>` path form — fixed-string, so a dot in a filename is a dot rather than a wildcard.

A hit inside the library directory itself, or anywhere under `src/admin/`, does **not** count as a use. For one day it did, and the scan cheerfully read the CMS's own test fixtures as content and rendered their alt strings as an author's.

> On the demo template, the library honestly reports every file unreferenced and missing alt text, because nothing outside the CMS's own source references those files. Your real repository gets real numbers.

## Deleting

Deletes the file and drops its default description, in its own commit. Like every other verb here, it is in your history afterwards.

## Selecting

Selection is keyed by **filename**, because the grid renders every panel and one file can therefore have up to three tiles on screen at once. Selecting it in one place selects it everywhere.

Upload, delete and optimise all reload the screen, because every figure on it — the counts, the tab totals, the savings — is derived from the listing.

## The picker

The same library, opened inline from [the editor's Image block](/docs/editor/) and from [the social image field](/docs/entry-fields/). The picker pre-fills the file's default description, so the common case is one click.

## Troubleshooting

**An uploaded image shows a "pending" badge.** Expected. The build has not seen it yet; the badge clears at the next build of your site.

**Optimise offers nothing for a large file.** The measured AVIF encode did not beat the original by a tenth of the file and 4 KB. That is the check working — a conversion that makes a file bigger is not an optimisation.

**Every file says unreferenced.** On the demo content, that is true. In your own repository, check that your entries reference images through the `assets/library/<file>` path form; that is the one spelling the scan matches.

**A description I wrote does not appear on an entry.** Precedence is first-described-use wins, default behind. An entry with its own alt text keeps it.

**An image 404s after Optimise.** The file was renamed to `.avif` and every reference was rewritten in the same commit — but only the guarded path form. A reference built by string concatenation in a component will not have been found.