Skip to content

Fix infinite amount and pending rate when submitting a self DM distance expense quickly - #99665

Merged
chuckdries merged 10 commits into
Expensify:mainfrom
mukhrr:fix/99315
Sep 8, 2026
Merged

chuckdries merged 10 commits into
Expensify:mainfrom
mukhrr:fix/99315

Conversation

@mukhrr

@mukhrr mukhrr commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

Clicking "Submit to a friend" snapshots the tracked expense into a draft with Onyx.set. Submit before that expense has finished being written and the snapshot has no customUnitRateID and no quantity, and because it is a set it never catches up. With no rate ID the confirmation page cannot resolve a rate, so the Rate row is stuck on "pending" and the amount is back-calculated by dividing by the missing quantity, which renders as ♾️.

Three changes, the first two reading data already in Onyx so the rate and amount fill in without waiting on the backend:

  1. addParticipant selects the destination workspace's rate unless the draft holds an explicit FAKE_P2P rate or one that workspace already owns. Previously the whole block was skipped for any tracked-expense move onto a workspace, so a draft carrying a missing or foreign rate ID was never repaired. Non-distance drafts are left alone: a per diem rate ID, and a time expense's absent one, are not mileage rates.
  2. getDistanceInMeters falls back to comment.customUnit.routeDistanceMeters when there is no quantity and no route. quantity still takes priority, so manual distance edits are unaffected. hasRoute deliberately does not take the same fallback, and now carries a comment saying why.
  3. useDistanceRequestState and MoneyRequestView no longer back-calculate a rate from a quantity that has not been written yet. Dividing by 0 is what produced the ♾️; substituting 1 only defers it, because the amount then becomes the rate and compounds back into the amount on every render.

Fixed Issues

$ #99315
PROPOSAL: #99315 (comment)

Tests

  1. Sign in on an account with at least one workspace that has distance rates enabled.
  2. Go to the self DM.
  3. Create a distance expense with two waypoints and save it.
  4. As soon as the expense preview appears, and while its map is still generating, click Submit to a friend on the whisper.
  5. In the participant picker, select the default workspace's chat (the one OpenApp returns on sign in).
  6. Verify the confirmation page opens with the Rate row showing that workspace's distance rate, and that the row is tappable so the rate can still be changed.
  7. Verify the Amount shows distance × rate, not $∞.
  8. Verify no "Rate not valid for this workspace. Please select an available rate from the workspace." error is shown.
  9. Repeat steps 2 to 5 but wait for the map to finish generating before submitting, and verify nothing changed for that case.
  10. Clear the browser cache (or sign out and back in), go back to the self DM, repeat steps 3 and 4, and this time select a non-default workspace chat. OpenApp does not return that workspace's rates, so the following is expected behaviour and not a bug:
    • The Distance row keeps the unit and value the expense was created with. It stays in miles, it is not restated in kilometres.
    • "Rate not valid for this workspace. Please select an available rate from the workspace." is shown, and submitting is blocked until a rate is picked manually.
  • Verify that no errors appear in the JS console

Offline tests

  1. Create a distance expense in the self DM while online, so the route resolves.
  2. Turn off the network connection.
  3. Click Submit to a friend and select the default workspace's chat.
  4. Verify the Rate row shows that workspace's rate from cached policy data and the Amount is calculated from the stored route distance, rather than showing a pending rate or $∞. A non-default workspace whose rates were never cached still shows the "rate not valid" error, as in step 10 of the Tests section.

QA Steps

Same as tests.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • 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
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • 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_app.mp4
Android: mWeb Chrome
android_web.mp4
iOS: Native
IOS_app.mp4
iOS: mWeb Safari
IOS_web.mp4
MacOS: Chrome / Safari
web.mp4

…s none

Submitting a self DM distance expense before it finishes being written snapshots
a draft with no customUnitRateID and no quantity. The confirmation page then
cannot resolve a rate, so the rate row is stuck on "pending" and the amount is
back-calculated against a missing quantity, rendering as an infinity symbol.

Keep only an explicit p2p rate when moving a tracked expense onto a workspace,
and fall back to routeDistanceMeters when no other distance is available.
…ates loaded

getCustomUnitRateID falls back to the p2p rate when the destination policy has
no distance custom unit yet. Only the default workspace is guaranteed full
customUnits from OpenApp, so selecting against an unloaded one would replace a
real workspace rate ID with the p2p sentinel.
@mukhrr
mukhrr marked this pull request as ready for review September 1, 2026 02:41
@mukhrr
mukhrr requested review from a team as code owners September 1, 2026 02:41
@melvin-bot
melvin-bot Bot requested review from JmillsExpensify and ZhenjaHorbach and removed request for a team September 1, 2026 02:41
@melvin-bot

melvin-bot Bot commented Sep 1, 2026

Copy link
Copy Markdown

@ZhenjaHorbach 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 September 1, 2026 02:41

@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: eea0ac2553

ℹ️ 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/hooks/useParticipantSubmission.ts
A tracked per diem carries a per-diem rate ID that is never among the
destination workspace's mileage rates, so the replacement would have swapped it
for a mileage rate the per-diem custom unit cannot resolve.
@mukhrr

mukhrr commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

@ZhenjaHorbach this is ready.. let's move forward. thanks

JmillsExpensify
JmillsExpensify previously approved these changes Sep 1, 2026

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

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

@codex
Review this PR

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

@MelvinBot
Review this PR

@ZhenjaHorbach

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 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
    • 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
    • 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 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
Android: mWeb Chrome
iOS: HybridApp
iOS: mWeb Safari
MacOS: Chrome / Safari

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 650ad60aa4

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

@MelvinBot

MelvinBot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

🤖 Reviewed. The fix works — I reproduced the flow on web and the Rate row resolves the workspace rate and the Amount is finite. CI is green and the unit tests cover the interesting branches (per diem, time, explicit p2p, unloaded rates, already-owned rate).

Four things worth mukhrr's answer before approval. None of them break the happy path.

1. There is already a self-heal path for this on the confirmation page

DistanceRequestController.tsx:188-224 already re-resolves the destination workspace's rate when the rate ID is unset — and isUnsetDistanceCustomUnitRateID returns true for a missing ID, which is exactly the reported state. It bails early on !lastSelectedRate, so it never fires for a workspace the user has never picked a rate on.

Relaxing that one guard would fix the bug in the place that already owns this decision. Instead the PR adds a second decision in addParticipant with different criteria (destination-rate membership vs. unset-ness). Two places now decide the same thing by different rules, which the DRY item on the checklist is about. Why not the existing effect?

2. The unloaded-rates escape hatch can strand a foreign rate

useParticipantSubmission.ts:276 keeps the current rate when isEmptyObject(destinationRates). For a non-default workspace, customUnits arrive lazily, so a tracked expense carrying another workspace's rate keeps it. Once the rates do load nothing re-runs, because the effect in point 1 only fires for unset IDs — a set-but-foreign ID sails past it. That leaves the "Rate not valid for this workspace" error from your test step 8 still reachable.

Not a regression (the old code always kept the rate), but it is inside the stated scope of the fix.

3. The $∞ divisor is still unguarded

useDistanceRequestState.ts:90 is where ♾️ comes from:

const rate = calculateFromTransactionData ? Math.abs(iouAmount) / (customUnit?.quantity ?? 1) : distanceRate;

?? covers null/undefined but not 0. The PR fixes the inputs so the branch usually isn't taken, but calculateFromTransactionData is still reachable through point 2. Changing ?? to || kills the symptom for every remaining path — one character, and it means no future regression can put $∞ in front of a user.

4. hasRoute was not taught the same fallback

getDistanceInMeters now accepts routeDistanceMeters as a distance, but hasRoute still only looks at routes.route0.geometry.coordinates or quantity !== undefined. For a transaction with routes cleared and quantity genuinely undefined, the two helpers now disagree about whether the same transaction has a route. hasRoute feeds isDistanceRequestWithPendingRoute, which blanks the amount, swaps the CTA text, and writes the waypoints pending field. Your flow short-circuits it via isMovingTransactionFromTrackExpense, so nothing is broken today — but the inconsistency is now baked in. Either give hasRoute the same fallback or leave a comment saying why it deliberately does not get one.

Nit on the description

It says the old skip used isUnsetDistanceCustomUnitRateID. The old skip was the if (!isMovingTransactionFromTrackExpense || !isPolicyExpenseChat) guard, and that helper does not appear in the diff. Reviewers will go looking for code that is not there.


🤖 Web test steps verification results: ⚠️ partial

I ran your test steps on dev NewDot web on fix/99315 with a fresh workspace (default rate €0.76/mile). Submitting while the preview map was still generating landed on Confirm details with Rate: Default Rate, Distance: 23.35 miles, Amount: €17.75 — no pending rate, no $∞, no invalid-rate error. Two caveats: the console-error check could not be run directly, and the map thumbnail stayed a grey placeholder even after waiting, so step 9 did not truly exercise the "route finished" state.

Both expenses in the workspace report

Step Status
Create a distance expense with two waypoints in the self DM and save itSet Start (1600 Amphitheatre Pkwy) and Stop (SFO) waypoints via Track distance in "Your space", saved successfully.
Self DM distance expense with two waypoints
Submit to a friend while the map is still generating, pick a workspace chat, Rate row shows the workspace rate not "pending"Caught the whisper with the map placeholder still grey, clicked Submit to a friend immediately, picked the workspace. Confirm details showed Rate: Default Rate and never showed "pending".
Whisper with map still generating
Choosing the workspace chat
Confirm details showing Default Rate and finite amount
The Rate row is still tappable so the rate can be changedThe Rate row is a button in the accessibility tree; tapping it opened the workspace reimbursement rate selector with Default Rate (€0.76/mile) selectable.
Rate row opens the workspace rate selector
Amount shows distance × rate, not $∞23.35 miles × €0.76/mile ≈ €17.75, matching the displayed Amount exactly. No infinity symbol anywhere.
Confirm details showing Default Rate and finite amount
No "Rate not valid for this workspace. Please select an available rate from the workspace." errorThat error text never appeared on Confirm details in either run.
Confirm details showing Default Rate and finite amount
Repeat but wait for the map to finish generating, verify nothing changed for that caseSecond expense, waited ~13s before clicking Submit to a friend. Confirm details again showed Default Rate, 23.35 miles, €17.75. Caveat: the map thumbnail stayed a grey placeholder in this headless dev environment even after waiting, so both runs effectively exercised the "still generating" state — the true "route finished" path is unconfirmed here and is worth a manual pass.
Second expense after waiting for the map
Confirm details on the waited path
⚠️
No JS console errors related to this flowCould not verify directly — the web agent-device harness exposes no console-log capture verb. As a proxy the network log showed no 4xx/5xx responses across the flow and no error toast or banner appeared. Reporting as a tooling limitation, not an observed failure.
⚠️

Both expenses landed correctly in the workspace draft report (€53.25 total across the 3 drafts).


view run · no recording available

@mukhrr

mukhrr commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

ohh reviews much improved 👍

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

Yeah
And I think it all makes sense

@mukhrr

mukhrr commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Yeah And I think it all makes sense

can you psl approve and we can continue? tjanks

@ZhenjaHorbach

ZhenjaHorbach commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

can you psl approve and we can continue? tjanks

Maybe you should add these changes in this PR?😅

#99665 (comment)

@mukhrr

mukhrr commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

oh sorry, got a bit confused with multiple ✅ emojis. let me check them

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

When trying to send an expense to a friend in a non-default workspace with a cleared cache We use the wrong unit(mile/km) I suppose we need the unit from the created transaction, which already has a unit optimistically

2026-09-04.13.48.44.mov

Okay
I think it's okay that we can't send an expense to a friend when we don't have data in the workspace
But I'm not sure that we need to show pending since we don't refresh the screen when data appears anyway

Снимок экрана — 2026-09-07 в 11 07 53

CC: @JmillsExpensify
Can you share your thoughts on how we should handle this, please?

When we are trying to send an expense to a friend to a non-default workspace after clearing cache or relogging in
We don't receive the data about non-default workspaces from the OpenApp request
As a result, we are blocked on the Confirm details screen since we don't have information about rates on non-default workspaces

2026-09-07.11.17.37.mov

Copy link
Copy Markdown
Contributor

My preference is that we deprecate support for this entire flow, so I'm fine blocking on the Confirm details case for now. This just isn't a mainline flow that matters.

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

@mukhrr
Let's mention this in the test cases that it's expected behaviour to not confuse QA

@mukhrr

mukhrr commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

@ZhenjaHorbach done

@melvin-bot
melvin-bot Bot requested a review from chuckdries September 8, 2026 09:43
@chuckdries
chuckdries merged commit 538f6e6 into Expensify:main Sep 8, 2026
36 of 38 checks passed
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

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

@OSBotify

OSBotify commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

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

@OSBotify

OSBotify commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/chuckdries in version: 9.4.75-0 🚀

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

@MelvinBot

MelvinBot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🤖 No help site changes are required for this PR, so I did not create a docs PR.

This is a defect fix, not a behavior or copy change. It stops the confirmation page from showing a stuck pending Rate row and a $∞ Amount when a tracked distance expense is submitted before the backend writes quantity and customUnitRateID. The help site already describes the intended behavior that this PR restores, so there is nothing to correct.

What I checked

Changed files — all logic and tests, no user-facing strings and no src/languages/* edits:

Articles reviewed — the only two in docs/articles that cover this flow:

Two passages I looked at closely, and why both still hold:

  1. Distance-Expenses.md:130-133 already says that for expenses tracked in Your space, your default Workspace's rates are applied automatically. That is exactly what the PR makes happen reliably — the article was right, the app was not.

  2. Distance-Expenses.md:168-172 says a Distance expense moved to another Workspace keeps its original unit and rate and shows the "Rate not valid for this workspace" violation. That FAQ is about the Move expense action, which this PR does not touch. Within the submission flow it does touch, the rate is still kept whenever it is the P2P rate or one the destination Workspace already owns — it is only replaced when the draft carries no rate ID or a rate the destination does not have, which is the broken state being fixed. The "Rate not valid" error also still appears for a Workspace whose rates are not cached, per step 10 of your test steps.

@mukhrr, please confirm you agree that no help site update is needed here. If you think one of the two articles above should be updated anyway — for example, if you want the Move-expense FAQ reworded to be explicit that it does not cover submitting a tracked expense — reply with @MelvinBot and what you'd like changed, and I'll open the draft HelpDot PR.


view run · view recording

@IuliiaHerets

Copy link
Copy Markdown

PR failed due to an original issue: Amount shows the infinity symbol, while Rate shows “Pending” when submitting a distance expense from a self-DM to a non-default workspace.

1788993811059.Recording__1568.mp4

cc @chuckdries @ZhenjaHorbach @JmillsExpensify

@mukhrr

mukhrr commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

Amount shows the infinity symbol, while Rate shows “Pending” when submitting a distance expense from a self-DM to a non-default workspace.

@IuliiaHerets i think you either wrongly commented or attached wrong video record. I don't see bug on video you uploaded. can you psl check it once more? thanks

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

Just checked the video
But everything looks good 😅

@IuliiaHerets

Copy link
Copy Markdown

@mukhrr @ZhenjaHorbach asked the tester to recheck. I'll be back soon

@IuliiaHerets

Copy link
Copy Markdown

@mukhrr @ZhenjaHorbach Step 10 says that the Rate should be shown as invalid and the user should be able to manually select an available rate before submitting.
The QA team got the following result:

  • The app does not freeze.
  • Amount shows 0.
  • Rate shows Pending and is not editable.
    The last behavior is not expected, so the tester failed the PR with the original issue.
    If we need to create a new issue for this behavior, please let us know.
Recording.1572.1.mp4

@mukhrr

mukhrr commented Sep 10, 2026

Copy link
Copy Markdown
Contributor Author

@IuliiaHerets i suggest reading this and this. It's expected disabled Rate field. It seems I mistyped last step ( sorry for that). It should have been:

"Rate not valid for this workspace. Please select an available rate from the workspace." is shown, and selecting rate is blocked

@ZhenjaHorbach did I miss anything? Correct me if I am wrong, pls. Thanks

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

Yes
That’s correct
This is not a bug
We agreed earlier that this is acceptable behavior for this edge case

@IuliiaHerets

Copy link
Copy Markdown

So we can check it off, right?
@ZhenjaHorbach @mukhrr

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

So we can check it off, right? @ZhenjaHorbach @mukhrr

Yes

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/mountiny in version: 9.4.75-3 🚀

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

Bundle Size Analysis (Sentry):

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/mountiny in version: 9.4.75-3 🚀

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

Bundle Size Analysis (Sentry):

@mukhrr

mukhrr commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

"Rate not valid for this workspace. Please select an available rate from the workspace." is shown, and selecting rate is blocked

@ZhenjaHorbach while this is expected, I am a bit worried about UX. I mean we show "... pls select a rate.." as error while it is not selectable.

image

so I have a suggestion here:

  1. in addParticipant, where we skip the rate write because the destination workspace has no rates loaded, call openPolicyDistanceRatesPage(policy.id). its a plain read that fills customUnits on the policy, and DistanceRequestController already has policy in its effect deps so the screen does refresh when rates land.
  2. in that controller effect, when we're moving a tracked expense and the draft's rate isnt in the now loaded policy, set the workspace default rate, same thing addParticipant does when cache is warm.

result online: same as warm cache, just a sub second delay. unit matches → rate shown, no error. unit differs → rate shown, row tappable, error stays and the user can actually pick one.

I understand we don't have data to show optimistically so offlime stays as-is now. what d oyuo think? cc @JmillsExpensify

@ZhenjaHorbach

Copy link
Copy Markdown
Contributor

@MelvinBot
Create a new issue for that and assign me and @mukhrr

"Rate not valid for this workspace. Please select an available rate from the workspace." is shown, and selecting rate is blocked

@ZhenjaHorbach while this is expected, I am a bit worried about UX. I mean we show "... pls select a rate.." as error while it is not selectable.

image so I have a suggestion here:
  1. in addParticipant, where we skip the rate write because the destination workspace has no rates loaded, call openPolicyDistanceRatesPage(policy.id). its a plain read that fills customUnits on the policy, and DistanceRequestController already has policy in its effect deps so the screen does refresh when rates land.
  2. in that controller effect, when we're moving a tracked expense and the draft's rate isnt in the now loaded policy, set the workspace default rate, same thing addParticipant does when cache is warm.

result online: same as warm cache, just a sub second delay. unit matches → rate shown, no error. unit differs → rate shown, row tappable, error stays and the user can actually pick one.

I understand we don't have data to show optimistically so offlime stays as-is now. what d oyuo think? cc @JmillsExpensify

@MelvinBot

Copy link
Copy Markdown
Contributor

🤖 I held off creating the issue — there's already an open one for this exact symptom, and this specific case was explicitly deprioritized on this PR.

Likely duplicate: [$250] DEV: Rate not valid error is displayed when submitting a distance expense, but the Rate field is not editable (Expensify/App#98426) — open, assigned to Ollyws / cristipaval / FitseTLT, PR in progress (FitseTLT asked for fix-direction clarification on Sep 14). Its repro is step-for-step the screenshot above: track distance in the self DM → Submit to a friend → pick a workspace → "Rate not valid" shown while the Rate row can't be tapped. This PR fixed the warm-cache half of it, so what's left in #98426 is essentially the cold-cache case mukhrr describes.

Also worth weighing: JmillsExpensify already called this case — "My preference is that we deprecate support for this entire flow, so I'm fine blocking on the Confirm details case for now. This just isn't a mainline flow that matters."

Your call:

  • Post the suggestion on #98426 — the openPolicyDistanceRatesPage idea is a concrete fix direction for the question FitseTLT is currently stuck on. Say the word and I'll comment there.
  • Still create a separate issue — reply @MelvinBot create it anyway and I'll file it scoped to the cold-cache / non-default-workspace case, assigned to you and mukhrr.
Code the suggestion touches

view run

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.

7 participants