fix(settings): close the wallpaper market's review findings
The market shipped three things that misbehave in ordinary use, plus a tail of smaller gaps. All are fixed here; the contract change between the Rust summary and its TypeScript mirror is why this is one commit. Concurrent downloads shared one staging file. The grid disabled only the card being downloaded, so a second wallpaper could be picked mid-download, and both writers then fought over `background.img.tmp`: the second `File::create` truncates what the first is still writing, and whichever `rename` lands second fails with ENOENT. On APFS that reproduced as "download failed" on a download that had actually succeeded, 58 times out of 60 interleavings. Writers now stage into `background.img.<pid>.<seq>.tmp` — rename stays atomic, so overlapping writers are a clean last-writer-wins and neither can truncate the other — and the dialog serializes downloads behind a synchronous ref, because two clicks in one frame both read the state guard before it re-renders. Crash debris is swept on a 10-minute age gate, which a live writer's file can never trip. Roughly one wallpaper in fifteen could never be applied. Downloads are capped at 16 MiB / 40 Mpx; measured against the default browse view, 5.0% of listings exceed the byte cap and 1.7% the pixel cap — one or two dead cards per page, each answering a click with "please retry" for a condition retrying cannot change. The listing already reports `file_size`, so the summary now carries it alongside the dimensions, and those cards are disabled up front, badged, and told the offending number against the ceiling. Real failures now show the backend's own message instead. The host allowlist stopped at the first URL. reqwest's default policy follows ten hops across hosts, and `fetch_asset` hands the body back to the caller — so a redirect off wallhaven made this a reader of whatever the host can reach, which in server mode may be a private network. Every hop is now re-checked, the way `forge` and `remote_proxy` re-check theirs. Also: the validate-and-write tail moved off the async runtime, matching every other command in `commands::background`; the 30 s total deadline became a read-stall timeout plus per-request deadlines, since 16 MiB in 30 s demanded 4.4 Mbit/s from exactly the networks this proxy exists for; the source-URL key joined the cross-window storage listener, so a second window stops claiming a replaced wallpaper is "in use"; the thumbnail cache is LRU-bounded; the search debounce no longer resets the page on its mount tick, which used to snap you back to page 1 if you paged within 300 ms of opening; paging follows the page wallhaven actually served; the card's accessible name says what it does and the focus ring shows it; and three i18n keys nothing referenced were dropped. `validate_background`'s doc claimed its callers only ever pass files the user picked off their own disk. The market broke that premise, so the doc now says what still holds on that path and what does not.
X
xintaofei committed
e79a512496742e36880a7db0d7190b3c82405fe6
Parent: 75cdec5