Files
enricobuehler c354d7cd09 feat(console): a grid arrangement for the library, plus art eviction and a skeleton shelf
The coverflow is a browsing surface: one cover at a time, big, artwork doing the
talking. It is the wrong shape for "I know what I want, show me everything",
which on a 200-title shelf means holding Right and watching. The grid is that
other surface — about eighteen covers at once — and it is a VIEW rather than a
second screen: same cursor, same collated order, same art cache, same detail
band underneath. Only the arrangement differs.

`grid_step` is the pure half, and it carries the one real subtlety: the two
boundary rules are different on purpose. A row's ENDS refuse, exactly as the
shelf's do, because wrapping would make a held Right scan the whole library and
the shoulders already do that. But Down into a SHORT last row CLAMPS to the last
title rather than refusing — a ragged tail is a layout accident, not a boundary
the user chose to hit. Both, plus page jumps and degenerate grids, are pinned by
tests.

Where the toggle lives is a deviation from the plan, and deliberate. The plan
put View on X and Collections on Y, but X is already Copy link in the library
and the grid needs Up/Down for rows, which leaves exactly one free face button
for two features. Rather than evict a shipped binding, `library_view` becomes a
settings row in Interface beside the palette and reduce-motion — which is what
that tab is for, and this is a preference you set once, not something you toggle
mid-browse. Y stays free for Collections.

Art eviction is not cosmetic. `LibraryScreen::art` had no eviction at all, so
every cover ever scrolled past stayed decoded for the life of the screen — the
shelf touched a dozen and hid it; the grid touches all of them. Posters are now
frame-stamped as they are DRAWN and the coldest trimmed past a budget of 160.
Stamping after the draw rather than on arrival is the load-bearing detail, and
the test says so: an LRU keyed on arrival would cheerfully evict the six covers
the cursor is sitting among. Only decodes are trimmed — the encoded bytes are
not kept here either way, so the fetch pipeline is untouched and a re-entry
costs one frame of grey.

Loading becomes a skeleton in the arrangement the real cards will arrive in,
with one travelling sheen — the wait is for a LIST whose shape is known before
its contents are, so showing that shape is more honest than a blank field that
abruptly becomes a coverflow. It costs one extra paint and freezes under reduced
motion.

Two things fell out of the refactor and are worth naming: the art-less card face
is now one function both arrangements call (a launcher without a poster still
must not read as a game whose cover failed to load), and the detail band is
shared rather than copied — it also gained the platform, which is the whole
point of plumbing it, because "Shadow of the Colossus" means something rather
different with "PS2" under it.

Verified in the pf-gtkflow container: fmt, clippy --all-targets -D warnings,
plain build, 118 console-ui + 213 client-core tests green. One run aborted
inside rav1d's CDEF filter; that is environmental (emulated amd64), confirmed by
A/B — the immediately preceding run carried the identical pf-client-core sources
and passed 213/213, and the re-run passes.
2026-08-16 15:55:32 +02:00
..