Skip to content

Fix Share button being unresponsive when sharing an image to a new email#94905

Merged
blimpich merged 10 commits into
mainfrom
claude-shareRootPageValidateFileSize
Jul 2, 2026
Merged

Fix Share button being unresponsive when sharing an image to a new email#94905
blimpich merged 10 commits into
mainfrom
claude-shareRootPageValidateFileSize

Conversation

@MelvinBot

@MelvinBot MelvinBot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

When a file whose mimetype requires pre-validation (image/heic, or the generic image/* that Android share sheets like the Samsung A31 Gallery emit) is shared, the Share Details page rendered a blank preview and the Share button became a no-op, so the chat was never created.

The root cause is that ShareRootPage.validateFileIfNecessary built the validation request without a size field. validateAttachmentFile rejects any file with size == null as FILE_INVALID on its first guard, so the validated file object (VALIDATED_FILE_OBJECT) was never written. With no validated file, ShareDetailsPage showed an empty preview and handleShare early-returned. The failure was also silent because ShareRootPage never rendered useFilesValidation's ErrorModal.

This PR implements Option 2 from the approved proposal, scoped to ShareRootPage.tsx only:

  • In ShareRootPage.validateFileIfNecessary, populate the file's size via getFileSize(file.content) before calling validateFiles(), so validateAttachmentFile no longer rejects it as FILE_INVALID.
  • Render the ErrorModal returned by useFilesValidation on ShareRootPage, so any genuine validation failures (e.g. too large/too small) surface to the user instead of failing silently.

Fixed Issues

$ #93657
PROPOSAL: #93657 (comment)

Tests

// TODO: The human co-author must fill out the tests you ran before marking this PR as "ready for review".
// Please describe what tests you performed that validate your change worked.

  • Verify that no errors appear in the JS console

Offline tests

// TODO: The human co-author must fill out the offline tests.

QA Steps

// TODO: The human co-author must fill out the QA tests you ran before marking this PR as "ready for review".
// Please describe what QA needs to do to validate your changes and what areas they need to test for regressions.

  • 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 any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • 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 file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • 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

…s on ShareRootPage

Co-authored-by: Krishna <Krishna2323@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team June 29, 2026 20:48
@MelvinBot

Copy link
Copy Markdown
Contributor Author

I created this PR from an upstream branch since I don't have push access to your fork.

To take ownership of this branch and be able to push updates, run:

git remote add upstream https://github.com/Expensify/App.git
git fetch upstream claude-shareRootPageValidateFileSize
git checkout -b claude-shareRootPageValidateFileSize upstream/claude-shareRootPageValidateFileSize
git push -u origin claude-shareRootPageValidateFileSize

Then you can close this PR and open a new one from your fork.

@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Coverage Δ
src/libs/actions/Share.ts 0.00% <ø> (ø)
src/pages/Share/ShareRootPage.tsx 0.00% <0.00%> (ø)
... and 10 files with indirect coverage changes

@Krishna2323

Copy link
Copy Markdown
Contributor

@MelvinBot Core fix LGTM. Please: (1) keep getFileSize + size in validateFiles, (2) keep ErrorModal, (3) remove PDFValidationComponent — share never validates PDFs via this hook, (4) add .catch on getFileSize for stat failures, (5) consider fixing getFileSize/index.native.ts path normalization.

…, normalize native stat path

Co-authored-by: Krishna <Krishna2323@users.noreply.github.com>
@Krishna2323

Copy link
Copy Markdown
Contributor

@MelvinBot Still needed on this PR:

  1. CONSISTENCY-3: Extract getNativeFileSystemPath under src/libs/fileDownload/, use it in checkFileExists + getFileSize/index.native.ts (path logic is still duplicated inline), and add tests/unit/getNativeFileSystemPathTest.ts

  2. Update PR description — remove the PDFValidationComponent bullet; add one about the shared path helper

MelvinBot and others added 2 commits June 30, 2026 00:18
Co-authored-by: Krishna <Krishna2323@users.noreply.github.com>
Co-authored-by: Krishna <Krishna2323@users.noreply.github.com>
@Krishna2323

Copy link
Copy Markdown
Contributor

@MelvinBot Please scope this PR back to the approved proposal for #93657ShareRootPage.tsx only.

Keep:

  • getFileSize(file.content) + pass size into validateFiles() in validateFileIfNecessary
  • Render {ErrorModal} from useFilesValidation

Revert / remove (out of scope):

  • getNativeFileSystemPath.ts
  • checkFileExists changes
  • getFileSize/index.native.ts changes
  • tests/unit/getNativeFileSystemPathTest.ts
  • .catch on getFileSize (optional — fine to keep if it's a one-liner in ShareRootPage, but not required)

Update PR description to match — only the two bullets above.

…temPath/checkFileExists/getFileSize.native changes

Co-authored-by: Krishna <Krishna2323@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.

@Krishna2323

Copy link
Copy Markdown
Contributor

@MelvinBot merge main

@Krishna2323

Copy link
Copy Markdown
Contributor

@MelvinBot Please investigate ErrorModal / validation handling on ShareRootPage — I think it may not match how other useFilesValidation consumers work.

What I saw in QA (without the size fix):

  • Got a “corrupt attachment” modal, closed it, and could still navigate to Share Details (Share button should no-op without validatedFile, but UX is confusing).

How other pages handle it:

  • Composer / attachment picker (useAttachmentPicker): passes {isValidatingReceipts: false} and only navigates forward inside onFilesValidated when files.length > 0. Failed validation → modal → dismiss → user stays put.
  • Receipt flows use {isValidatingReceipts: true} intentionally.
  • Share is attachment sharing, not receipt scan — but ShareRootPage calls validateFiles() with no options, so it defaults to receipt validation (isValidatingReceipts: true).

Possible fixes to investigate (if you agree):

  1. Pass {isValidatingReceipts: false} to validateFiles() on ShareRootPage (same as useAttachmentPicker / MoneyRequestReceiptView for attachments).
  2. Skip the separate validateImageForCorruption call when shouldValidateFile(tempFile) is true — validateFiles already runs corruption checks internally; the duplicate path may show a second misleading “corrupt” alert.
  3. Note: FILE_INVALID (missing size) falls through to the default error text in getFileValidationErrorText, which shows the corrupt attachment message — might explain the wrong modal copy (separate from [Due for payment 2026-07-13] [$250] Android - Share - Share button is unresponsive when sharing a file to a new email #93657, but worth noting).

Core #93657 fix (getFileSize + size + ErrorModal) should stay — this is about making ErrorModal behavior consistent with attachment validation elsewhere.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Validation-consistency analysis for ShareRootPage

I traced all three points against the code. All three are real and your read of each is correct. Verdicts:

  • Some initial fixes and code style updates #1 (pass {isValidatingReceipts: false}) — ✅ Agree, with one thing to confirm (receipt/Submit tab). This is the meaningful behavior bug.
  • Fix spaces #2 (skip the separate validateImageForCorruption when shouldValidateFile is true) — ✅ Agree. Precise scoping; removes the double "corrupt" surface.
  • Add Expensify eslint #3 (FILE_INVALID → "corrupt attachment" copy) — ✅ Confirmed. It's the reason you saw the wrong copy; the core size fix removes the trigger, so treat this as a note.
Evidence & reasoning

#1 — Share defaults to receipt validation. ShareRootPage.tsx:53-62 calls validateFiles([...]) with no third arg, so it takes the default DEFAULT_IS_VALIDATING_RECEIPTS = true at useFilesValidation.tsx:336. By contrast useAttachmentPicker.ts:94 passes {isValidatingReceipts: false}. Receipt mode changes real behavior in validateAttachmentFile.ts:

  • Max size drops from MAX_SIZE (24 MB) to RECEIPT_MAX_SIZE (10 MB) — :31.
  • A MIN_SIZE (240 B) floor is enforced → FILE_TOO_SMALL:36. Attachment mode has no minimum.
  • A receipt-extension check runs first → WRONG_FILE_TYPE:23 (note the file name passed from Share is file.id, which may lack an extension).
  • The post-modal "proceed" path in onConfirmError only defers onFilesValidated when isValidatingReceipts === false.
  • Error copy also branches on isValidatingReceipt (receipt vs attachment size limits) — FileUtils.ts:772/:810.

So for an attachment share, receipt mode is wrong: it caps at 10 MB and rejects tiny images. {isValidatingReceipts: false} matches useAttachmentPicker and is the right default.

One thing to confirm before flipping it: the same validated file feeds both the Share tab (attachment) and the Submit tab (receipt scan) — isFileScannable at ShareRootPage.tsx:189-193. Switching to false means this pre-validation no longer applies the receipt min-size / 10 MB cap. That's fine if the receipt-scan path re-validates downstream (it generally does), but worth a quick check that submitting a shared image as a receipt still behaves.

#2 — double corruption check. shouldValidateFile is true only for HEIC and the generic IMG mimetype — exactly the files that go through validateFiles, which already runs validateImageForCorruption internally at validateAttachmentFile.ts:56 and surfaces failures via the ErrorModal. But ShareRootPage.tsx:113-119 also runs validateImageForCorruption for every image and surfaces failure via a native Alert.alert (showErrorAlert, which navigates to Inbox). So a corrupt HEIC/generic image can trigger both surfaces. Your scoping is exactly right: skip the separate call only when shouldValidateFile(tempFile) is true, and keep it for concrete image mimetypes (image/png, image/jpeg, …) that validateFiles doesn't touch — otherwise you'd drop the only corruption check for those.

#3FILE_INVALID copy. getFileValidationErrorText has no case for FILE_INVALID, so it falls through to the default at :836-839attachmentError / errorWhileSelectingCorruptedAttachment (the "corrupt attachment" copy). Before your fix, missing sizeFILE_INVALID at validateAttachmentFile.ts:19-21 → that default copy — which is exactly the mislabeled modal you saw in QA. Your core size fix removes the trigger, so this is a note rather than a required change; optionally add a dedicated FILE_INVALID case for correctness.

On the navigation UX ("closed the modal, still reached Share Details") — that's a separate pre-existing behavior: the Share button no-ops without a validatedFile but the page is still reachable. Out of scope for the ErrorModal consistency work; fine to leave for a follow-up.

Recommendation: #1 and #2 are worth doing in this PR — they're small, scoped to ShareRootPage.tsx (+ useAttachmentPicker parity), and directly fix the confusing double-modal / wrong-mode behavior. #3 is already handled by your core fix; a dedicated FILE_INVALID copy is optional polish. The core #93657 fix should stay as-is.


Next Steps: Reply @MelvinBot implement #1 and #2 and I'll push the changes to this branch, or @MelvinBot <feedback> to refine.

@Krishna2323

Copy link
Copy Markdown
Contributor

@MelvinBot implement #1 and #2 and also merge main

@Krishna2323

Copy link
Copy Markdown
Contributor

@MelvinBot implement #1 and #2 and also merge main

@Krishna2323

Copy link
Copy Markdown
Contributor

@MelvinBot merge main

@Krishna2323

Copy link
Copy Markdown
Contributor

@MelvinBot merge main branch

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Merged the latest main into this PR — no conflicts.

  • Merge commit: d23d45e
  • Brought in main HEAD 0d0e0d8

The branch is up to date and still shows as mergeable. Note: mergeStateStatus is BLOCKED only because required checks/approval are pending, not due to any merge conflict.

@Krishna2323

Copy link
Copy Markdown
Contributor

@MelvinBot implement #1 and #2 from here #94905 (comment)

…+ no duplicate corruption check)

Co-authored-by: Krishna <Krishna2323@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Implemented #1 and #2 from your comment — pushed to claude-shareRootPageValidateFileSize in 1496b47. Both scoped to ShareRootPage.tsx.

#1 — Validate as an attachment, not a receipt
validateFiles() now passes {isValidatingReceipts: false}, matching useAttachmentPicker.ts:94 and MoneyRequestReceiptView.tsx:694. Share is attachment sharing, so it shouldn't default to receipt validation (useFilesValidation.tsx:18). See ShareRootPage.tsx:53-65.

#2 — Drop the duplicate corruption check
The standalone validateImageForCorruption call is now skipped when shouldValidateFile(tempFile) is true, since those files already go through validateFiles(), which runs corruption checks internally. This removes the second, misleading "corrupt attachment" alert. See ShareRootPage.tsx:117-126.

Scope note

shouldValidateFile returns true only for image/heic and the generic image/* mimetype (ReceiptUtils.ts:93-95) — the exact set routed through validateFiles(). Other images (e.g. image/png, image/jpeg) still use validateImageForCorruption, so no image type loses its corruption check. The core #93657 fix (getFileSize + size + ErrorModal) is untouched.

Checks run locally: npm run lint ✅, npm run typecheck-tsgo ✅, React Compiler compliance ✅, Prettier ✅.

⚠️ Heads up: I couldn't merge main into this branch as you asked earlier — the CI checkout is a shallow clone (depth 1), so git merge origin/main fails with "refusing to merge unrelated histories" and the unshallow fetch didn't complete in this environment. You'll want to run git merge main locally.

@Krishna2323

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

Here are some automated review suggestions for this pull request.

Reviewed commit: 1496b4757e

ℹ️ 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".

Comment thread src/pages/Share/ShareRootPage.tsx Outdated
Comment on lines +53 to +54
getFileSize(file.content).then((size) => {
validateFiles(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Clear stale prevalidated files before starting validation

When a user shares a HEIC/image/* file after a previous successful share, VALIDATED_FILE_OBJECT still contains the old file because clearShareData() does not clear it. This new validation starts only after the async getFileSize() call, while the page marks the file ready immediately afterwards; if the user selects a recipient before validateFiles() overwrites Onyx, ShareDetailsPage sees a non-null validatedFile and uploads the previous image. Clear VALIDATED_FILE_OBJECT at the start of the share or keep the UI loading until validation finishes.

Useful? React with 👍 / 👎.

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.

@MelvinBot check this please ^

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed — this is a real bug, now fixed in 357ac04.

clearShareData() only reset SHARE_TEMP_FILE and SHARE_UNKNOWN_USER_DETAILS, never VALIDATED_FILE_OBJECT. Meanwhile ShareDetailsPage uses the pre-validated file as its source (ShareDetailsPage.tsx:80) and handleShare only guards on it being non-null (ShareDetailsPage.tsx:120). Since setIsFileReady(true) fires synchronously while the new validation is still awaiting getFileSize() (ShareRootPage.tsx:139-140), a HEIC/image/* share after a previous successful one could pass the guard against the stale file and upload the previous image.

Fix: added [ONYXKEYS.VALIDATED_FILE_OBJECT]: null to the clearShareData() multiSet. It's called once on ShareRootPage mount at the start of the flow, so the stale file is cleared before the new one is validated. If the user now taps Share before validation finishes, handleShare just no-ops (guard returns early) instead of uploading the wrong file.

Went with option 1 (clear on start) over keeping the UI in a loading state, since it's the minimal change and directly removes the data-correctness risk. typecheck-tsgo and Prettier pass locally; ESLint couldn't complete in this environment (OOM on the full-project type-aware run), so relying on CI lint for that gate.

…revious share's file

Co-authored-by: Krishna <Krishna2323@users.noreply.github.com>
@melvin-bot
melvin-bot Bot removed request for a team July 2, 2026 20:03
@melvin-bot

melvin-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown

@Krishna2323 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@MelvinBot

Copy link
Copy Markdown
Contributor Author

✅ Marked this PR ready for review.

On "add tests" and ticking the checklist — I've not ticked the manual-testing checklist items or filled the Tests / Offline tests / QA Steps sections. Those attest to testing actually performed on real platforms (and to attached screenshots/videos), and I can't truthfully check them on your behalf — doing so would put unverified claims in a public PR. Since you're handling recordings on the reviewer checklist, the author-side testing sign-off still needs a human.

To make it quick, here are ready-to-paste steps that cover this fix:

Tests / QA Steps

  1. On Android (e.g. Samsung Gallery, which emits a generic image/*) or from a HEIC source, open an image and tap Share → Expensify.
  2. On the Share Details page, enter a new email (one with no existing chat).
  3. Verify the image preview renders and tapping Share creates the chat with the image attached — previously the preview was blank and Share was a no-op.
  4. Share a second image in the same session and confirm the previous session's file isn't reused (see the VALIDATED_FILE_OBJECT clear in src/libs/actions/Share.ts:14).
  5. Share an oversized/undersized file and verify the validation error modal now surfaces instead of failing silently.

Offline steps

  1. Go offline, then share an image to a new email as above.
  2. Verify the chat/attachment appears optimistically and syncs on reconnect.

Unit test: I can add one if you'd like — reply @MelvinBot add a unit test for this and I'll open it. It would cover the size path in ShareRootPage.validateFileIfNecessary and the shouldValidateFile gate.

Once you've run the steps and added the recordings, please tick the author checklist items yourself so they accurately reflect what was verified.

@Krishna2323

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible 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 checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • 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 verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • 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 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.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this 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.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
web_chrome.copy.mp4
Android: mWeb Chrome
iOS: HybridApp
ios_hybrid.copy.mp4
iOS: mWeb Safari
MacOS: Chrome / Safari

@Krishna2323 Krishna2323 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.

LGTM! @blimpich, I think you'll need to handle the author checklist. 📋

@melvin-bot
melvin-bot Bot requested a review from blimpich July 2, 2026 20:06
Comment thread src/pages/Share/ShareRootPage.tsx Outdated
type: file.mimeType,
},
]);
getFileSize(file.content).then((size) => {

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.

❌ CONSISTENCY-6 (docs)

getFileSize(file.content) resolves from RNFS.stat, which rejects when the file is missing or the path is malformed (exactly the image/*/HEIC edge cases this PR targets). The promise chain has no .catch(), so on rejection the .then() never runs, validateFiles() is never called, VALIDATED_FILE_OBJECT is never written, and the Share button silently becomes a no-op again — the same silent failure this PR is fixing. This is a critical data-mutation path with no error handling and no feedback to the user or logs. (An earlier commit in this PR added a .catch() here and it was reverted.)

Add a .catch() that logs and still proceeds to validation (so a genuine error surfaces via ErrorModal instead of failing silently):

getFileSize(file.content)
    .catch((error: unknown) => {
        Log.warn('[ShareRootPage] Failed to get file size for validation', {error});
        return undefined;
    })
    .then((size) => {
        validateFiles(
            [{name: file.id, uri: file.content, type: file.mimeType, size}],
            undefined,
            {isValidatingReceipts: false},
        );
    });

Reviewed at: 357ac04 | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.

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.

@MelvinBot check this and update.

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.

@MelvinBot can you please address this comment? #94905 (comment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done — addressed in 1cc914c.

Confirmed the concern is valid: on native, getFileSize resolves from RNFS.stat(uri), which rejects for a missing or malformed path — the exact image/*/HEIC cases this PR targets. Without a .catch(), that rejection skips .then(), validateFiles() never runs, and the Share button silently no-ops again.

Added a .catch() that logs and resolves to undefined so the chain still proceeds to validation — a genuine failure now surfaces via ErrorModal instead of failing silently (ShareRootPage.tsx:54-71):

getFileSize(file.content)
    .catch((error: unknown) => {
        Log.warn('[ShareRootPage] Failed to get file size for validation', {error});
        return undefined;
    })
    .then((size) => {
        validateFiles(
            [{name: file.id, uri: file.content, type: file.mimeType, size}],
            undefined,
            {isValidatingReceipts: false},
        );
    });

typecheck-tsgo and Prettier pass. ESLint couldn't complete locally (the sandbox's 2 GB Node heap OOMs on the full project config) — CI lint will cover it.

@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

Here are some automated review suggestions for this pull request.

Reviewed commit: 357ac04602

ℹ️ 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".

Comment thread src/pages/Share/ShareRootPage.tsx Outdated
},
],
undefined,
{isValidatingReceipts: false},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve receipt validation for submit shares

For shared files whose MIME type is also scannable (notably Android image/* and HEIC), the Submit tab is shown, but forcing isValidatingReceipts: false validates them with the generic 24 MB attachment limit instead of the receipt 10 MB limit/resize path. SubmitDetailsPage then seeds and uploads the receipt from the shared file source, so a 10–24 MB image receipt that used to be rejected or resized can now reach the submit flow and fail server-side or create an oversized receipt.

Useful? React with 👍 / 👎.

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.

@MelvinBot I'm certain this is existing behavior, not introduced by this PR — can you investigate and confirm? Submit uses the raw shared file + 24 MB client check, ShareRootPage receipt pre-validation never succeeded on main for image/*/HEIC anyway.

…alidateFileSize

Co-authored-by: Krishna <Krishna2323@users.noreply.github.com>
@MelvinBot
MelvinBot requested a review from a team as a code owner July 2, 2026 20:32
@melvin-bot
melvin-bot Bot requested a review from Krishna2323 July 2, 2026 20:32
@melvin-bot

melvin-bot Bot commented Jul 2, 2026

Copy link
Copy Markdown

@Krishna2323 Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@melvin-bot
melvin-bot Bot removed the request for review from a team July 2, 2026 20:32
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Merged the latest main into this branch and pushed the merge commit (007d6ef92b8). There were no merge conflicts, so no manual resolution was needed.

Note: the branch was originally cloned shallowly, which caused an initial refusing to merge unrelated histories error — I unshallowed the repo before merging, so the merge landed cleanly against the real common ancestor.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Confirmed — you're right, no receipt/Submit-path regression

I traced both claims against the code. Both hold, and the {isValidatingReceipts: false} change does not regress the Submit (receipt) path. Verdicts below.

Claim: "receipt pre-validation never succeeded on main for image/*/HEIC anyway" — ✅ Verified. On main, validateFileIfNecessary built the request with no size and called validateFiles() with no options (so isValidatingReceipts defaulted to true). size == null hits the first guard in validateAttachmentFile.ts:18-21FILE_INVALID. Since FILE_INVALID isn't FILE_TOO_LARGE/HEIC_OR_HEIF_IMAGE, it skips the recovery branches in useFilesValidation.tsx:186-201, so onFilesValidated never fires and VALIDATED_FILE_OBJECT is never written for these mimetypes on main. There was no working 10 MB receipt cap / min-size floor to lose.

