Fix: copy only the selected text for system messages#95517
Conversation
Co-authored-by: Sahil <thesahindia@users.noreply.github.com>
|
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: Then you can close this PR and open a new one from your fork. |
|
🤖 I checked the failing checks — the only one failing is PR Author Checklist / checklist, and it's failing for a non-code reason:
All other checks are passing or still running (run). There's no code fix to push here — this check just needs the boxes in the PR Author Checklist section (and the Once the checkboxes are completed the check will re-run and pass. If a different check fails after the pending ones finish, re-tag me and I'll dig into that. |
Codecov Report❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.
|
|
@MelvinBot, mark off all the items in the author checklist |
|
✅ Done — I checked off all items in the PR Author Checklist section. Note: I left the two "Verify that no errors appear in the JS console" boxes under the Tests and QA Steps sections unchecked, since those aren't part of the author checklist. Let me know if you'd like those marked too. |
Yes do it @MelvinBot. And mark the PR as ready for review |
|
@thesahindia 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] |
|
✅ Done:
|
| if (selection) { | ||
| // When the user has highlighted part of a message, always copy exactly what's selected, | ||
| // regardless of the report action type (including system messages like "marked as paid"). | ||
| setClipboardMessage( |
There was a problem hiding this comment.
❌ CONSISTENCY-3 (docs)
This new if (selection) branch copies the mention-cleanup logic verbatim from the existing else if (content) fallback branch further down in the same handler (the identical setClipboardMessage(<value>.replaceAll(/(<mention-user>)(.*?)(<\/mention-user>)/gi, (match, openTag, innerContent, closeTag) => { const modifiedContent = Str.removeSMSDomain(innerContent) || ''; return openTag + modifiedContent + closeTag || ''; })) block). The only difference is selection vs content. Duplicating this regex/replace logic means a future fix to the mention handling has to be made in two places and risks the branches drifting apart.
Extract the shared logic into a small helper and call it from both branches, e.g.:
function setClipboardMessageWithCleanedMentions(value: string) {
setClipboardMessage(
value.replaceAll(/(<mention-user>)(.*?)(<\/mention-user>)/gi, (match, openTag: string, innerContent: string, closeTag: string): string => {
const modifiedContent = Str.removeSMSDomain(innerContent) || '';
return openTag + modifiedContent + closeTag || '';
}),
);
}Then use setClipboardMessageWithCleanedMentions(selection) in the new branch and setClipboardMessageWithCleanedMentions(content) in the existing else if (content) branch.
Reviewed at: fcc8e2a | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
Done ✅ Extracted the shared mention-cleanup logic into a helper, setClipboardMessageWithCleanedMentions, and called it from both the new if (selection) branch and the existing else if (content) fallback, removing the duplicated regex/replace.
Pushed as 3b23fb3.
Checks run locally
npm run fmt(oxfmt) — cleannpm run lint-changed— passednpm run typecheck-tsgo— passednpm run react-compiler-compliance-check check— passed (no components/hooks in this file)npm test -- tests/unit/ContextMenuActionsCopyMessageTest.ts— 2 passed
|
The only failing check is It fails with To clear it: the assigned reviewer needs to complete and post the PR Reviewer Checklist on this PR; the check will re-run and pass. No branch change is required. |
Co-authored-by: Sahil <thesahindia@users.noreply.github.com>
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-07-08.at.1.01.43.PM.movAndroid: mWeb ChromeScreen.Recording.2026-07-08.at.12.31.48.PM.moviOS: HybridAppiOS: mWeb SafariScreen.Recording.2026-07-08.at.4.05.10.PM.movMacOS: Chrome / SafariScreen.Recording.2026-07-08.at.10.55.30.AM.mov |
|
@thesahindia lmk when you're done on your end, and I can merge this |
|
The fix can't be tested on Safari Desktop because the selection is cleared when the context menu opens. Screen.Recording.2026-07-08.at.11.02.05.AM.mov |
@flodnv, we can merge this. |
flodnv
left a comment
There was a problem hiding this comment.
All good with this @trjExpensify ?
trjExpensify
left a comment
There was a problem hiding this comment.
I think this is fine, as it's consistent with highlighting normal text and using the copy button in the context menu. 👍
|
🚧 flodnv has triggered a test Expensify/App build. You can view the workflow run here. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/flodnv in version: 9.4.32-0 🚀
|
No help site changes requiredI reviewed the changes in this PR against the help site articles under This PR is a narrow bug fix to copy-to-clipboard behavior: when a user highlights part of a system message (e.g. "marked as paid", approved, reimbursed) and copies via the context menu, it now copies only the highlighted selection instead of the full reconstructed message — matching how regular chat messages already behaved. Why no docs update is needed:
Since there is no documented behavior that this change contradicts or adds to, no draft help site PR was created. @thesahindia, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR |
|
🚀 Deployed to production by https://github.com/grgia in version: 9.4.32-3 🚀
Bundle Size Analysis (Sentry): |
Explanation of Change
When copying a message via the context menu, system messages (e.g. "marked as paid", approved, reimbursed) always copied the full reconstructed message text, even when the user had only highlighted part of it. Regular chat messages, by contrast, already copied just the highlighted selection.
The copy handler captured the highlighted text into
content = selection || messageHtmlbut only honored it in the fallbackelse if (content)branch at the bottom of a longif / else ifchain. System messages match dedicated earlier branches that rebuild the whole message from the report action and never look atselection, so partial selections were ignored for them.This was surfaced (not introduced) by #92142, which made system-message text selectable on desktop web — before that, users couldn't produce a partial selection for those actions.
The fix adds an early
if (selection)guard at the top of the dispatch chain: when the user has highlighted part of any message, we copy exactly what's selected (reusing the same mention-cleanup already applied in the fallback branch). Full-message reconstruction remains the default when nothing is selected. This makes Step 8 and Step 10 in the report behave identically.Fixed Issues
$ #95463
PROPOSAL: #95463 (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 validates your changes worked.
Offline tests
Same as tests (copy to clipboard is a client-side operation).
QA Steps
// TODO: The human co-author must fill out the QA tests you ran before marking this PR as "ready for review".
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectiontoggleReportand notonIconClick)Avatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari