Files
hermes-webui/api
happy5318 b59a9cadd1 fix: ETag revalidation for /api/media so in-place file updates show immediately
/api/media served every file type with Cache-Control: private, max-age=3600,
so the browser reused a cached response for 1 hour without revalidating. When
an agent overwrites a file in place (same name, new content — e.g. a
regenerated image), inline previews kept showing the stale version until a
manual full-page refresh.

Changes:
- _serve_file_bytes now computes a weak ETag from file metadata
  (mtime_ns + size) and honors If-None-Match with a 304 short-circuit
  (RFC 7232 weak comparison, W/ prefixes ignored, '*' support), processed
  before the Range block since a matched conditional request skips the
  entity entirely. 200/206 responses carry the ETag.
- _handle_media splits the cache strategy by MIME: text/html keeps no-store
  (inline previews change constantly, PR #6706); all other media (images,
  audio, video, PDF) switch from max-age=3600 to no-cache so the browser
  MAY cache but MUST revalidate on every use — in-place updates are picked
  up immediately while unchanged files still short-circuit with 304.

Why weak ETag: metadata-based equality is sufficient for the overwrite
case and avoids hashing large files per request.
Why no-cache over must-revalidate: no-cache forces revalidation before
every use regardless of freshness; must-revalidate only fires after
max-age elapses, which does not help when the browser believes its cached
copy is still fresh.

Tests: tests/test_media_etag_revalidation.py (15 tests) covering ETag
emission, 304 short-circuit (match/star/mismatch/list/weak-comparison),
ETag change on in-place replacement, Range+ETag interaction, 304-before-
Range precedence, and the _handle_media end-to-end path (no-cache for
images/PDF, no-store preserved for HTML).
2026-08-11 14:06:52 +08:00
..