feedBack/static
Byron Gamatos 21997f4b5c
Fix slow library cover loading: serve sloppak art without unpacking + revalidated caching (#534)
* sloppak: read cover without unpacking + serialize/cap zip unpacks

Album art for a zip-form sloppak was served by resolve_source_dir(), which
unpacks the ENTIRE archive (stems included, ~30 MB) to disk just to read
cover.jpg. On the library grid that meant a full extraction per card on scroll.

- read_cover_bytes(): opens only the cover member from the zip (or reads the
  file for dir-form), with zip-slip guarding. ~4 ms vs a full unpack.
- resolve_source_dir(): per-file lock + bounded global semaphore so concurrent
  callers don't rmtree + re-extract the same dest at once (a race), and a burst
  can't saturate disk/CPU. 8 concurrent calls now dedupe to 1 unpack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* server: serve sloppak art via read_cover_bytes + cache album-art responses

- get_song_art() sloppak branch now reads the cover directly (no full unpack),
  off-thread via asyncio.to_thread.
- All art responses carry Cache-Control: public, max-age=86400. URLs are already
  cache-busted with ?v=<mtime>, so the browser stops re-fetching every cover on
  scroll-back; day bound self-heals any URL missing ?v.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* v3 library: lazy-load + async-decode card cover images

The grid (24 cards/page) and artist-row thumbnails emitted plain <img> with no
loading hint, so a whole page of covers fetched + decoded at once on each
scroll batch. Add loading="lazy" decoding="async" to defer off-screen fetches
and keep image decode off the main thread.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* address Codex review: zip cover normalization + correct art revalidation

Findings from the preflight Codex passes:

- sloppak.read_cover_bytes (zip form) read the raw manifest cover string via
  zf.read(), so a non-canonical name like './cover.jpg' or 'art/../cover.jpg'
  404'd. Normalize via safe_join → relative member; reject escape and the
  degenerate root-collapse case ('.', 'subdir/..') like _unpack_zip does.

- Album-art caching is correctness-first: Cache-Control: no-cache plus a strong
  validator, with real conditional handling (Starlette FileResponse emits an
  ETag but doesn't evaluate If-None-Match). All three art paths route through
  _art_conditional/_file_art_response → bodyless 304 on a matching validator.
  A long immutable max-age was rejected because the frontend ?v=<mtime> buster
  is only second-resolution and would pin a same-second rewrite.

- The sloppak cover is validated by CONTENT (sha1 of the bytes), not a stat:
  a dir-form sloppak edited in place changes the cover file's mtime but not the
  directory's, so a dir-stat ETag could emit a stale 304. Content hashing is
  correct for both dir- and zip-form. get_song_art gained an optional request
  (internal get_art caller passes none — safe).

Adds tests/test_sloppak_cover_art.py pinning read_cover_bytes (canonical,
non-canonical, degenerate/escape, dir/zip, webp) and the endpoint's 304 contract
incl. the dir-form in-place-edit no-stale-304 regression.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-20 00:22:43 +02:00
..
assets/venue/themes/small-club Clean release snapshot 2026-06-16 18:47:13 +02:00
capabilities feat(onboarding): first-run home tour (spotlight coach marks) (#528) 2026-06-19 16:32:33 +02:00
lottie Clean release snapshot 2026-06-16 18:47:13 +02:00
svg Clean release snapshot 2026-06-16 18:47:13 +02:00
v3 Fix slow library cover loading: serve sloppak art without unpacking + revalidated caching (#534) 2026-06-20 00:22:43 +02:00
vendor Clean release snapshot 2026-06-16 18:47:13 +02:00
_tailwind.src.css Clean release snapshot 2026-06-16 18:47:13 +02:00
app.js Purge external-format terminology from code, tests and docs 2026-06-16 19:36:53 +01:00
audio-mixer.js Clean release snapshot 2026-06-16 18:47:13 +02:00
capabilities.js feat(onboarding): first-run home tour (spotlight coach marks) (#528) 2026-06-19 16:32:33 +02:00
diagnostics.js Clean release snapshot 2026-06-16 18:47:13 +02:00
highway.js Purge external-format terminology from code, tests and docs 2026-06-16 19:36:53 +01:00
index.html feat(onboarding): input-device setup step + core-owned midi-input domain (#526) 2026-06-19 16:31:16 +02:00
lottie-api.js Clean release snapshot 2026-06-16 18:47:13 +02:00
style.css Repoint dead slopsmith URLs -> got-feedback 2026-06-17 11:02:04 +02:00
tailwind.min.css Clean release snapshot 2026-06-16 18:47:13 +02:00
tour-engine.css feat(onboarding): first-run home tour (spotlight coach marks) (#528) 2026-06-19 16:32:33 +02:00
tour-engine.js feat(onboarding): first-run home tour (spotlight coach marks) (#528) 2026-06-19 16:32:33 +02:00