From dd8706e2fcd5ba026edf877c3b15859c589afd57 Mon Sep 17 00:00:00 2001 From: jay Date: Sun, 21 Jun 2026 18:17:30 -0400 Subject: [PATCH] Art post-audit polish (Codex): image HEAD, texture immutable cache, lightbox a11y, spacing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /api/art/image/{id} now answers HEAD as well as GET (was 404 on HEAD) — mirrors the /a/{id} fix. Added tests/test_art_api.py (GET+HEAD+size=full fallback + today payload). - /textures/* served immutable (long cache) instead of no-cache; excluded from the revalidate matcher. Live Caddyfile + repo snapshot both updated. - Lightbox: Escape closes it, and focus moves to it on open (keyboard-friendly). - Trimmed the gallery's top padding so "Daily Art" sits closer to the bar. Co-Authored-By: Claude Opus 4.8 --- deploy/caddy/Caddyfile.snapshot | 6 ++++ frontend/src/routes/art/+page.svelte | 14 +++++++-- goodnews/api.py | 2 +- tests/test_art_api.py | 45 ++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 tests/test_art_api.py diff --git a/deploy/caddy/Caddyfile.snapshot b/deploy/caddy/Caddyfile.snapshot index 0ebf024..3d9450f 100644 --- a/deploy/caddy/Caddyfile.snapshot +++ b/deploy/caddy/Caddyfile.snapshot @@ -62,6 +62,11 @@ upbeatbytes.com { @immutable path /_app/immutable/* header @immutable Cache-Control "public, max-age=31536000, immutable" + # Static texture assets (frame wood grain, etc.) — large and unchanging. Cache + # them forever like immutable assets; rename the file if the texture ever changes. + @textures path /textures/* + header @textures Cache-Control "public, max-age=31536000, immutable" + # The SPA shell: "/" and extensionless client routes (try_files → index.html). # Briefly cacheable at the CDN edge (s-maxage) so a first paint never depends # on this origin's uplink; browsers still revalidate every visit (max-age=0). @@ -80,6 +85,7 @@ upbeatbytes.com { # classic blank-screen cause behind a CDN. @revalidate { not path /_app/immutable/* + not path /textures/* path *.* } header @revalidate Cache-Control "no-cache" diff --git a/frontend/src/routes/art/+page.svelte b/frontend/src/routes/art/+page.svelte index fb54490..5d0db8e 100644 --- a/frontend/src/routes/art/+page.svelte +++ b/frontend/src/routes/art/+page.svelte @@ -18,6 +18,13 @@ let zoom = $state(false); let frame = $state('walnut'); let thickness = $state(1); // frame-scale multiplier, 0.7–1.9 (mat caps at 1.5) + let lightboxEl = $state(null); + + function onKey(e) { + if (e.key === 'Escape' && zoom) zoom = false; + } + // Move focus to the lightbox when it opens, so Escape/Enter work and focus is trapped sanely. + $effect(() => { if (zoom && lightboxEl) lightboxEl.focus(); }); let who = $derived( art ? [art.artist || 'Unknown artist', art.date_text].filter(Boolean) : [] @@ -50,6 +57,8 @@ } + + Daily Art · upbeatBytes @@ -145,7 +154,7 @@ {#if zoom && art} -