Skip to content

Give downloaded attachments a file extension - #101711

Merged
Julesssss merged 15 commits into
mainfrom
claude-attachmentDownloadFileExtension
Oct 12, 2026
Merged

Julesssss merged 15 commits into
mainfrom
claude-attachmentDownloadFileExtension

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Explanation of Change

Some attachments were saved with no file extension, so the OS treated a valid video as a generic document. This PR fixes both the download side and the upload side.

  1. Download uses the real file name. The video player's Download action never passed a file name, so it fell back to the extensionless storage key. The name is now passed from AttachmentView (and the inline VideoPlayerPreview) down to fileDownload. VideoRenderer also prefers the stored data-name, matching ImageRenderer.
  2. Uploads get an extension when one is missing. validateAttachmentFile now adds an extension from the MIME type (new getExtensionFromMimeType / appendExtensionFromMimeType helpers in FileUtils). This covers every upload flow: picker, drag and drop, paste, and share. If the type can't be resolved, the name is left unchanged.
  3. Unknown MIME types are detected from the file header. When the picker reports application/octet-stream (or an empty type), getMimeTypeFromFileHeader reads the first 32 bytes to identify the type. Web uses Blob.slice, Android uses RNFS.read, and iOS copies the header into a temp file with react-native-blob-util, because RNFS.read returns an empty string on iOS.

mime-db is now a direct dependency, pinned to the version already in the lockfile. Existing attachments are not backfilled.

Fixed Issues

$ #100579
PROPOSAL: #100579 (comment)

Tests

Precondition: Have a video (for example, a screen recording) whose file name has no extension, such as recording instead of recording.mp4.

  1. On native, attach a video from the gallery or document picker so the picker returns a name with no extension.
  2. Verify the message renders an inline video player rather than a generic file row.
  3. Open the player's three-dots menu and tap Download. Verify the saved file is named <name>.mp4 and opens in the OS video player.
  4. Repeat with the message context menu download, verify the file is named after the uploaded file and opens in the OS video player
  • Verify that no errors appear in the JS console

Offline tests

QA Steps

Same as tests

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari

MelvinBot and others added 2 commits September 21, 2026 09:08
Recover a missing extension from the MIME type at upload, and pass the
stored file name into the video player's download action instead of
falling back to the extensionless storage key.

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
@codecov

codecov Bot commented Sep 21, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ Changes either increased or maintained existing code coverage, great job!

Files with missing lines Coverage Δ
...omponents/Composer/implementation/index.native.tsx 90.38% <100.00%> (ø)
...HTMLEngineProvider/HTMLRenderers/VideoRenderer.tsx 95.00% <100.00%> (ø)
src/components/VideoPlayer/BaseVideoPlayer.tsx 47.07% <ø> (ø)
...ts/VideoPlayerContexts/VideoPopoverMenuContext.tsx 85.00% <100.00%> (+0.78%) ⬆️
src/components/VideoPlayerPreview/index.tsx 2.17% <ø> (ø)
src/libs/fileDownload/FileUtils.ts 47.26% <100.00%> (+1.48%) ⬆️
...meTypeFromFileHeader/getMimeTypeFromHeaderBytes.ts 100.00% <100.00%> (ø)
...rc/libs/getMimeTypeFromFileHeader/index.android.ts 100.00% <100.00%> (ø)
src/libs/snapshotPickedFile/index.native.ts 100.00% <100.00%> (ø)
src/libs/validateAttachmentFile.ts 97.91% <100.00%> (+0.35%) ⬆️
... and 6 more
... and 10 files with indirect coverage changes

@MelvinBot

MelvinBot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor Author

🤖 Android test steps verification results: ⚠️ partial

Verified on standalone Android NewDot: attachment download extension backfill works and inline video rendering/regressions are correct, but the video player's three-dot Download and the message context-menu Download produce different filenames for the same synced attachment (both have valid .mp4 extensions, but different base names).

Invalid file type modal rejecting a .txt receipt upload

