Skip to content
This repository was archived by the owner on Jul 15, 2026. It is now read-only.

Media: stat-gated image renderability + labeled placeholders for missing files - #26

Merged
joestump-agent merged 3 commits into
mainfrom
feature/15-media-broken-images
Jul 11, 2026
Merged

Media: stat-gated image renderability + labeled placeholders for missing files#26
joestump-agent merged 3 commits into
mainfrom
feature/15-media-broken-images

Conversation

@joestump-agent

Copy link
Copy Markdown
Owner

Part of #15. Closes #15. Re-verifies #4.

Diagnosis (issue #15)

The <img> renders (imgRenderable returned true) but its src 404s at request time. Worked through the ticket's hypotheses in tests:

  • Missing source file — attachment row exists in DB but the file isn't on disk → handleMedia 404 → browser broken-image glyph. Confirmed the dominant cause.
  • Path/encoding round-trip — verified mediaURLhandleMedia agree on RelPath with spaces/unicode/#/subfolders (regression test).
  • HEIC/TIFF transcode gap — the renderability gate could mark a file renderable while its derived JPEG was absent.

Fix

  • Stat-gated renderability: the grid checks the file (and any required derivative) exists before emitting an <img>; genuinely-missing files degrade to msgbrowse's own labeled placeholder instead of a raw broken image. No inline JS (CSP-safe).
  • Applied to the transcript surface too, so a missing inline image there degrades to a chip rather than a broken <img>.
  • Regression tests: TestTranscriptMissingImageNoBrokenImg, TestGalleryMissingImagePlaceholder, TestGalleryFilesMissingInert, plus the path-encoding round-trip.

Issue #4 re-verification

PR #18's hx-boost="false" did not address the reported cause — confirmed: #main-content sits inside the un-boosted .drawer-content; only the toolbar title/nav/search and the sidebar are boosted, so those file anchors were never htmx-intercepted (the attribute is harmless future-proofing, kept). Web-server evidence:

  • File present → cannot reproduce "click does nothing": native nav returns 200 + Content-Disposition: attachment → real download.
  • File missing → the same 404 this PR now makes graceful.

Remaining for #4: desktop-webview (Wails) download interception is out of scope for the server and needs a cmd/msgbrowse-desktop follow-up. Recommend keeping #4 open for that.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 3 commits July 11, 2026 08:24
…ing files (#15, #4)

Diagnosis of issue #15 (browser broken-image glyphs on Media -> Images):

- Hypothesis 1 CONFIRMED (root cause): imgRenderable returned true for any
  web-native extension without checking that the file exists on disk (or
  that the source's archive root even resolves), so an attachment row whose
  file is absent rendered an <img> whose /media src answers 404 (400 on an
  unconfigured root) -- exactly the browser's broken-image glyph.
- Hypothesis 2 (encoding round-trip) disproved: mediaURL PathEscapes per
  segment and the Go 1.22 mux decodes {path...}; spaces, unicode, '#', '%',
  '&', '+', ';' and subfolders all round-trip. Locked in as regression by
  TestMediaURLEncodingRoundTrip and the extended TestMediaURLEscaping.
- Hypothesis 3 (transcode gap) disproved as stated: the HEIC/TIFF gate
  already stats the derived JPEG -- it never claims a derivative that does
  not exist. Full state matrix covered by TestImgTileState.
- Hypothesis 4 ruled out by inspection: tiles are static HTML swapped in by
  htmx; no JS touches img src, and img-src 'self' admits /media.

Fix: a three-state imgTileState (img / nopreview / missing) classifies each
image with the same existence checks handleMedia applies at serve time. The
gallery grid renders msgbrowse's own inert labeled "missing" placeholder for
absent files (an <img> would 404; a download link would save an error page),
and the transcript -- imgRenderable now delegates to the classifier -- falls
back to its labeled attachment chip instead of a broken thumbnail.

Files tab (issue #4 remnant): decorateFiles already stat'ed every row, so
missing files now flag Missing and render an inert labeled card instead of a
download anchor -- a native click on <a download> answering 404 surfaces as
a silently failed download, i.e. "clicking does nothing" in a plain browser.

Cost: one stat per rendered tile (plus one for the derivative on convertible
formats) -- the same per-item price the Files tab already paid in
decorateFiles, microseconds against the SPEC-0008 budgets.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…SS (#15, #4)

The transcript fix for a DB-only image (file absent from the archive on this
machine) was left as uncommitted working-tree edits, so the tree was in an
inconsistent, un-mergeable state: partials.html rendered an inert
attach-chip-missing <span>, but media_issue15_test.go still asserted a live
<a class="attach-chip"> download anchor to ghost.jpg, and app.css was never
regenerated for the new .attach-chip-missing rules — CI failed on the tree.

Reconcile the deliverable into one self-consistent commit:

- Finalize partials.html: a missing image/attachment renders the inert,
  labeled attach-chip-missing placeholder instead of a download anchor whose
  click fetches a 404 (the issue #4 "clicking does nothing" silently failed
  download). This matches the gallery's inert missing tile/card treatment,
  so graceful degradation is applied consistently across the transcript and
  the Media Images/Files tabs.
- Update TestTranscriptMissingImageNoBrokenImg to codify the fix: assert the
  missing image is NOT a link (no <a ...ghost.jpg) and renders the labeled
  attach-chip-missing placeholder, mirroring the gallery's inert-element
  assertions — rather than green-lighting the dead download link.
- Regenerate internal/web/static/app.css so the .attach-chip-missing rules
  ship; the embedded server serves static/app.css, so without this the chip
  would render unstyled.
- gallery.html/input.css three-state imgTileState changes folded in.

CGO_ENABLED=0 make check passes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…en-images

# Conflicts:
#	internal/web/static/app.css
@joestump-agent
joestump-agent merged commit b345d03 into main Jul 11, 2026
3 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: some images on the Media tab fail to load (broken-image placeholders)

2 participants