Thread beta overrides into the remaining util call sites - #100061
Conversation
|
Hey, I noticed you changed If you want to automatically generate translations for other locales, an Expensify employee will have to:
Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running: npx bun ./scripts/generateTranslations.ts --helpTypically, you'd want to translate only what you changed by running |
|
@codex review |
|
@MelvinBot review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 75beffc515
ℹ️ 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".
| function BetaOverridesPage() { | ||
| const styles = useThemeStyles(); | ||
| const {translate} = useLocalize(); | ||
| const {isBetaEnabled} = usePermissions(); | ||
| const [betaOverrides] = useOnyx(ONYXKEYS.BETA_OVERRIDES); |
There was a problem hiding this comment.
Block the beta override route in production
When an authenticated production user opens /beta-overrides directly, the linking config routes here even though only the Test Tool Menu entry is guarded by !isProduction. This page has no environment check, so it exposes all test switches; because usePermissions ignores overrides in production, toggling a switch writes a hidden override but leaves the effective switch state unchanged. Gate the page or route itself so the production-only restriction cannot be bypassed by a deep link.
Useful? React with 👍 / 👎.
Review: 2 blocking test failures, both caused by this PRThe refactor itself is sound — I verified the 🔴 1. Onyx export keys are out of alphabetical order
🔴 2.
|
|
@codex review |
|
@MelvinBot review |
|
Codex Review: Didn't find any major issues. Bravo. Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
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". |
Re-review of
|
|
@MelvinBot review |
Re-review of
|
JakubKorytko
left a comment
There was a problem hiding this comment.
Nothing blocking, a handful of small ones but I could miss something because of the size 😅 tried my best to skip commenting stuff already added by #99444
Screen.Recording.2026-09-17.at.21.51.21.mov@arekm213 Is this expected behavior or a bug? I think it’s a bug because when we press Back, we should return to the test tool instead of closing it. |
@huult its not a bug, i think some other flow in same menu does the same, or we at least agreed upon the behaviour being like this in original PR where we introduced this feature, i will update this comment in a moment with solid informations edit: you can check out |
@huult this came up in original PR - the first version was a modal rendered inside the Test Tools menu exactly so the menu would stay underneath. I flagged the RHP tradeoff here (#99444 (comment)) and design chose the RHP anyway here (#99444 (comment)): "the mobile issue of the troubleshooting menu being closed when you leave the RHP is worthwhile tradeoff for this". |
|
🚧 mountiny has triggered a test Expensify/App build. You can view the workflow run here. |
|
✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release. |
|
🧪🧪 Use the links below to test this adhoc build on Android, iOS, and Web. Happy testing! 🧪🧪
|
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.82-0 🚀
|
|
No help site changes are required for this PR. I did not create a draft docs PR. This is an internal refactor of how three betas ( Why I concluded no docs changes are needed1. The feature this PR extends is hidden in production. Beta overrides only exist on non-production builds — 2. No UI, copy, or navigation changed. Of the 211 changed files, all are under 3. The one real behavior change does not reach customers. Routing 4. The features these betas gate are already documented, and their documented behavior is unchanged. I checked the articles that would have been affected if customer behavior had shifted:
Each describes the customer-facing feature, not the beta gate. None of them states or implies anything this PR alters. Per If you disagree — for example if @arekm213, please confirm you agree that no help site changes are required. There is no linked help site PR to mark |
|
I agree that no help site changes are required |
|
@arekm213 please help with QA steps for Applause to perform. Could you provide some detailed steps? |
|
@kavimuru sure, here is a list and explaination
One expected difference from production, in step 1. On production the report shows Draft with a Submit button for a moment before switching to Outstanding. On staging it is Outstanding right away. The report ends up in the same place either way. This only affects accounts that have the |
|
@arekm213 in staging which beta toggle should we enable?
|
|
I think for the all you dont need to enable anything - that is already included on your expensifail accounts |
|
Yes, exactly |
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.86-0 🚀
|
|
🤖 No help site changes are required for this PR — so no draft docs PR was created. This PR rewires how three betas ( Why nothing in
If you'd like a docs update anyway — for example, documenting the Troubleshoot overrides flow for internal use — let me know and I'll open the draft PR. My recommendation is not to, since the help site is customer-facing and the feature is not available in production. @arekm213, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR |
|
🚀 Deployed to staging by https://github.com/mountiny in version: 9.4.89-0 🚀
|
|
🤖 Still no help site changes required — no draft docs PR created. This is the same answer I gave earlier today, which I re-checked rather than assuming nothing changed:
@arekm213, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR |
|
🚀 Deployed to production by https://github.com/luacmartins in version: 9.4.89-0 🚀
Bundle Size Analysis (Sentry): |
|
@MelvinBot confirmed again, no help site changes are required and there is no linked help site PR to mark ready |
|
🤖 Confirmed — closing this out. No help site changes are required, and there is no linked help site PR, so there is nothing to mark Sorry for asking three times. My last two comments appended a boilerplate "review the linked help site PR" line even after concluding that no such PR existed, which made a settled question look unsettled. Nothing further is needed from you here. |
Explanation of Change
Follow up to #99444, which added local beta overrides but left three betas resolving from the raw
betasarray, so an override never reached them.The three remaining betas are now override aware:
asapSubmitanddefaultRooms: thebetasparameter is replaced by the resolved boolean (isASAPSubmitBetaEnabled,isDefaultRoomsBetaEnabled), passed down fromusePermissionsat the hook or component boundary. This is the pattern the codebase already uses at over 130 call sites covering more than twenty other betas: resolve once whereusePermissionsis available, then pass the answer down. SinceusePermissionsmerges betas, configuration and overrides, nothing downstream can drift.vendorMatching:ViolationsUtilsused to resolve this beta itself from module level Onyx subscriptions. Those are gone, including theBETASone that predates this PR.getViolationsOnyxDatatakes the resolved value as a parameter and the components pass it down, the same way the other two betas work here. The value isboolean | undefinedrather than a plain boolean, because the code it feeds has to tell "the beta is off" apart from "the account betas have not loaded yet" and must leave existing violations alone in the second case.usePermissionsexposesisBetaEnabledOrUnknownfor the few call sites that need that distinction, and everything else keeps usingisBetaEnabled.TransactionInlineEdittook the same route until main made it pure. It now receivesisASAPSubmitBetaEnabledfromuseTransactionInlineEdit, which resolves it throughusePermissions, so the file holds no Onyx subscriptions of its own.Because the boolean replaces the array, the
betasplumbing that existed only to answer these checks is now dead and has been removed.Permissions.isBetaEnabledalso makesbetaConfigurationandbetaOverridesrequired rather than optional. Only two call sites remain insrc,usePermissionsand the beta overrides page, and the page passesundefinedfor the overrides explicitly, with a comment, because it deliberately shows what the account alone resolves to. A required parameter is what stops the next util from quietly resolving a beta with two arguments.Unifying how these betas resolve
Permissions.isBetaEnabledonly applies theexplicitOnlyandexclusionrules whenbetaConfigurationis supplied. A two argument call therefore reports an explicit-only beta as enabled, and an exclusion beta as enabled, for every account holding theallbeta. Accounts withoutallare unaffected: the branch is never reached and both forms reduce tobetas.includes(beta).Before this PR,
asapSubmit(an explicit-only beta) was resolved without the configuration inReportUtils,SearchandPolicy, while every UI check resolved it with the configuration throughusePermissions. The two disagreed onallbeta accounts. Routing these call sites throughusePermissionsmakes them agree, which means that on anallbeta account withoutasapSubmitgranted explicitly, these paths now resolve it tofalsethe way the rest of the app already did.This is the same change of behaviour that #100851 reported for
preventSpotnanaTravel(an exclusion beta) after #99444, and that issue was closed as intended behaviour. The durable fix for an account that wants the feature is to grant the beta explicitly rather than rely onall. The analysis on that issue also named the call sites still carrying the two argument form (ReportUtils.ts,Search.ts, allASAP_SUBMIT), which are exactly the ones this PR converts.defaultRoomsmoves the same way.canSeeDefaultRoomusedisBetaEnabled(DEFAULT_ROOMS, betas ?? [])and now takes the resolved boolean, so if the backend listsdefaultRoomsunderexplicitOnlyorexclusion, anallbeta account without it granted explicitly loses non partner-managed domain rooms from the LHN, Search and the chat finder. Admin and announce rooms are unaffected,canSeeDefaultRoomreturns early for them. This one is the most visible of the three, so it is worth a look during QA even though the mechanism is identical.ViolationsUtilswas resolvingvendorMatchingwithout the configuration too, so on anallbeta account it could write an inactive vendor violation for a feature the user cannot see. It now receives the value already resolved throughusePermissions, so it picks up both the configuration and the overrides.Behaviour worth calling out beyond "the utils see overrides"
A pre-existing swapped argument pair is fixed. On main,
AttachmentPickerWithMenuItemscallscreateNewReport(personalDetails, isASAPSubmitBetaEnabled, hasViolations, ...)while the signature is(ownerPersonalDetails, hasViolationsParam, isASAPSubmitBetaEnabled, ...). Both are booleans so TypeScript never caught it, and the ten other call sites are correct. Today this only corrupts the next step, because the optimistic report state and status come from the raw betas array further down. This PR moves that resolution to the boolean, which would have made the swap corrupt the state and status as well, so the arguments are put in the right order here rather than left for a separate fix.setWorkspaceApprovalModekeeps its loading guard. On main the guard readsadditionalData?.transactionViolations != null && additionalData?.betas != null && additionalData?.personalDetailsList. Thebetasnull check doubled as a check that Onyx had loaded them, because the callers read the array withuseOnyxand passedundefineduntil it arrived. That is preserved: the field isboolean | undefined, the callers resolve it withisBetaEnabledOrUnknown, and the conjunct is nowadditionalData?.isASAPSubmitBetaEnabled !== undefined. The optimistic next step update is still skipped while the beta is unknown rather than running with it resolved to off.A pinned override is ignored for one page's UI gating while the betas load.
Permissions.isBetaEnabledchecks the overrides first and returns the pinned value before it looks atbetasat all.isBetaEnabledOrUnknownshort circuits onbetas === undefinedand returnsundefinedwithout consulting them.WorkspaceMoreFeaturesPagenow reads the beta once through the second one and coerces it for its two UI checks, so between app start and theBETASresponse a pinnedvendorMatchingreads as off there, where before it read as pinned.This is left as is deliberately. The scope is dev and staging only, since overrides never apply in production, it lasts only until
BETASarrives, and the row it gates also needs an accounting connection. Making the helper consult the overrides before the short circuit would fix it, but that same helper feeds the violation builders, where returningundefinedis exactly what stops an existing server sentINACTIVE_VENDORfrom being stripped while the betas are unknown. A pin set to off would start stripping it again, which is the failure this whole design exists to prevent. Honouring a pin a few hundred milliseconds earlier on one settings page is not worth that.Two call sites keep a hardcoded
falseon purpose.InSelectorandUnreadIndicatorUpdaterpreviously passedbetas: undefinedandbetas: [], which resolved to false for everyone, so the literal keeps their behaviour identical. Passing the real value there would change what the Search filter and the unread indicator show, which is unrelated to overrides and belongs in its own PR.Follow up: the remaining
betasplumbing is deadAfter this PR,
betasis read in exactly two places:usePermissionsand the beta overrides page. Everything else that still carriesbetasforwards it into one of four parameters that no function body ever reads:OpenReportActionParams.betasinsrc/libs/actions/Report/index.tsgetMoneyRequestOptionsinsrc/libs/ReportUtils.tscompletePaymentOnboardinginsrc/libs/actions/IOU/PayMoneyRequest.tsBuildPolicyDataOptions.betasinsrc/libs/actions/Policy/Policy.tsAll four were already dead on main, so this is not something this PR introduced. What changed is that they used to sit next to live
betaschains and were impossible to tell apart from them. That residue is around 138 files and roughly 100useOnyx(ONYXKEYS.BETAS)subscriptions that exist only to deliver a value nobody reads. Removing it belongs in a separate PR, kept out of this one so the diff here stays reviewable.Fixed Issues
$ #98409
PROPOSAL:
Tests
canSeeDefaultRoomreturns early for them, so they stay visible either way. Without a domain room this one is only covered by thecanSeeDefaultRoomcases inReportUtilsTest.getExpenseReportStateAndStatusfalls through to the sameOPEN / OPENwhether the beta is pinned on or off. Second, go offline before creating the expense.asapSubmitis gated on the backend too, and the backend does not know about a device local override, so online the write command's response replaces the optimistic state a moment later and the report lands on Outstanding either way. Offline the optimistic value survives long enough to read. With both in place, toggle asapSubmit on, create an expense, and verify the report stays in Draft. Toggle the beta off, create an expense on a fresh report rather than adding to the existing draft, and verify that one is Submitted immediately instead. To see the next step differ as well, make one of the expenses break a rule so it carries a violation, since both next step branches are gated on a violation plus instant submit.The
vendorMatchingoverride needs a QuickBooks Online or Xero workspace with a synced vendor list to demonstrate by hand, so it is covered by unit tests instead:tests/unit/ViolationUtilsTest.tsasserts that pinning the beta on adds the inactive supplier violation when the account does not have the beta, and that pinning it off suppresses the violation when the account does have it.Offline tests
Overrides are stored locally and only affect frontend checks, so for steps 1, 2 and 4 to 7 the behaviour is identical offline.
Step 3 is the exception and has to be run offline.
asapSubmitis enforced on the backend as well, and the backend has no knowledge of a device local override, so online the server's own state, status and next step replace the optimistic ones the client built. Offline there is no response to overwrite them, which is the only way to observe the pinned value. The page itself says as much: "Some betas are gated on the backend too, so those can still fail at the request level."QA Steps
Overrides are hidden in production, so QA is a regression pass rather than a feature test: create, submit, approve and pay expenses, open reports from the LHN, and use the
in:filter in Search. Everything should behave exactly as it does today.One thing to watch on internal accounts holding the
allbeta. If the backend lists a beta underexplicitOnlyorexclusion, that beta now resolves tofalsein the converted paths unless the account is granted it explicitly, which matches how the rest of the app already behaved. That coversasapSubmitin expense creation, Search approve and the workspace approval mode,vendorMatchingin the violation builders, anddefaultRoomsin the LHN, Search and the chat finder.defaultRoomsis the visible one: check that non partner-managed domain rooms still appear for anallbeta account. See #100851 for the same change onpreventSpotnanaTravel.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
No UI changes in this PR, the beta overrides screen itself is in #99444.