Fix Account and Wallet RBR still showing for personal cards past the 90-day grace period#96364
Fix Account and Wallet RBR still showing for personal cards past the 90-day grace period#96364wildan-m wants to merge 4 commits into
Conversation
…race period The 90-day grace correctly removed the home task, but the Account indicator and the Wallet row red dot stayed lit for personal cards. Both are driven by the card's own error, which the issue requires us to keep so the card stays fixable: - getShouldShowRBR short-circuits on hasFeedErrors before the grace check, so a past-grace card's error still forced the RBR. - hasPaymentMethodError counts any personal card with errors, and it feeds both the Account indicator (useAccountIndicatorChecks) and the Wallet row (InitialSettingsPage). Gate both on isBrokenConnectionPastDismissThreshold, leaving card.errors untouched so the Wallet-page card row keeps its error and Fix card action. Adds regression tests for a past-grace card that still carries the error (the case the original fixtures missed), plus guards that within-grace and unrelated errors still show.
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
…al-card-rbr-followup
|
@jayeshmangwani 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] |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ac02fdb2c8
ℹ️ 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".
|
@jayeshmangwani @joekaufmanexpensify The follow up PR is ready |
Addresses Codex review. The previous change dropped ALL of a stale-broken personal card's errors/errorFields, so an unrelated actionable error also lost its RBR — a failed reimbursable/start-date update (another errorFields entry) or a failed remove (card.errors). Now only errorFields.lastScrape (the broken-connection error the card detail page reads) is excluded from the RBR once past the grace period; every other error is kept. Reverts the over-broad hasPaymentMethodError gate, since card.errors on a personal card is an actionable error, not the connection error. Adds regression tests for a past-grace card that also carries an unrelated field error and one that carries a card-level error.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c2ad732aa
ℹ️ 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".
| const isPastDismissThreshold = isBrokenConnectionPastDismissThreshold(card); | ||
| const errorFieldsForRBR = | ||
| isPastDismissThreshold && card.errorFields ? Object.fromEntries(Object.entries(card.errorFields).filter(([field]) => field !== 'lastScrape')) : card.errorFields; | ||
| const hasCardErrors = !isEmptyObject(card.errors) || !isEmptyObject(errorFieldsForRBR); |
There was a problem hiding this comment.
Suppress dismissed broken-connection card errors
For past-grace personal cards this only drops errorFields.lastScrape, but card.errors is still treated as RBR-worthy. In the Account indicator path, useAccountIndicatorChecks still calls hasPaymentMethodError(), which returns true for any personal card with card.errors, so a dismissed broken connection stored as a card-level error such as errors.brokenConnection will continue to light the Account dot and Wallet row even though isPastDismissThreshold is true. Please filter the broken-connection card-level error in the same past-grace case, while preserving unrelated card errors.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Good catch, fixed in e50ca23235c.
You're right that the broken connection is also stored as a card.errors entry (server-set) — that's what keeps lighting the Account button via hasPaymentMethodError and the Wallet row, so dropping only errorFields.lastScrape wasn't enough. This also corrects my earlier note on the PaymentMethods thread: the connection error does land in card.errors, not just errorFields.
Past the grace period we now also skip a broken personal card's card.errors when deciding the RBR, in both the derived value and hasPaymentMethodError, so both surfaces clear. It's gated by isBrokenConnectionPastDismissThreshold at the card level rather than by an errors.brokenConnection key — there's no dedicated key, the error is server-set and uses the generic shape. Any actionable error kept in a separate errorFields entry (e.g. a failed reimbursable/start-date update) still surfaces. The one case this can't separate is a card that is both past-grace broken and carries a fresh card.errors failure (e.g. a failed remove); it's treated as the dismissed connection, but that's a rare/transient state and the error itself stays on the card either way.
Added regression tests for the Account indicator and the derived value.
A broken personal card's connection is also stored as a server-set card.errors entry, which lit the Account button (via hasPaymentMethodError) and the Wallet row even past the 90-day grace period. Gate it by isBrokenConnectionPastDismissThreshold in both the derived value and hasPaymentMethodError, so those surfaces clear while actionable errors kept in a separate errorFields entry (reimbursable/start-date) still surface. Adds Account-indicator and derived-value regression tests.
|
🚧 joekaufmanexpensify has triggered a test Expensify/App build. You can view the workflow run here. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e50ca23235
ℹ️ 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".
| return true; | ||
| // A broken personal-card connection is surfaced as a card error too, so once it is unresolved past the grace | ||
| // period we stop leading the user to it and it must no longer light the RBR (any other error still does). | ||
| return !CardUtils.isBrokenConnectionPastDismissThreshold(card); |
There was a problem hiding this comment.
Preserve unrelated personal-card errors past threshold
When a personal card is past the broken-connection threshold, this return ignores every root-level card.errors entry on that card, not just the stale connection error. Personal-card actions can create unrelated root-level errors—for example unassignCard failure writes errors into CARD_LIST, and the personal-card details menu can invoke that path—so a card that is also long-broken would no longer light the Account/Wallet indicators for a new actionable failure. Please distinguish the broken-connection error from other card.errors instead of suppressing the whole card.
Useful? React with 👍 / 👎.
|
Let me know if you need any more information from me to investigate further! |

Explanation of Change
Follow-up to #93523. That PR added the 90-day grace period and correctly removed the Home task, but for personal cards the
Accountindicator and theWalletrow red dot stayed on — which the issue asks us to remove:Reported on production by @joekaufmanexpensify: #91451 (comment)
Why it happens. Those two dots never ask "is this past 90 days?" — they only ask "does this card have an error?", via
getShouldShowRBR()(which returns early onhasFeedErrors) andhasPaymentMethodError()(true for any personal card with errors). Since the issue tells us to keep that error so the card stays fixable, the dots never turn off. The Home task disappearing is the giveaway: the 90-day check works, these two paths just skip it.The fix. Make both paths respect the same 90-day check.
card.errorsis untouched, so theWalletpage still shows the error with a working Fix card button — only theAccount/Walletdots stop. Cards inside the grace period, and cards with unrelated errors, are unaffected (both covered by new tests).Fixed Issues
$ #91451
PROPOSAL: #91451 (comment)
Tests
This is shared logic (no platform-specific code), so it behaves the same on web, mWeb, iOS and Android.
Important — what actually reproduces this. It only shows up on a personal card (one added on
Account > Wallet, i.e.fundIDis absent or'0') whose broken connection also carries an error on the card itself. That error is what lights theAccount/Walletred dots, and we deliberately keep it so the card stays fixable. A company card from a workspace will not reproduce it — it goes through the feed path, which the existing grace check already covers.Rather than waiting 90 days for real data, paste this in the browser console on a dev build to put your existing personal card into exactly that state:
Account > Wallet > Add personal card). Run the script withDAYS_AGO = 1.DAYS_AGO = 100.Walletpage: still listed with its own red dot, and opening it still shows "Your card connection is broken." with a working Fix card button (the error is kept so it stays fixable).RESET = trueto put the card back to healthy.You can also read the underlying state directly instead of eyeballing the dots:
shouldShowRBRis what drives theAccount/Walletred dots;isFeedConnectionBrokendrives the Home task. Onmainthe task is correctly gone butshouldShowRBRstaystrue— which is exactly what was reported.Offline tests
Same as the Tests above. This is a derived value computed from data already in Onyx plus the device date, so it behaves identically offline — the task and indicators stay hidden for a past-grace connection, and the card keeps its error and Fix card action.
QA Steps
Please treat this as a regression check around the card feature. Behaviour is the same on all platforms, so any one platform is fine.
Walletpage with its error and a working Fix card button.If a 90-days-broken connection isn't available as test data, the removal is also covered by automated unit tests; the essential manual checks are step 1 (recently-broken still prompts) and step 3 (no regressions).
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Kapture.2026-07-18.at.10.46.14.mp4