Skip to content

fix: native arbitrary-file download + image context-menu flash#830

Merged
wesbillman merged 2 commits into
mainfrom
brain/fix-image-file-download
Jun 3, 2026
Merged

fix: native arbitrary-file download + image context-menu flash#830
wesbillman merged 2 commits into
mainfrom
brain/fix-image-file-download

Conversation

@wesbillman
Copy link
Copy Markdown
Collaborator

Problem

Two regressions in message media handling, reported by @wesbillman:

  1. Arbitrary file downloads hit a Block CDN "browser not supported" interstitial. The FileCard (added in feat(media): support arbitrary file types with download cards #810) used a bare <a href download>; in the Tauri webview that navigates to the blob URL, escaping to the OS browser → WARP/CDN proxy.
  2. The image right-click "Download Image" menu flashed and immediately vanished, so it couldn't be clicked. The opening contextmenu/click also drove the Radix Dialog trigger and the menu's own global dismiss listeners, tearing it down on the same interaction.

Fix

File download — new native download_file Tauri command beside download_image:

  • Same SSRF /media/-origin validation, 50 MiB streaming cap, and MIME blocklist (HTML/SVG/executables only — PDFs, zips, text, docs pass).
  • Sanitizes the imeta filename (strips path components / control chars) before the save dialog.
  • Generic "All Files" save dialog instead of image-only.
  • download_image refactored to share a fetch_blob_bytes helper.
  • FileCard is now a <button> invoking download_file (with toast on error) — fetch stays inside the app's HTTP tunnel, no browser escape.

Context-menu flash — three complementary guards on markdown.tsx:

  • Defer attaching the global dismiss listeners by one tick (setTimeout(0)), so the opening right-click can never be the event that closes the menu.
  • onContextMenuCapture + stopImmediatePropagation to stop the contextmenu during capture.
  • preventDefault on non-left pointerdown on the Radix dialog trigger so the trigger stack can't tear the menu down.

Testing

  • pnpm typecheck
  • desktop unit suite (470 tests) ✅
  • cargo test media_download (10 URL-validation tests) ✅
  • e2e bridge + file-attachment specs updated to assert download_file is invoked on card click ✅
  • Full pre-push hook suite green: desktop-test, mobile-test, desktop-tauri-clippy, desktop-tauri-test, rust-clippy, rust-tests ✅
  • Manually verified in the Tauri app by @wesbillman: right-click image → menu stays open → Download saves via native dialog; file card → native save dialog, no browser launch.

Co-authored-by: Brain

@wesbillman wesbillman requested a review from a team as a code owner June 3, 2026 15:06
wesbillman and others added 2 commits June 3, 2026 10:25
Two regressions in message media handling:

1. Arbitrary file downloads (FileCard, added in #810) used a bare
   `<a href download>`. In the Tauri webview that navigates to the blob
   URL, escaping to the OS browser and landing on the Block CDN "browser
   not supported" interstitial. Add a native `download_file` Tauri
   command mirroring `download_image`'s SSRF `/media/`-origin validation,
   50 MiB streaming cap, and MIME blocklist, but with a generic save
   dialog and a sanitized imeta filename. FileCard is now a `<button>`
   that invokes it, keeping the fetch inside the app's HTTP tunnel.

2. The image right-click "Download Image" menu flashed and vanished:
   the opening contextmenu/click also drove the Radix Dialog trigger and
   the menu's own global dismiss listeners, tearing it down immediately.
   Defer attaching the dismiss listeners by one tick, stop the contextmenu
   during capture with stopImmediatePropagation, and preventDefault
   non-left pointerdown on the dialog trigger so the menu survives the
   interaction that opened it.

Refactor `download_image` to share a `fetch_blob_bytes` helper with the
new command. Update the e2e mock bridge and file-attachment specs to
assert clicking a file card invokes `download_file`.

Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
ForumPostCard rendered <Markdown imetaByUrl={parseImetaTags(post.tags)} />,
building a fresh imeta object on every render. The Markdown component's
React.memo compares imetaByUrl by reference, so the memo never held for
forum posts: react-markdown re-parsed and rebuilt the FileCard <button>
on every parent render, swapping the live DOM node.

A click landing across one of those swaps split mousedown/mouseup onto
different nodes (mousedown hit the markdown <div>, pointer/up hit the
button). The browser only synthesizes a `click` when mousedown and mouseup
share a target, so no click fired, the button's onClick never ran, and the
file download was silently dropped. This is why forum file downloads failed
while chat (which does not re-render the card the same way) worked.

Memoize the imeta map on post.tags so the Markdown memo holds, the FileCard
node identity stays stable, and the click lands. Also avoids re-parsing
every forum post's markdown on each render.

Verified: full desktop smoke e2e suite (127 passed); the previously failing
forum file-attachment download test green 6/6 on repeated runs.

Co-authored-by: Brain <21994759fc7a6fa6b965551d35cfd7897d262f2495467f2d78694ddcfa6a5c7e@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: Wes <wesbillman@users.noreply.github.com>
@wesbillman wesbillman force-pushed the brain/fix-image-file-download branch from bdcbf30 to 94e8a14 Compare June 3, 2026 16:48
@wesbillman wesbillman merged commit 82ae85f into main Jun 3, 2026
15 checks passed
@wesbillman wesbillman deleted the brain/fix-image-file-download branch June 3, 2026 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant