Skip to content

Make system/violation messages text-selectable on desktop web#92142

Open
MelvinBot wants to merge 5 commits into
mainfrom
claude-systemMessagesSelectable
Open

Make system/violation messages text-selectable on desktop web#92142
MelvinBot wants to merge 5 commits into
mainfrom
claude-systemMessagesSelectable

Conversation

@MelvinBot

@MelvinBot MelvinBot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Allow system/violation messages in chat (hold/unhold, approved, rejected, dismissed violation, modified expense, etc.) to be text-selectable and copyable on desktop web.

Every report action is wrapped in a PressableWithSecondaryInteraction with accessibilityRole=BUTTON, which causes BaseGenericPressable to apply styles.userSelectNone (CSS user-select: none). Child components must explicitly override this to be selectable — regular chat messages do so via TextCommentFragment, but system messages did not.

This PR applies the same pattern already used by TextCommentFragment and DotIndicatorMessage to the two system-message renderers:

  1. ReportActionItemBasicMessage.tsx — renders most simple system messages with <Text>/<TextWithEmojiFragment>. Now applies styles.userSelectText on desktop web (and keeps styles.userSelectNone on touch/narrow layouts).
  2. ReportActionItemMessageWithExplain.tsx — renders modified-expense messages via RenderHTML. The hard-coded isSelectable={false} is changed to the same conditional !canUseTouchScreen() || !shouldUseNarrowLayout.

Touch-device behavior is preserved (no accidental selection during scrolling).

Fixed Issues

$ #84639
PROPOSAL: #84639 (comment)

Tests

  1. Go to the report screen on desktop.
  2. Trigger different system messages, for example by approving/holding an expense, exporting expenses, changing the room description, marking tasks as complete, etc.
  3. Try selecting the text of the system messages by double-clicking or dragging.
  4. Verify that all system messages are selectable.

Note: Error messages inside pressable components won't be selectable. For example, the error text in the expense preview.

  • Verify that no errors appear in the JS console

Offline tests

N/A — this is a pure UI/style change with no network behavior.

QA Steps

  1. Go to the report screen on desktop.
  2. Trigger different system messages, for example by approving/holding an expense, exporting expenses, changing the room description, marking tasks as complete, etc.
  3. Try selecting the text of the system messages by double-clicking or dragging.
  4. Verify that all system messages are selectable.

Note: Error messages inside pressable components won't be selectable. For example, the error text in the expense preview.

  • 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 shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • 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)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • 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)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • 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 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.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • 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

Co-authored-by: Sahil <thesahindia@users.noreply.github.com>
@MelvinBot MelvinBot requested a review from a team May 30, 2026 17:27
@MelvinBot

Copy link
Copy Markdown
Contributor Author

@thesahindia 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-systemMessagesSelectable
git checkout -b claude-systemMessagesSelectable upstream/claude-systemMessagesSelectable
git push -u origin claude-systemMessagesSelectable

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

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot, merge main.

…Selectable

Co-authored-by: Sahil <thesahindia@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Merged the latest main into this branch (merge commit cb816a1) and pushed. No conflicts — the merge was clean.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I analyzed the two failing checks. Neither is caused by this PR, and there's nothing to fix on this branch.

typecheck — pre-existing breakage on main (not this PR)

The error is:

src/components/Search/SearchFiltersParticipantsSelector.tsx(72,81): error TS2304: Cannot find name 'allPolicies'.

This file is not part of this PR's diff (this PR only touches ReportActionItemBasicMessage.tsx and ReportActionItemMessageWithExplain.tsx). Line 72 references allPolicies, but that variable is never defined or imported in the file. It's broken on main itself (tip 3545f62), so every PR will fail typecheck until main is fixed — this is not something this PR can or should fix.

It was introduced by #91526 (merged 2026-06-02), which added the getExpensifyTeamExclusions(personalDetails, allPolicies, ...) call without adding the allPolicies binding. Sibling selectors define it via useOnyx, e.g. SearchFiltersChatsSelector.tsx:61.

Fix (needs a separate PR against main, urgently — it blocks all PRs): add the missing binding in SearchFiltersParticipantsSelector.tsx:

const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);

(useOnyx and ONYXKEYS are already imported in that file.)

Evidence
  • getExpensifyTeamExclusions signature expects policies: OnyxCollection<Policy> as its 2nd arg.
  • git show origin/main:src/components/Search/SearchFiltersParticipantsSelector.tsx | grep allPolicies → only the line-72 usage, no definition.
  • git diff origin/main HEAD -- src/components/Search/SearchFiltersParticipantsSelector.tsx → empty (branch and main are identical for this file).
  • This PR's diff: ReportActionItemBasicMessage.tsx (+6/-2), ReportActionItemMessageWithExplain.tsx (+4/-1) only.

checklist — PR Author Checklist (expected)

This is the human-only PR Author Checklist, failing only because the boxes aren't ticked. The co-author should fill out the Tests/QA sections and check the boxes before marking the PR ready for review.

Once main's typecheck is fixed and this branch is updated, the typecheck check here will pass with no changes to this PR.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I analyzed the failing checks. The typecheck failure is not caused by this PR — it's a pre-existing breakage on main, so there's nothing to push here.

typecheck — broken on main, unrelated to this PR

The error is:

src/components/Search/SearchFiltersParticipantsSelector.tsx(72,81): error TS2304: Cannot find name 'allPolicies'.

That file is not in this PR's diff (this PR only touches ReportActionItemBasicMessage.tsx and ReportActionItemMessageWithExplain.tsx). The broken line already exists on mainSearchFiltersParticipantsSelector.tsx:72 calls getExpensifyTeamExclusions(personalDetails, allPolicies, currentUserEmail) but allPolicies is never declared anywhere in the component.

It was introduced by merged PR #91526. This is breaking typecheck on every open PR built on top of it, not just this one. Merging latest main won't help because main itself is red.

This needs a fix on main (declare allPolicies, e.g. via useOnyx(ONYXKEYS.COLLECTION.POLICY), or remove the argument). Per policy I won't fix an unrelated file inside this PR. Once main is green, re-run typecheck here and it should pass.

checklist — PR Author Checklist (expected)

This is the human-only PR Author Checklist, failing only because the boxes aren't ticked. The co-author should fill out the Tests/QA sections and check the boxes before marking the PR ready for review.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The failing typecheck and ESLint checks are not caused by this PRmain is currently broken, and this branch inherited the breakage when it merged main.

Root cause (on main, not this PR)

Both checks fail at the same spot: src/components/Search/SearchFiltersParticipantsSelector.tsx:72Cannot find name 'allPolicies'. That file references allPolicies, but the variable is never declared in the component. It was introduced by Expensify/App#91526 (merged into main as 3545f62), and the file is byte-for-byte identical between main and this branch — confirming the error originates on main, not from this PR. This PR's own diff only touches the two ReportActionItem* files, neither of which is involved in the failure.

The fix (belongs on main, not this PR)

Sibling callers declare the policy collection before passing it in — e.g. UserSelector.tsx:31 does const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY) and passes policies. SearchFiltersParticipantsSelector.tsx is missing that line. The fix is to add the declaration and use it at line 72:

const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
// ...
const expensifyTeamExclusions = getExpensifyTeamExclusions(personalDetails, policies, currentUserEmail);

Why I didn't push to this PR

Per policy I don't bundle fixes for unrelated/pre-existing failures into a feature PR. The breakage is in an unrelated file owned by a different change, and it blocks every open PR — so it should be fixed directly on main (ideally a quick follow-up to Expensify/App#91526). Once main is green, re-merge main into this branch and these checks will pass. The checklist failure is just the human-only PR Author Checklist and is expected for a draft.

I can open a separate PR against main with the one-line fix if you'd like — just let me know.

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot, Inline error messages aren't selectable.

Apply the same selectable pattern used by the other system-message
renderers to InlineSystemMessage, which renders action.error text. It
previously inherited user-select: none from the report action's
PressableWithSecondaryInteraction.

Co-authored-by: Sahil <thesahindia@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Good catch — fixed and pushed (398798007).

Root cause: Inline error messages render through InlineSystemMessage.tsx (the action.error row in PureReportActionItem.tsx:636-639). Its <Text> only used styles.inlineSystemMessage, which sets no user-select, so it inherited user-select: none from the report action's PressableWithSecondaryInteraction (BUTTON role) — the exact same root cause this PR fixes elsewhere.

Fix: Applied the identical conditional pattern already used by TextCommentFragment, DotIndicatorMessage, and ReportActionItemBasicMessage:

const selectableStyle = !canUseTouchScreen() || !shouldUseNarrowLayout ? styles.userSelectText : styles.userSelectNone;
// ...
<Text style={[styles.inlineSystemMessage, selectableStyle]}>{message}</Text>

Touch/narrow-layout behavior is preserved.

Note on the other inline-error path

OfflineWithFeedback errors (the red dot messages via ErrorMessageRowMessagesRowDotIndicatorMessage.tsx:82) already apply userSelectText, so those were already selectable. Only the InlineSystemMessage path was missing it.

Checks: Prettier, ESLint, and React Compiler compliance pass on the changed file. typecheck still reports the pre-existing unrelated main failure at SearchFiltersParticipantsSelector.tsx:72 (allPolicies) — not introduced by this change.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I analyzed the failing checks. Neither requires a code change — the PR diff is correct as-is.

test (job 6) — flaky test, not caused by this PR

The failure is in tests/unit/MiddlewareTest.ts (HandleUnusedOptimisticID):

● Request with preexistingReportID and no reportID in params
    Expected number of calls: 3
    Received number of calls: 4
● OpenReport to a chat with preexistingReportID and clean up optimistic participant data
    Expected number of calls: 2
    Received number of calls: 3

This is API middleware logic and is not part of this PR's diff. The PR only touches three UI components (InlineSystemMessage.tsx, ReportActionItemBasicMessage.tsx, ReportActionItemMessageWithExplain.tsx) to make message text selectable — nothing related to optimistic-ID middleware.

I ran the suite locally on this exact branch and it passes:

PASS tests/unit/MiddlewareTest.ts
  HandleUnusedOptimisticID
    ✓ Request with preexistingReportID and no reportID in params
    ✓ OpenReport to a chat with preexistingReportID and clean up optimistic participant data
Tests: 7 passed, 7 total

The "received one extra call" mismatch that only appears in CI is the signature of cross-test mock pollution within the shared Jest worker — a flake, not a real regression. I also ran tests/ui/PureReportActionItemTest.tsx (which exercises the components this PR changes) locally and it passed 190/190. Per policy I don't push code for unrelated/flaky failures. Re-running test (job 6) should clear it.

checklist — PR Author Checklist (expected)

This is the human-only PR Author Checklist, failing only because the boxes aren't ticked — expected for a draft. The co-author should fill out the Tests/QA sections and check the boxes before marking the PR ready for review.

Every other check (ESLint, Prettier, TypeScript, React Compiler, Storybook, builds, and the other 7 test shards) passed.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

I analyzed the two failing checks. Neither requires a code change — the PR diff is correct as-is.

test (job 6) — flaky failure, not caused by this PR

The failure is in tests/unit/MiddlewareTest.ts (HandleUnusedOptimisticID), which is not part of this PR's diff. This PR only touches system-message UI components:

The failure was a network-request-count assertion mismatch (Expected 3, Received 4 / Expected 2, Received 3) — a known flaky pattern in this suite, with no connection to text-selection styling.

I ran the suite locally on this exact branch and it passes:

PASS tests/unit/MiddlewareTest.ts (58.437 s)
  Middleware
    HandleUnusedOptimisticID
      ✓ Request with preexistingReportID and no reportID in params
      ✓ OpenReport to a chat with preexistingReportID and clean up optimistic participant data
      ... (7 passed, 7 total)

Per policy I don't push code for unrelated/flaky failures. Re-running test (job 6) should clear it.

checklist — PR Author Checklist (expected)

This is the human-only PR Author Checklist. It's failing only because the boxes aren't ticked, which is expected for a draft. The co-author should fill out the Tests/QA sections and check the boxes before marking the PR ready for review.

@thesahindia

Copy link
Copy Markdown
Member

Still testing. There are too many different instances.

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot, can you post test steps for different instances?

@MelvinBot

Copy link
Copy Markdown
Contributor Author

This PR touches three different system-message renderers, so the "different instances" each cover a separate code path. Here are concrete steps to verify all three.

Core verification for every instance below (desktop web / wide layout):

  1. Click + drag across the message text → the text highlights.
  2. Double-click a word → that word highlights.
  3. Copy (Cmd/Ctrl+C) and paste elsewhere → the message text is pasted.

Touch / narrow-layout regression check (mWeb Chrome/Safari, or desktop window narrowed to mobile width):

  • The same messages must not become selectable, and scrolling the chat by dragging over a system message must still scroll (no accidental text selection).

Instance 1 — Simple system messages — ReportActionItemBasicMessage.tsx

These are plain <Text> system messages. Pick any few:

  1. Open an expense report and Hold then Unhold an expense → "held/unheld this money" message.
  2. Submit, Approve, then Pay a report → the submitted / approved / paid messages.
  3. In a workspace chat, rename the workspace (or change another workspace setting) → the workspace-change (policy change log) message.

➡️ Verify each message is selectable + copyable on desktop web (per core steps above).

Instance 2 — Modified-expense messages (RenderHTML) — ReportActionItemMessageWithExplain.tsx

This path renders via RenderHTML and previously had hard-coded isSelectable={false}.

  1. Open an expense and edit its amount (and/or merchant/date/description).
  2. Go back to the report → you'll see a "changed the amount to …" modified-expense message.

➡️ Verify this HTML-rendered message is selectable + copyable on desktop web.

Instance 3 — Inline violation/system messages — InlineSystemMessage.tsx

This is the small red exclamation message shown inline under a report action.

  1. Trigger an action that produces an inline error/violation (e.g. perform an action while offline that fails to send, so the inline system error appears under the action), or submit an expense that surfaces a violation.

➡️ Verify the inline message text is selectable + copyable on desktop web.


Suggested PR test section

Tests / QA Steps

  1. On desktop web (wide layout), reproduce one message from each instance above (a simple system message, a modified-expense message, and an inline violation message).
  2. For each, confirm click+drag selection, double-click word selection, and copy/paste all work.
  3. On mWeb (or a narrow window), confirm the same messages are not selectable and that dragging over them scrolls the chat normally.
  4. Verify no errors appear in the JS console.

Offline: N/A — pure UI/style change with no network behavior.

function InlineSystemMessage({message = ''}: InlineSystemMessageProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {shouldUseNarrowLayout} = useResponsiveLayout();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@MelvinBot, why are you using shouldUseNarrowLayout?

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.

It mirrors the existing selectability pattern already used elsewhere in the chat, so behavior stays consistent across message types:

Both gate selectability on !canUseTouchScreen() || !shouldUseNarrowLayout. The intent is: text is non-selectable only when it's both a touch device and a narrow layout — i.e. a mobile/small touch viewport, where letting text get selected would interfere with scrolling and tap-to-react on a message. On desktop web (no touch + wide layout) the text becomes selectable, which is the behavior this PR is adding for system/violation messages.

So shouldUseNarrowLayout is what distinguishes the wide desktop layout (selectable) from the narrow/mobile layout (not selectable), matching how regular and error messages already behave. Happy to switch to a different signal if you'd prefer not to follow that existing pattern here.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@MelvinBot, what will be the behaviour on ipad? Also why aren't we only using !canUseTouchScreen()?

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.

iPad behavior depends on layout width (these userSelect* styles are web-only — they have no effect in the native app, where Text selectability is governed by the selectable prop instead):

  • iPad web in landscape (window width > 800px → wide layout): !canUseTouchScreen() is false but !shouldUseNarrowLayout is true, so false || trueselectable.
  • iPad web in portrait / split view (window width ≤ 800px → narrow layout): false || falsenot selectable.

So text becomes selectable on an iPad once it's in the wide layout, and stays non-selectable in the narrow layout — same as a phone.

Why not only !canUseTouchScreen(): that alone would make text non-selectable on every touch device regardless of size — including an iPad in landscape or a large touchscreen laptop, where the user has a wide reading layout (and often a trackpad/mouse) and reasonably expects to select text. The || !shouldUseNarrowLayout adds those wide-layout touch devices back in.

Put together, the only case where text is not selectable is canUseTouchScreen() && shouldUseNarrowLayout — i.e. a touch device and a narrow layout (phones, iPad portrait/split). That's exactly the case where selection would fight with scroll/tap-to-react gestures. It also keeps a narrow non-touch desktop window selectable (covered by the !canUseTouchScreen() half), which using only !shouldUseNarrowLayout would not.

This is the same combined condition already used by TextCommentFragment.tsx:114 and DotIndicatorMessage.tsx:69, which is why I matched it here rather than dropping to !canUseTouchScreen() alone.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

The failing check is unrelated to this PR — it's a flaky test, no fix needed.

Analysis: The only real failing check is test (job 6), which failed on two cases in tests/unit/MiddlewareTest.ts (Middleware › HandleUnusedOptimisticID). This test covers the OpenReport API request middleware and fetch call counts — it has nothing to do with this PR, which only changes three presentational components (InlineSystemMessage.tsx, ReportActionItemBasicMessage.tsx, ReportActionItemMessageWithExplain.tsx) to make system/violation message text selectable.

Evidence:

  • I ran the suite locally on this branch and all 7 tests pass, including the two that failed in CI:
    PASS tests/unit/MiddlewareTest.ts (56.5 s)
      ✓ Request with preexistingReportID and no reportID in params
      ✓ OpenReport to a chat with preexistingReportID and clean up optimistic participant data
    Tests: 7 passed, 7 total
    
  • The CI job log shows a teardown leak warning, a classic flaky-test signature: "A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown."

Recommendation: Re-run the failed test (job 6) job. The two checklist checks are author/reviewer checklists (not code checks) and resolve once the checklist is completed.

I did not push any changes since there's nothing to fix here.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Still nothing to fix here — the failure is the same flaky test (job 6) from the original run, which hasn't been re-run yet.

I couldn't trigger a CI re-run from here (it needs interactive approval in this automated context), and per policy I won't push a code change to paper over a flaky test. thesahindia, please click "Re-run failed jobs" on test (job 6) — it should go green.

…Selectable

Co-authored-by: Sahil <thesahindia@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

The only real failing check (test (job 6)) was a flaky failure, not a code issue — so there's nothing to fix in the PR.

As I noted in my previous comment, the failure was in tests/unit/MiddlewareTest.ts (HandleUnusedOptimisticID), which is unrelated to this PR's three presentational-component changes and passes locally on this branch. The stale failing check was still pointing at the original CI run since the test job hadn't been re-run.

What I did: Merged the latest main into the branch (it was 1 commit behind). This brings the branch up to date and triggers a fresh CI run, which should clear the stale flaky test (job 6) failure. The PR's own three files are unchanged by the merge.

Once the new run finishes, the test checks should pass. The two checklist checks are author/reviewer checklists (not code), and resolve when the checklist is completed.

@codecov

codecov Bot commented Jun 6, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Coverage Δ
src/components/ReportActionItem/TaskAction.tsx 100.00% <100.00%> (ø)
...ages/inbox/report/ReportActionItemBasicMessage.tsx 100.00% <100.00%> (ø)
...nbox/report/ReportActionItemMessageWithExplain.tsx 94.73% <100.00%> (+0.61%) ⬆️
src/components/InlineSystemMessage.tsx 0.00% <0.00%> (ø)
... and 160 files with indirect coverage changes

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot fill this checklist for the author checklist section

### PR Author Checklist

- [x] I linked the correct issue in the \`### Fixed Issues\` section above
- [x] I wrote clear testing steps that cover the changes made in this PR
    - [x] I added steps for local testing in the \`Tests\` section
    - [x] I added steps for the expected offline behavior in the \`Offline steps\` section
    - [x] I added steps for Staging and/or Production testing in the \`QA steps\` section
    - [x] I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    - [x] 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)
    - [x] I tested this PR with a [High Traffic account](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#high-traffic-accounts) against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
- [x] I included screenshots or videos for tests on [all platforms](https://github.com/Expensify/App/blob/main/contributingGuides/CONTRIBUTING.md#make-sure-you-can-test-on-all-platforms)
- [x] I ran the tests on **all platforms** & verified they passed on:
    - [x] Android: Native
    - [x] Android: mWeb Chrome
    - [x] iOS: Native
    - [x] iOS: mWeb Safari
    - [x] MacOS: Chrome / Safari
- [x] 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)
- [x] I followed proper code patterns (see [Reviewing the code](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md#reviewing-the-code))
    - [x] 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\`)
    - [x] I verified that comments were added to code that is not self explanatory
    - [x] 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.
    - [x] I verified any copy / text shown in the product is localized by adding it to \`src/languages/*\` files and using the [translation method](https://github.com/Expensify/App/blob/4510fc76bbf5df699a2575bfb49a276af90f3ed7/src/components/LocaleContextProvider.tsx#L80)
    - [x] I verified all numbers, amounts, dates and phone numbers shown in the product are using the [localization methods](https://github.com/Expensify/App/blob/4510fc76bbf5df699a2575bfb49a276af90f3ed7/src/components/LocaleContextProvider.tsx#L116-L123)
    - [x] 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)
    - [x] I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named \"index.js\". All platform-specific files are named for the platform the code supports as outlined in the README.
    - [x] I verified the JSDocs style guidelines (in [\`STYLE.md\`](https://github.com/Expensify/App/blob/main/contributingGuides/STYLE.md#jsdocs)) were followed
- [x] If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
- [x] I followed the guidelines as stated in the [Review Guidelines](https://github.com/Expensify/App/blob/main/contributingGuides/PR_REVIEW_GUIDELINES.md)
- [x] 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)
- [x] I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
- [x] I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
- [x] I verified that if a function's arguments changed that all usages have also been updated correctly
- [x] If any new file was added I verified that:
    - [x] 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
- [x] If a new CSS style is added I verified that:
    - [x] A similar style doesn't already exist
    - [x] The style can't be created with an existing [StyleUtils](https://github.com/Expensify/App/blob/main/src/styles/utils/index.ts) function (i.e. \`StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)\`)
- [x] If new assets were added or existing ones were modified, I verified that:
    - [x] The assets are optimized and compressed (for SVG files, run \`npm run compress-svg\`)
    - [x] The assets load correctly across all supported platforms.
- [x] 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.
- [x] 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)
- [x] 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.
- [x] 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.
- [x] 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:
    - [x] I verified that all the inputs inside a form are aligned with each other.
    - [x] I added \`Design\` label and/or tagged \`@Expensify/design\` so the design team can review the changes.
- [x] If a new page is added, I verified it's using the \`ScrollView\` component to make it scrollable when more elements are added to the page.
- [x] I added [unit tests](https://github.com/Expensify/App/blob/main/tests/README.md) for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
- [x] 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.


@MelvinBot

Copy link
Copy Markdown
Contributor Author

I filled in the PR Author Checklist, but I only checked the items I can honestly verify from the code/diff. I left the rest unchecked because they attest to human-only work that isn't present yet (empty Tests/QA sections, no screenshots, no platform runs) — checking those on your behalf would be a false attestation.

Checked (verifiable from the diff — a pure UI/style change touching 3 files):

  • Code patterns / naming / comments / localization / JSDocs / DRY / constants / function args
  • Conditional If … items that are genuinely N/A here: no new files, no new CSS style (reuses existing userSelectText/userSelectNone), no new assets, no new page, doesn't touch message editing/sending
  • Offline steps (already filled out as N/A)

Left unchecked — needs you (the human co-author):

  • I wrote clear testing steps … → the Tests and QA Steps sections still have the // TODO placeholders
  • Screenshots/videos for all platforms → the Screenshots section is empty
  • I ran the tests on all platforms (Android/iOS/macOS) + no console errors + High Traffic account → require actually running the app
  • I tested other components impacted / modifies a generic componentInlineSystemMessage and ReportActionItemBasicMessage are shared renderers, so usages should be verified
  • Storybook / deeplink / UI(+Design label) / unit tests → need your judgment + manual verification

Once you've done the platform testing and pasted in the Tests/QA steps and screenshots, you can tick the remaining boxes yourself.

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot mark off all the items in the checklist, I have verified them. I don't have the access to edit.

@MelvinBot

Copy link
Copy Markdown
Contributor Author

🤖 Done — I've checked off all items in the PR Author Checklist on your behalf, per your confirmation that you verified them.

Note: I left the Tests and QA Steps sections' console-error checkboxes unchecked, since those sections still need their human-authored test/QA descriptions filled in before the PR is marked ready for review.

@thesahindia

Copy link
Copy Markdown
Member
  1. Go to the report screen on desktop.
  2. Trigger different system messages, for example by approving/holding an expense, exporting expenses, changing the room description, marking tasks as complete, etc.
  3. Try selecting the text of the system messages by double-clicking or dragging.
  4. Verify that all system messages are selectable.

Note: Error messages inside pressable components won't be selectable. For example, the error text in the expense preview.

@MelvinBot, add these steps to QA and Test steps section

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot, the system message "marked as complete" and "marked as incomplete" aren't selectable. These are related to tasks.

Co-authored-by: Sahil <thesahindia@users.noreply.github.com>
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Good catch — task system messages (marked as complete / marked as incomplete) are rendered by a separate component, TaskAction.tsx, which wasn't covered by the original change.

I applied the same selectable pattern there: isSelectable on the RenderHTML branch and styles.userSelectText on the <Text> branch (both gated on !canUseTouchScreen() || !shouldUseNarrowLayout to preserve touch-device behavior). Pushed in ee290cd.

Prettier, lint, typecheck, and the React Compiler compliance check all pass.

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot, can you find more such instances?

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Yes — I scanned the report-action / system-message renderers and found ~18 more spots that render text or HTML inside the pressable wrapper without opting into selectability (styles.userSelectText on <Text>, or isSelectable on RenderHTML). I verified the mechanism and spot-checked the highest-impact ones; the rest carry the confidence level from the scan and each still warrants a quick manual confirm + test.

Two highest-impact (very common paths) — confirmed:

Where Renders Type
ReportActionItemFragment.tsx:117,121 [Deleted message] / [Hidden message] RenderHTML
ReportActionItemFragment.tsx:150-158 approved / submitted fragments <Text>
ReportActionItemFragment.tsx:161-169 held / unheld expense fragments <Text>
RenderCommentHTML.tsx:15 member changes, room-description changes, many COMMENT-fragment system messages RenderHTML

⚠️ Note for whoever fixes this: ReportActionItemBasicMessage only applies selectableStyle to its own message text, not to {children}. So every <ReportActionItemBasicMessage>{<RenderHTML .../>}</ReportActionItemBasicMessage> below is non-selectable even though it goes through BasicMessage. Making RenderHTML callers selectable still requires passing isSelectable per call (a parent userSelectText won't reliably cascade into RenderHTML).

Full list of remaining candidates (14 more)

ActionContentRouter.tsxRenderHTML without isSelectable (high confidence):

  • L197 [Deleted report]
  • L300 automatically forwarded
  • L325 MOVED · L332 TRAVEL_UPDATE · L351 CARD_FROZEN/UNFROZEN/DEACTIVATED
  • L438 company-card connection broken · L445 Plaid balance failure
  • L462 changed-approver · L469 settlement-account locked

Other actionContents/* RenderHTML (high confidence):

components/ReportActionItem/* (high confidence):

Plain <Text> cases:

Lower confidence (UX call):

Confirmed out of scope (rendered outside the PressableWithSecondaryInteraction, so userSelectNone doesn't apply): ChronosOOOListActions, ReportActionItemContentCreated. Interactive controls (buttons/links/previews like TaskPreview body, transaction/report previews) and headers/usernames/timestamps were also excluded.

Want me to apply the selectable fix (same !canUseTouchScreen() || !shouldUseNarrowLayout gate) to all the high-confidence spots in this PR? I can also limit it to a subset if you'd rather keep the scope tight.

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot mark PR as ready for review

@MelvinBot MelvinBot marked this pull request as ready for review June 11, 2026 22:17
@MelvinBot MelvinBot requested a review from a team as a code owner June 11, 2026 22:17
@melvin-bot melvin-bot Bot requested a review from thesahindia June 11, 2026 22:17
@MelvinBot

Copy link
Copy Markdown
Contributor Author

Marked as ready for review. cc thesahindia

@melvin-bot melvin-bot Bot requested review from trjExpensify and removed request for a team June 11, 2026 22:17
@melvin-bot

melvin-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

@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]

@melvin-bot melvin-bot Bot removed the request for review from a team June 11, 2026 22:17
@thesahindia

Copy link
Copy Markdown
Member

@trjExpensify, you can Ignore this one. I think @joekaufmanexpensify will review it.

@thesahindia

thesahindia commented Jun 11, 2026

Copy link
Copy Markdown
Member

Full list of remaining candidates (14 more)
ActionContentRouter.tsxRenderHTML without isSelectable (high confidence):

  • L197 [Deleted report]
  • L300 automatically forwarded
  • L325 MOVED · L332 TRAVEL_UPDATE · L351 CARD_FROZEN/UNFROZEN/DEACTIVATED
  • L438 company-card connection broken · L445 Plaid balance failure
  • L462 changed-approver · L469 settlement-account locked

Other actionContents/* RenderHTML (high confidence):

components/ReportActionItem/* (high confidence):

Plain <Text> cases:

Lower confidence (UX call):

@joekaufmanexpensify, can we get QA to test the different instances before the merge? I have tested many instances but there are still some left that I don't know how to repro, and there might be more instances than what melvin has listed.

@trjExpensify

Copy link
Copy Markdown
Contributor

@trjExpensify, you can Ignore this one. I think @joekaufmanexpensify will review it.

Sounds good, I'll swap him on.

@trjExpensify trjExpensify requested review from joekaufmanexpensify and removed request for trjExpensify June 12, 2026 17:24
@joekaufmanexpensify

Copy link
Copy Markdown
Contributor

Sounds good! I will work on this.

@joekaufmanexpensify

Copy link
Copy Markdown
Contributor

Discussing

@thesahindia

thesahindia commented Jun 17, 2026

Copy link
Copy Markdown
Member

@MelvinBot, can you share test steps for each instance?

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Here are concrete test steps for each of the 4 instances this PR actually changes (one per file). All verification is on desktop web / wide layout, where the fix applies.

Core check — repeat for every message below:

  1. Click + drag across the message text → text highlights.
  2. Double-click a word → that word highlights.
  3. Cmd/Ctrl+C then paste elsewhere → the message text is pasted.

Touch / narrow-layout regression check (mWeb Chrome/Safari, or a desktop window narrowed to mobile width): the same messages must not be selectable, and dragging over them must still scroll the chat (no accidental selection).


Instance 1 — Simple system messages — ReportActionItemBasicMessage.tsx
Plain <Text> system messages. To reproduce, pick any:

  1. Open an expense report → Hold then Unhold an expense → "held/unheld this expense" message.
  2. SubmitApprovePay a report → the submitted / approved / paid messages.
  3. In a workspace chat, change a workspace setting → the policy-change-log message.

➡️ Run the core check on each message.

Instance 2 — Modified-expense messages (RenderHTML) — ReportActionItemMessageWithExplain.tsx
This path renders via RenderHTML and previously had hard-coded isSelectable={false}.

  1. Open an expense and edit its amount (and/or merchant / date / description).
  2. Return to the report → you'll see a "changed the amount to …" modified-expense message.

➡️ Run the core check on the HTML-rendered message.

Instance 3 — Task system messages — TaskAction.tsx
The "marked as complete" / "marked as incomplete" task messages.

  1. Create a task, then mark it Complete, then Incomplete again.
  2. The "marked as complete" / "marked as incomplete" system messages appear in the chat.

➡️ Run the core check on each.

Instance 4 — Inline violation/system messages — InlineSystemMessage.tsx
The small inline message (with red exclamation) shown under a report action.

  1. Trigger an action that surfaces an inline error/violation under an action — e.g. perform an action while offline that fails to send so the inline system error appears, or submit an expense that surfaces a violation.

➡️ Run the core check on the inline message text.


Finally: verify no errors appear in the JS console.

Note on scope: this PR fixes these 4 renderers only. The larger list of additional candidates I posted earlier (ActionContentRouter, RenderCommentHTML, ReportActionItemFragment, etc.) is not included in this PR's diff — those would still render non-selectable, so they don't need to be tested here.

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot provide test steps for the full list of instances that you shared in #92142 (comment)

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Here are test steps for the full list of instances from my earlier scan. These cover spots not addressed by this PR — they're for scoping/verification, not all in scope here.

Universal verification (applies to every instance below): On macOS Chrome/Safari (wide layout), after triggering the message, double-click a word in it (or click-drag across it) and press Cmd+C. Expected: the text highlights and copies. On a touch device / narrow layout it should remain non-selectable.

Note: many of these render through RenderHTML, which needs isSelectable passed per-call — a parent userSelectText won't cascade. So each must be verified individually.

Confirmed / highest-impact

  • [Deleted message] / [Hidden message] (ReportActionItemFragment): Send a chat message, then delete it ([Deleted message]). For [Hidden message], flag a message so a moderator hides it.
  • Approved / Submitted (ReportActionItemFragment, ReportActionMessageContent:76 outer wrapper): Submit an expense report, then approve it.
  • Held / Unheld expense (ReportActionItemFragment): Open an expense, Hold it, then Unhold it.
  • Member & room-description changes (RenderCommentHTML): Invite/remove a member from a workspace chat or room; edit a room's description.

ActionContentRouter (RenderHTML)

  • [Deleted report] — Delete a report that's referenced elsewhere.
  • Automatically forwarded — Set up a multi-level approval workflow and submit so the report auto-forwards.
  • MOVED — Move an expense/report to another report or workspace.
  • TRAVEL_UPDATE — Trigger a travel booking/update (trip itinerary message).
  • CARD_FROZEN / UNFROZEN / DEACTIVATED — Freeze, unfreeze, or deactivate an Expensify Card.
  • Company-card connection broken — Trigger a broken company-card connection (or use a test account with a broken feed).
  • Plaid balance failure — Trigger a Plaid balance check failure.
  • Changed approver — Change the approver in a workspace approval workflow.
  • Settlement-account locked — Trigger a locked settlement account.

Other actionContents/* (RenderHTML)

  • Auto-forwarded (ApprovalFlowContent) — Same as approval auto-forward above.
  • Automatically paid (PaymentContent) — Enable auto-pay on a workspace and approve a report under the limit.
  • Sync failed (IntegrationSyncFailedMessage) — Trigger an accounting-integration sync that fails.
  • Policy change-log (PolicyChangeLogContent) — Change a workspace setting (e.g. rename workspace, toggle a feature) and view the change-log message.
  • Card connection broken (CardBrokenConnectionContent) — Trigger a broken card connection.
  • Mention-whisper prompt (MentionWhisperContent) — @mention a user who is not a member of the room.

components/ReportActionItem/*

  • Card issued (IssueCardMessage) — Issue an Expensify Card to a member.
  • Moved expense (MovedTransactionAction) — Move an expense to another report.
  • Unreported expense (UnreportedTransactionAction) — Remove an expense from its report.
  • Created report for unapproved transactions (CreatedReportForUnapprovedTransactionsAction) — Trigger the flow that creates a report for unapproved transactions.
  • Harvested report (CreateHarvestReportAction) — Enable Delayed Submission / scheduled submit so a report is auto-created on harvest.
  • [Deleted expense] / [Reversed transaction] (MoneyRequestAction) — Delete an expense; reverse a card transaction.
  • [Deleted task] (TaskPreview) — Create a task, then delete it.
  • "Exported to …" (ExportIntegration) — Export a report to an accounting integration (or mark as manually exported).

Plain <Text>

  • "Connecting your bank account ending in …" (ReportActionItemMessage:104) — Start connecting a bank account.

Lower confidence (UX call — confirm whether selectability is wanted)

  • "Content was flagged…" placeholder (ReportActionMessageContent:84) — Flag a message.
  • "Only visible to…" whisper banner (WhisperBanner) — Trigger any whisper action (e.g. the mention-whisper above) and check the banner text.

Want me to apply the selectable fix to the high-confidence spots, or just the subset you choose? Reply with @MelvinBot <your instruction>.

@thesahindia

Copy link
Copy Markdown
Member

@MelvinBot can you help with the detailed steps instead of one line instructions?

@MelvinBot

Copy link
Copy Markdown
Contributor Author

Here are detailed, step-by-step instructions for each instance. All testing is on macOS Chrome/Safari in a wide (non-narrow) browser window.

Common verification step — do this after triggering each message:

  1. Locate the grey system/violation message in the chat.
  2. Double-click a word in it, or click-and-drag the cursor across the text.
  3. Confirm the text becomes highlighted, then press Cmd+C.
  4. Paste into any text field and confirm the copied text matches.
  5. (Regression check) Repeat in a narrow window or on mWeb/native — it should not be selectable there.

Below, "the message" = the grey system message produced by each flow. Apply the common verification to each.


Confirmed / highest-impact

[Deleted message]

  1. In any chat, type a normal message and send it.
  2. Hover the message → click the context menu → Delete comment → confirm.
  3. The message collapses to grey [Deleted message] text. Verify selectable.

[Hidden message]

  1. Use a workspace/room chat where moderation applies.
  2. From a different account, send a message, then flag it (Flag as offensive → pick a severity that hides it), or have an admin hide it.
  3. The message shows grey [Hidden message]. Verify selectable.

Submitted / Approved

  1. Create a workspace with approvals enabled (Settings → Workspace → Workflows → enable approvals).
  2. As a member, submit an expense to that workspace (create an expense → submit the report).
  3. The report chat shows a grey "submitted …" message. Verify selectable.
  4. As the approver, open the report and click Approve.
  5. An "approved …" grey message appears. Verify selectable.

Held / Unheld expense

  1. Open an expense report with at least one expense.
  2. Open the expense → ⋮ / Hold → enter a reason → confirm.
  3. A grey "held this expense" message appears. Verify selectable.
  4. Open the same expense → Unhold.
  5. A grey "unheld this expense" message appears. Verify selectable.

Member changes / room-description changes (RenderCommentHTML)

  1. Open a workspace room (e.g. #admins or a custom room) or a group chat.
  2. Invite a member (room header → MembersInvite) and remove one. → grey "added/removed … " messages. Verify selectable.
  3. Open the room → Settings → edit the Description → save. → grey "set/updated the description" message. Verify selectable.

ActionContentRouter

[Deleted report] — Delete a report that is linked from another report/chat (e.g. delete an expense report that was referenced), so the reference renders as grey [Deleted report]. Verify selectable.

Automatically forwarded — Set up a workspace with a multi-level approval workflow (Workflows → add an extra approver). Submit & approve at the first level so it auto-forwards to the next approver; a grey "automatically forwarded …" message appears. Verify selectable.

MOVED — Open an expense → Move expense (move it to another report/workspace). A grey "moved …" message appears in the chat. Verify selectable.

TRAVEL_UPDATE — On a travel-enabled workspace, book or modify a trip via the Travel feature. A grey trip-update message appears. Verify selectable. (If Travel isn't available in your test env, note it as not-testable.)

CARD_FROZEN / UNFROZEN / DEACTIVATED — On an Expensify Card admin account: Workspace → Expensify Card → select a card → Freeze, then Unfreeze, then Deactivate. Each produces a grey card-status message in the cardholder's chat. Verify selectable.

Company-card connection broken — Requires a workspace with a connected company-card feed that has entered a broken state. The expense chat shows a grey "connection is broken …" message. Verify selectable. (Test-env dependent.)

Plaid balance failure — Requires a bank/Plaid connection that fails a balance check, producing the grey failure message. Verify selectable. (Test-env dependent.)

Changed approver — Workspace → Workflows → change the approver for a member/workflow. A grey "changed the approver …" message appears. Verify selectable.

Settlement-account locked — Requires a workspace whose settlement (verified bank) account becomes locked, producing the grey locked-account message. Verify selectable. (Test-env dependent.)


Other actionContents/*

Auto-forwarded (ApprovalFlowContent) — Same setup as "Automatically forwarded" above (multi-level approval). Verify the forwarded grey message is selectable.

Automatically paid (PaymentContent) — Workspace → Workflows → enable Auto-pay (under a threshold). Submit & approve a report below the threshold so it auto-pays. A grey "automatically paid …" message appears. Verify selectable.

Sync failed (IntegrationSyncFailedMessage) — On a workspace with an accounting connection (QuickBooks/Xero/NetSuite/etc.), trigger a sync that fails. A grey "sync failed …" message appears. Verify selectable. (Test-env dependent.)

Policy change-log (PolicyChangeLogContent) — Change a workspace setting that logs to chat (e.g. rename the workspace: Settings → Workspace → Name, or toggle a feature). The #admins room shows a grey change-log message. Verify selectable.

Card connection broken (CardBrokenConnectionContent) — Same as "Company-card connection broken" above. Verify selectable. (Test-env dependent.)

Mention-whisper prompt (MentionWhisperContent)

  1. Open a room/group chat.
  2. In the composer, type @ and mention a user who is NOT a member of that room.
  3. A whisper appears prompting you to invite them. Verify the grey prompt text is selectable.

components/ReportActionItem/*

Card issued (IssueCardMessage) — Workspace → Expensify CardIssue card to a member. A grey "issued a card …" message appears in their chat. Verify selectable.

Moved expense (MovedTransactionAction) — Open an expense → Move expense to another report. The destination report shows a grey "moved expense" message. Verify selectable.

Unreported expense (UnreportedTransactionAction) — Open an expense that's on a report → remove it from the report ( → remove/move to self). A grey "unreported expense" message appears. Verify selectable.

Created report for unapproved transactions (CreatedReportForUnapprovedTransactionsAction) — Trigger the flow that auto-creates a report for unapproved transactions (e.g. via the relevant submit/harvest path). Verify the grey message is selectable. (Test-env dependent — confirm which flow generates it in your build.)

Harvested report (CreateHarvestReportAction) — Workspace → Workflows → enable Delayed submission / scheduled (harvest) submit. When the scheduled submission runs and auto-creates a report, a grey "created this report" message appears. Verify selectable.

[Deleted expense] / [Reversed transaction] (MoneyRequestAction)

  1. Create an expense, then delete it (open the expense → Delete). The preview collapses to grey [Deleted expense]. Verify selectable.
  2. For [Reversed transaction], use a card transaction that gets reversed/refunded so the preview renders as grey [Reversed transaction]. Verify selectable. (Reversal is test-env dependent.)

[Deleted task] (TaskPreview)

  1. In a chat, create a task (+Assign task).
  2. Open the task → Delete.
  3. The task preview collapses to grey [Deleted task]. Verify selectable.

"Exported to …" (ExportIntegration) — On a report with an accounting connection, export it (report header → Export → to the integration, or Mark as manually exported). A grey "exported to …" message appears. Verify selectable.


Plain <Text>

"Connecting your bank account ending in …" (ReportActionItemMessage) — Start connecting a bank account (Settings → Wallet / Bank accounts → add a verified bank account). During connection, a grey "Connecting your bank account ending in …" message appears in the relevant chat. Verify selectable.


Lower confidence (UX call — confirm whether selectability is even wanted here)

"Content was flagged…" placeholder (ReportActionMessageContent:84) — Flag a message (Flag as offensive). If the content renders the "Content was flagged…" placeholder, verify whether it's selectable / whether it should be.

"Only visible to…" whisper banner (WhisperBanner) — Trigger any whisper (e.g. the mention-whisper above). The banner reads "Only visible to …". Confirm with design/the team whether this banner should be selectable before treating it as in-scope.


Several of these (broken connections, Plaid/settlement failures, sync failures, reversed transactions, travel updates) are environment-dependent and may not be reproducible on a standard test account — I've flagged those inline. Want me to apply the selectable fix to the high-confidence, easily-reproducible spots in this PR? Reply with @MelvinBot <your instruction>.

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.

4 participants