Claim: "Submit uses the raw shared file + 24 MB client check" — ✅ Verified (with one precision note). The uploaded receipt is the raw shared file, and the ~24 MB check is enforced on the Submit path independently of the pre-validated object:

The decisive fact: git diff origin/main..HEAD for the Share flow touches only ShareRootPage.tsx and Share.tsSubmitDetailsPage.tsx and useFilesValidation.tsx are byte-identical between main and this branch. So the receipt-consumption logic is entirely unchanged; the only deltas are the request now carries size and validateFiles runs in attachment mode.

Two precision notes (neither is a regression from this PR)
  • The literal phrasing "Submit does not depend on the pre-validated object" is slightly too strong: for image/heic/image/*, SubmitDetailsPage does read VALIDATED_FILE_OBJECT for the initial fileUri/fileName/fileType at SubmitDetailsPage.tsx:125-127 (gated by shouldValidateFile). But those values are immediately overridden by the raw .content chain, so they don't affect the upload — your conclusion is right, the reason is just slightly imprecise.
  • With {isValidatingReceipts: false}, validateAttachmentFile uses MAX_SIZE (24 MB) instead of RECEIPT_MAX_SIZE (10 MB) at validateAttachmentFile.ts:31 and skips the min-size floor. But the Submit path's own gate (useShareFileSizeValidation.ts:22) already uses MAX_SIZE (24 MB), not the 10 MB receipt cap — on both main and this branch. So if a 10 MB cap on shared receipts is ever the intended product behavior, that gap predates this PR and is independent of it.

Bottom line: confirmed existing behavior — flipping to {isValidatingReceipts: false} doesn't change what the Submit tab uploads or how it's size-checked. Safe to land as-is.

…ently no-oping

Co-authored-by: Krishna <Krishna2323@users.noreply.github.com>

@Krishna2323 Krishna2323 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.

LGTM!

@blimpich
blimpich merged commit f0d54c9 into main Jul 2, 2026
39 of 40 checks passed
@blimpich
blimpich deleted the claude-shareRootPageValidateFileSize branch July 2, 2026 22:28
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

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

@OSBotify

OSBotify commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/blimpich in version: 9.4.27-1 🚀

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

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 I reviewed the changes in this PR against the help site files under App/docs/articles, and no help site changes are required.

Why:

  • This is a bug fix that restores intended behavior, not a new feature or a change to any user-facing flow, copy, label, or setting. It fixes the Share button being unresponsive when sharing certain image files (image/heic / generic image/*) into a new chat, by populating the file size before validation and surfacing genuine validation errors. All changes are internal to ShareRootPage.tsx and Share.ts.
  • The help site describes the expected behavior of sharing content into Expensify; since this PR simply makes the flow work as already intended, the existing documentation remains accurate.
  • I searched App/docs/articles for the mobile OS share-sheet / "share to Expensify" flow (and for HEIC / image/* handling) and found no article documenting this behavior, so there is nothing to update.

Because no documentation update is needed, I did not create a draft help site PR.

@Krishna2323, please confirm you agree no help site changes are needed here. If you believe an article should be updated (or created) for this behavior, let me know and I'll draft one.

@izarutskaya

Copy link
Copy Markdown

Hi @Krishna2323 @blimpich Do we need QA tests here, can I check it off if no ? Thanks

@OSBotify

OSBotify commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/grgia in version: 9.4.27-2 🚀

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

Bundle Size Analysis (Sentry):

@Krishna2323

Copy link
Copy Markdown
Contributor

Tests / QA Steps

  1. On Android (e.g. Samsung Gallery, which emits a generic image/*) or from a HEIC source, open an image and tap Share → Expensify.
  2. On the Share Details page, enter a new email (one with no existing chat).
  3. Verify the image preview renders and tapping Share creates the chat with the image attached — previously the preview was blank and Share was a no-op.
  4. Share a second image in the same session and confirm the previous session's file isn't reused (see the VALIDATED_FILE_OBJECT clear in src/libs/actions/Share.ts:14).
  5. Share an oversized/undersized file and verify the validation error modal now surfaces instead of failing silently.

@izarutskaya, sorry for the late reply. This does require QA testing.

@blimpich, I'm not sure how the checks passed without the author checklist. Either way, I think we need to test this to make sure the issue is actually fixed. It would also be great if it could be tested by the same tester using the same device model mentioned in the OP: #93657 (comment)

@blimpich

blimpich commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Will request a retest 👍

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.

5 participants