Step Status
VERIFY 1: Attach a video from gallery/document picker so the picker returns a name with no extensionVia document picker, pushed file noextclip (no OS extension) attached successfully. Via "Choose from gallery", selecting a real video/mp4 asset produced the App's own picker-derived name 1000000031 (bare numeric, no extension) — confirmed directly in the app's Onyx SQLite store (report action html: <video ...>1000000031.mp4</video>, i.e. the raw picker name was 1000000031 before extension backfill), exactly matching the PR's described "gallery picker derives name from content:// URI, bare numeric segment" bug.
Second video attached via gallery picker, rendered inline in the Concierge thread
✅
VERIFY 2: Message renders an inline video player rather than a generic file rowBoth the document-picker attachment and the gallery-picker attachment rendered as an inline video thumbnail with a play button in the message bubble, not a generic paperclip/file row.
Extensionless document-picker video rendered as inline video player in chat
✅
VERIFY 3: Open the player's three-dots menu, tap Download, saved file named <name>.mp4 and opens in OS video playerOpened full-screen player for the gallery-picked video (proper BaseVideoPlayer with play/pause, mute, fullscreen, and a "More" three-dot button). Tapping More > Download saved w_5678cbb9204f1d16b2d608e9c64a36866da9343b-2026-09-21 09_27_14.187.mp4 to /storage/emulated/0/Download/Expensify/, confirmed via adb shell ls and file (ISO Media, MP4 Base Media v1) — a valid, playable .mp4. Note: the base name used is the server storage key, not the friendly original name 1000000031, because by the time the message had synced with the server its html no longer carried the data-name attribute the fix reads (confirmed via direct SQLite inspection of the app's OnyxDB — the synced <video> tag has no data-name attribute, only in the ephemeral pre-sync optimistic html).
Full-screen video player with play/pause, mute, fullscreen, and three-dot More menu
✅
VERIFY 4: Message context-menu Download produces the same filename as the player's DownloadLong-pressing the same gallery-picked video message and choosing Download saved 1000000031-2026-09-21 09_34_23.436.mp4 (confirmed via adb shell ls /storage/emulated/0/Download/Expensify/) — the true original picker name, correctly extension-backfilled. This differs from the player's three-dot Download result for the identical attachment (w_5678cbb9204f1d16b2d608e9c64a36866da9343b-...mp4). Both have valid .mp4 extensions, but the base filenames are not the same, so this step's explicit "verify both produce the same filename" assertion does not hold once the message has synced with the server.
❌
VERIFY 5 (regression): Attach a file whose name already has an extension — name unchangedAttached regression.mp4 via document picker. Onyx SQLite inspection of the resulting report action shows <video ...>regression.mp4</video> — unchanged, no double extension or alteration.
✅
VERIFY 6 (regression): Attach a receipt with an unsupported file type — still rejectedIn Create expense > Scan, choosing file badreceipt.txt (a .txt file) triggered an "Invalid file type" modal: "This file type is not allowed. Please try a different file type."
Invalid file type modal rejecting a .txt receipt upload
✅
Console errors observed (all pre-existing / unrelated)
  • Recurring: [hmmm] [PusherConnectionManager] WebSocketError - An exception was thrown by the websocket and intermittent "Unexpected error posting the comment. Please try again later." tied to Concierge's own automated thread/reply creation in this sandbox, not to the attachment/download flow under test.
  • Generic dev-mode warnings: "The result of getSnapshot should be cached to avoid an infinite loop", "Codegen didn't run for RNCViewPager...", "ActivityIndicator has been shown for longer than expected".
  • [warn] [AttachmentCache] Unsupported file type, skipping cache fired for every test attachment (including valid video/mp4 ones) — a pre-existing cache-skip warning, not a functional error.

view run · view recording

@MelvinBot

MelvinBot commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor Author

🤖 Web test steps verification results: ❌ fail

Blocked before reaching the feature under test: the session is stuck on the onboarding "work email" screen and every mutating command (click/press) fails with Unknown ref: e22, the same error the CI prestart replay hit. No VERIFY step could be exercised. Additionally, all 6 steps require file upload and/or download, which agent-device explicitly lists as out of scope for --platform web, so these test steps are not verifiable on the web platform even absent the blocker.

Session stuck on onboarding What's your work email? screen; Skip button click fails

Step Status
VERIFY 1: Attach a video whose filename has no extension (web adaptation: use the composer's attachment/paperclip flow to upload a video file without an extension)Could not proceed past onboarding; also file upload is explicitly out-of-scope for agent-device --platform web.
Session stuck on onboarding What's your work email? screen; Skip button click fails
❌
VERIFY 2: Message renders inline video player rather than a generic file rowDepends on VERIFY 1's attachment, which could not be created; not exercised.
❌
VERIFY 3: Open the player's three-dot menu, click Download, confirm saved filename is <name>.mp4 (web adaptation: the browser download would need to be intercepted/inspected, which agent-device web does not support)Not reachable; downloads are explicitly out of scope for agent-device --platform web per agent-device help web.
❌
VERIFY 4: Repeat via message context-menu Download and compare filename to VERIFY 3Not reachable for the same reason as VERIFY 3.
❌
VERIFY 5: Regression — attach a file that already has an extension and confirm name is unchangedRequires file upload, blocked by both the onboarding defect and web's out-of-scope upload capability.
❌
VERIFY 6: Regression — attach a receipt and confirm validation still rejects unsupported typesRequires file upload, blocked by both the onboarding defect and web's out-of-scope upload capability.
❌
Blocker detail

1. Onboarding could not be cleared. The CI prestart handed over a signed-in but onboarding-incomplete session. Its log records the same failure the tester then hit live:

melvin-session: onboarding failed - onboarding replay failed: Error (REPLAY_DIVERGENCE):
Replay failed at step 1 (press "role=\"button\" label=\"Skip\" || label=\"Skip\""): Unknown ref: e22
melvin-session: start FAILED - handing over signed-in session, onboarding incomplete

Read-only commands (snapshot, is visible) work against the screen, so the session is alive. Every mutating command fails identically regardless of target selector (label="Skip", label="Add work email", or a freshly-snapshotted ref), which points at the click pipeline rather than a stale ref. Re-running start or re-open is lifecycle-gated, so the tester failed fast as required.

2. These steps are out of scope for web regardless. agent-device lists downloads/uploads as unsupported on --platform web, and every step here needs one or both. The verification would need the native path to be meaningful — which is what the Android run covered.

Android coverage for the same steps is in the earlier Android results comment — 5 of 6 passed there, with step 4 failing.


view run · view recording

@suneox

suneox commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

I'll take a look at this PR today, @MelvinBot merge main & resolve conflict

…loadFileExtension

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>

# Conflicts:
#	tests/unit/FileUtilsTest.ts
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I merged main into this branch and pushed it as a merge commit (cef8a7c).

Conflict: only one file had a conflict, tests/unit/FileUtilsTest.ts. main reverted "Keep a file attachment intact when its comment is edited", and that revert removed getDownloadFileName and its tests. I dropped those tests, matching main, and kept this PR's getExtensionFromMimeType and appendExtensionFromMimeType tests. Nothing in this PR used getDownloadFileName.

Checks: FileUtilsTest and ValidateAttachmentFileTest pass (86 tests), and npm run typecheck passes.

@suneox

suneox commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review


P2 Badge Forward the filename to inline preview players

When a video is played inline in a wide report view, this new fileName prop stops at VideoPlayerPreview: its VideoPlayer render does not receive the existing fileName prop. The overflow Download action therefore still passes undefined through MoreMenuTrigger and falls back to the extensionless storage key. Pass fileName to that VideoPlayer as well so inline-player downloads get the same corrected name as attachment-modal downloads.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@suneox

suneox commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot Two issues before we can move forward:

  1. VideoPlayerPreview doesn't pass fileName to the VideoPlayer it renders, only the attachment modal path threads it, so the inline player three dot Download still names the file from the URL.

  2. A file object without an extension currently comes back as application/octet-stream at validateAttachmentFile, so fileNameWithExtension always appends .bin for those names and the issue is not fixed. @MelvinBot propose a solution to recover the real MIME type for the application/octet-stream case solution only, no implementation yet.

…l name

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

1. Fixed. VideoPlayerPreview now passes fileName to the inline VideoPlayer, so the three-dots Download uses the stored name (VideoPlayerPreview/index.tsx:146). VideoRenderer already passed the name to the preview. Pushed in 0c7a4e8. Lint and VideoRendererTest pass.

2. Proposal: detect the real type from the file's first bytes.

The application/octet-stream comes from the Android document picker. It calls ContentResolver.getType(), which returns octet-stream for an extensionless file. The picker returns no other useful signal: nativeType is the same value, and neither the URI nor the display name has an extension. The gallery picker already reports video/mp4. On web, File.type is '' for these files.

  1. Add a platform-split helper, getMimeTypeFromFileHeader (index.ts / index.native.ts, like snapshotPickedFile/). It reads the first 32 bytes: RNFS.read(path, 32, 0, 'base64') on native, and file.slice(0, 32).arrayBuffer() on web.
  2. Match a small signature table, so no new dependency is needed:
    • ftyp brand at offset 4: qt → video/quicktime, 3gp* → video/3gpp, heic/mif1 → image/heic, other brands → video/mp4
    • 1A45DFA3 → video/webm, FFD8FF → JPEG, 89504E47 → PNG, GIF8 → GIF, %PDF → PDF, RIFF…WEBP → WebP
  3. In validateAttachmentFile, before :71, sniff only when the name has no extension and the type is empty or octet-stream. Return the sniffed type along with the new name at :103. This also fixes the optimistic message, which currently renders an octet-stream video as a file link.
  4. Make appendExtensionFromMimeType skip application/octet-stream, so a failed sniff leaves the name unchanged instead of adding .bin.
Why not reuse the existing magic-byte check

verifyFileFormat only compares bytes 4–15, so it can't see signatures at offset 0, such as JPEG, PNG, PDF, and WebM. Its iOS branch also reads the whole file into base64, which is too expensive for large videos. RNFS.read reads just a byte range, and RNFS is already a dependency.

Not verified yet: what the iOS document picker returns as type for an extensionless file. It is probably null, which the empty-type condition above covers.


view run

@MelvinBot

Copy link
Copy Markdown
Contributor Author

No fix needed. All code checks pass on the latest commit (0c7a4e8), including ESLint, TypeScript, all 8 Jest jobs, and the builds.

The only failing check is Check independent approval. It fails because the PR has no approving reviews yet (approvers: []). It will pass once an eligible reviewer approves.


view run

@suneox

suneox commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot apply fix

…t-stream attachments

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

⚠️ MelvinBot CI Fix Loop Detected

MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR.

Next steps: A human should review the recent commits and resolve the conflicting CI errors manually.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Applied the proposal in d0dc5d8. An extensionless file reported as application/octet-stream (or '' on web) now has its real type detected from its first 32 bytes. It gets both the matching extension and the matching type.

One deviation: HEIC and AVIF brands (heic, mif1, avif, and similar) return no type instead of image/heic. Renaming a file to .heic after the HEIC check would skip the HEIC conversion flow. AVIF would otherwise fall through to video/mp4.

Tests
  • Typecheck and ESLint pass on the changed files.
  • 151 Jest tests pass across FileUtilsTest, ValidateAttachmentFileTest, ValidateAttachmentFileSnapshotTest, the new getMimeTypeFromFileHeaderTest, VideoRendererTest, AttachmentViewTest, useFilesValidation, useDownloadAttachmentTest, and AttachmentPickerAssetProcessingTest.
  • I couldn't run cspell locally. I added a cspell:ignore for the byte-signature words (ftyp, hevc, and so on) in the two new files.
  • I didn't test this in a running app. To check on Android, pick an extensionless MP4 through the document picker, send it, and confirm the Download saves <name>.mp4.

@suneox

suneox commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot merge main

…loadFileExtension

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Merged main into this branch and pushed (ac2a3fe). The merge brought in 1,766 commits with no conflicts.


view run

@suneox

suneox commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot verify pipeline failed & merge main.

MelvinBot and others added 2 commits October 5, 2026 16:12
…loadFileExtension

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
…tection

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I merged main into this branch and fixed both failing checks that came from this PR. The merge had no conflicts.

  • Oxfmt check: I formatted src/libs/getMimeTypeFromFileHeader/index.native.ts and tests/unit/getMimeTypeFromFileHeaderTest.ts. npm run fmt now changes nothing else.
  • spellcheck: I added extensionless and quicktime to cspell.json. npm run spell-changed now finds 0 issues.

Check independent approval still fails. It needs a reviewer's approval, not a code change.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I made both fixes and pushed them in a23340c:

  • QuickTime extension: video/quicktime now resolves to mov instead of qt, so Str.isVideo recognizes it (FileUtils.ts:184-194).
  • Unknown ftyp brands: Only brands in a new MP4 allowlist return video/mp4. Any other brand now returns undefined (getMimeTypeFromHeaderBytes.ts:11). I removed IMAGE_FTYP_BRANDS because the allowlist already excludes HEIC and AVIF.

I added a unit test for each change. The 103 tests in FileUtilsTest, getMimeTypeFromFileHeaderTest, and ValidateAttachmentFileTest pass. Typecheck, ESLint, cspell, and oxfmt are clean on the changed files. I didn't test this in the running app.


view run

@suneox

suneox commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot merge main and verify typecheck

…loadFileExtension

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I merged main and pushed (48d7eac). The merge brought in 1,933 commits with no conflicts.

Typecheck fails, but not because of this PR. It reports one error, and that error is also on main:

  • tests/unit/OptionsListUtilsTest.tsx:5750: Expected 1 arguments, but got 10.
  • This PR doesn't touch that file or OptionsListUtils. Both match main.
  • Every other project, including this PR's source files, passes.
Cause and fix

Match the self DM by the localized You/Me in report search added a test that calls filterAndOrderOptions with 10 positional arguments. Refactor activePolicyID in OptionsListUtils had already changed the function to take one object.

Two open PRs fix it:

After one of them merges, ask me to merge main again and typecheck should pass.


view run

@Julesssss

Copy link
Copy Markdown
Contributor

@suneox I think the broken checks on main were resolved just after you merged main. Could you try again?

@daledah

daledah commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot please merge main

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I didn't merge main. I can only push changes to this PR when the PR author, an assignee, or an existing co-author asks. daledah isn't one of those.

To have me merge main, the PR assignee (suneox) should reply with a request tagging me.


view run

@daledah daledah left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code changes LGTM

@melvin-bot
melvin-bot Bot requested a review from Julesssss October 9, 2026 17:09
@suneox

suneox commented Oct 11, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot Merge main

…loadFileExtension

Co-authored-by: Cong Pham <suneox@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I merged main into this branch and pushed it in 5b7d424. The merge brought in 270 commits with no conflicts. The mime-db 1.52.0 dependency is still there.

I didn't run any checks locally. CI will run on the new commit.


view run

@suneox

suneox commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

@Julesssss All comments and pipeline issues have been resolved

@Julesssss
Julesssss merged commit ee747d1 into main Oct 12, 2026
51 of 52 checks passed
@Julesssss
Julesssss deleted the claude-attachmentDownloadFileExtension branch October 12, 2026 20:28
@github-actions

Copy link
Copy Markdown
Contributor

🚧 Julesssss has triggered a test Expensify/App build. You can view the workflow run here.

@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/Julesssss in version: 9.5.8-0 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

@MelvinBot

Copy link
Copy Markdown
Contributor Author

No help site update needed. This is a bug fix: video downloads and extensionless uploads now keep a proper file extension, which is what users already expect. The existing article (Chat Features and Moderation Tools) already says Expensify adds the original extension back when you download, so no sentence became incorrect.


view run

@izarutskaya

Copy link
Copy Markdown

@suneox For step 4 the filenames are not the same. This is reproducible on Android, iOS and Web and the same in Prod. Not sure we need to create an issue for this, let me know

1791891669324.2-ios.mp4
1791891923936.3-AND.mp4
Captura de pantalla 2026-10-13 a la(s) 18 51 45

@suneox

suneox commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

@MelvinBot Update the test steps so QA isn't asked to verify the pre-existing behavior:

  1. Repeat with the message context menu download, verify the file is named after the uploaded file and opens in the OS video player

and remove step 5,6

@suneox

suneox commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

@izarutskaya Thanks for checking! For step 4 the key expectation is that the downloaded file is playable, the extension is preserved and the file opens in the OS video player, I've requested an update to the test steps.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Updated the Tests section. Step 4 now reads "Repeat with the message context menu download, verify the file is named after the uploaded file and opens in the OS video player", and steps 5 and 6 are removed. QA Steps still say "Same as tests", so QA picks up the change.


view run

@suneox

suneox commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

@suneox For step 4 the filenames are not the same. This is reproducible on Android, iOS and Web and the same in Prod. Not sure we need to create an issue for this, let me know

@Julesssss following up on this thread: the downloaded video file name shows the storage hash (w_<hash>) instead of the uploaded file name, and QA confirmed it also happens in production, so it's pre-existing and not introduced by this change.

I'd like to separate it into another issue: this PR's scope is already large, and the naming issue is outside it. Could you create another issue and assign me?

cc: @izarutskaya

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/cristipaval in version: 9.5.8-5 🚀

platform result
🕸 web 🕸 success ✅
🤖 android 🤖 success ✅
🍎 iOS 🍎 success ✅

Bundle Size Analysis (Sentry):

@izarutskaya

Copy link
Copy Markdown

@suneox created issue #103993

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Melvin-Test-Android Melvin-Test-Web Triggers Melvin to run the testing steps of the PR on web

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants