forked from unom/punktfunk
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.