From 87616586d25eb49ba38a2ba9037ea6f49e3129c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Mon, 4 May 2026 16:47:31 +0900 Subject: [PATCH 01/11] Onboarding: drop cohort gate now that server generates per-tier welcome MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Web-Expensify side (#52519) generates the full bespoke welcome body server-side from a per-tier template, so the App-side cohort gate that previously required either Phase-1 (MICRO) cohort or the suggestedFollowups beta is no longer needed. Every MANAGE_TEAM signup now opts into the bespoke direct-post path; the server picks the right copy based on companySize and attaches the guide block when an eligible guide is on the policy. getBespokeWelcomeMessage's optimistic placeholder is reshaped to match the server's per-tier skeleton (header + Concierge intro + per-tier hint, no guide block) so the hand-off when the server response reconciles is visually continuous β€” the only change on reconciliation is the guide block appearing for non-MICRO cohorts. --- src/libs/ReportUtils.ts | 64 ++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 42 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 780a0f7710ff..f637325b1b5e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11638,43 +11638,28 @@ type PrepareOnboardingOnyxDataParams = { betas: OnyxEntry; }; -function getBespokeWelcomeMessage(companySize: OnboardingCompanySize | undefined, userReportedIntegration?: OnboardingAccounting): string { - // Use markdown (not HTML) because buildOptimisticAddCommentReportAction -> getParsedComment - // escapes HTML entities before parsing, so raw HTML tags would render as literal text. - const welcomeHeader = "# Your free trial has started! Let's get you set up.\nπŸ‘‹ Hey there! I'm your Expensify setup specialist. "; - - let message = welcomeHeader; +function getBespokeWelcomeMessage(companySize: OnboardingCompanySize | undefined): string { + // Optimistic placeholder painted while CompleteGuidedSetup is in flight. The server's + // `UserAPI::buildBespokeWelcomeMessage` reconciles via `optimisticConciergeReportActionID` + // and replaces this with the final body β€” including the guide block on TEAM 11+. Mirror + // the server's per-tier skeleton (header + Concierge intro + per-tier hint) here so the + // hand-off is visually continuous; the only change on reconciliation is the guide block + // appearing for non-MICRO cohorts. + // + // Markdown, not HTML: buildOptimisticAddCommentReportAction β†’ getParsedComment escapes + // entities, so raw HTML tags would render as literal text. + const header = '# Welcome to Expensify πŸ‘‹\n'; switch (companySize) { - case CONST.ONBOARDING_COMPANY_SIZE.MEDIUM: - case CONST.ONBOARDING_COMPANY_SIZE.LARGE: - message += - 'For an organization your size, the fastest path to value is setting up approval workflows, ' + - 'connecting your accounting software, and rolling out the Expensify Card to your team. ' + - "I'm here to walk you through each step β€” just ask!"; - break; case CONST.ONBOARDING_COMPANY_SIZE.SMALL: case CONST.ONBOARDING_COMPANY_SIZE.MEDIUM_SMALL: - message += - 'For a growing team like yours, the fastest way to get value is to set up expense categories, ' + - 'configure approval workflows, and invite your team members. ' + - "I'm here to walk you through each step β€” just ask!"; - break; + return `${header}\nI'm Concierge, here for quick questions any time.\n\nMost teams your size start by connecting accounting, setting up approvals, and inviting everyone.`; + case CONST.ONBOARDING_COMPANY_SIZE.MEDIUM: + return `${header}\nI'm Concierge, here for quick questions any time.\n\nMost teams your size start by connecting accounting, setting up multi-level approvals, and rolling out the Expensify Card.`; + case CONST.ONBOARDING_COMPANY_SIZE.LARGE: + return `${header}\nI'm Concierge, here for quick questions while your rollout gets planned.`; default: - message += - 'For a small team like yours, the fastest way to get value is to set up a few expense categories, ' + - 'invite your team members, and have them start snapping receipts right away. ' + - "I'm here to walk you through each step β€” just ask!"; - break; - } - - if (userReportedIntegration && userReportedIntegration !== 'other') { - const friendlyName = CONST.ONBOARDING_ACCOUNTING_MAPPING[userReportedIntegration as keyof typeof CONST.ONBOARDING_ACCOUNTING_MAPPING]; - if (friendlyName) { - message += `\n\nSince you use ${friendlyName}, I can help you connect it so your expenses sync automatically β€” just say the word!`; - } + return `${header}\nI'm Concierge, and I'll help you get set up. For a team your size, the fastest path is adding a few expense categories, inviting everyone, and having them snap receipts.\n\nTry one of the suggestions, or reply with any questions.`; } - - return message; } function prepareOnboardingOnyxData({ @@ -11702,15 +11687,10 @@ function prepareOnboardingOnyxData({ onboardingMessage = getOnboardingMessages().onboardingMessages[CONST.ONBOARDING_CHOICES.SUBMIT]; } - // Phase 1 cohort (MANAGE_TEAM + micro company size) bypasses the beta gate β€” the backend - // handles gating via NVP, so all micro users get followups without needing the beta flag. - // Includes MICRO_SMALL, MICRO_MEDIUM, and the deprecated MICRO for backwards compatibility. - const isPhase1Cohort = - companySize === CONST.ONBOARDING_COMPANY_SIZE.MICRO_SMALL || companySize === CONST.ONBOARDING_COMPANY_SIZE.MICRO_MEDIUM || companySize === CONST.ONBOARDING_COMPANY_SIZE.MICRO; - // Followups path: MANAGE_TEAM + (Phase 1 cohort OR suggestedFollowups beta). Reaches every - // MANAGE_TEAM cohort user, including `+` aliases and phone-primary sign-ups. - const shouldUseFollowupsInsteadOfTasks = - engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM && (isPhase1Cohort || Permissions.isBetaEnabled(CONST.BETAS.SUGGESTED_FOLLOWUPS, betas, betaConfiguration)); + // Every MANAGE_TEAM signup uses the bespoke direct-post path. The server generates the + // per-tier welcome body in `UserAPI::buildBespokeWelcomeMessage`, so the App no longer + // needs a cohort gate or the suggestedFollowups beta to opt in. + const shouldUseFollowupsInsteadOfTasks = engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM; // Post to #admins room when followups fire OR the existing tasks-in-admins predicate approves. const shouldPostTasksInAdminsRoom = shouldUseFollowupsInsteadOfTasks || isPostingTasksInAdminsRoom(engagementChoice); const adminsChatReport = deprecatedAllReports?.[`${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`]; @@ -11791,7 +11771,7 @@ function prepareOnboardingOnyxData({ let bespokeAction: OptimisticReportAction | undefined; if (shouldUseFollowupsInsteadOfTasks) { - const bespokeMarkdown = getBespokeWelcomeMessage(companySize, userReportedIntegration); + const bespokeMarkdown = getBespokeWelcomeMessage(companySize); optimisticConciergeReportActionID = rand64(); // delegateAccountIDParam: will be threaded in PR 14; buildOptimisticAddCommentReportAction falls back to module-level Onyx.connect value (https://github.com/Expensify/App/issues/66425) bespokeAction = buildOptimisticAddCommentReportAction({ From e159a01d7756e1efacc47a4d8d6af94b2b3c62f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Mon, 4 May 2026 16:54:48 +0900 Subject: [PATCH 02/11] Drop unused betaConfiguration + betas after cohort gate removal Now that prepareOnboardingOnyxData no longer reads betas/betaConfiguration, clear the supporting plumbing: - Remove the betaConfiguration Onyx.connect (one no-onyx-connect seatbelt entry). - Drop the BetaConfiguration type import. - Stop destructuring betas in prepareOnboardingOnyxData; keep an optional `betas?` field on PrepareOnboardingOnyxDataParams so existing callers keep type-checking. --- config/eslint/eslint.seatbelt.tsv | 2 +- src/libs/ReportUtils.ts | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/config/eslint/eslint.seatbelt.tsv b/config/eslint/eslint.seatbelt.tsv index f54d892f7416..9a45b0052771 100644 --- a/config/eslint/eslint.seatbelt.tsv +++ b/config/eslint/eslint.seatbelt.tsv @@ -228,7 +228,7 @@ "../../src/libs/PolicyUtils.ts" "rulesdir/no-onyx-connect" 1 "../../src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts" "no-restricted-syntax" 2 "../../src/libs/ReportActionsUtils.ts" "rulesdir/no-onyx-connect" 3 -"../../src/libs/ReportUtils.ts" "rulesdir/no-onyx-connect" 17 +"../../src/libs/ReportUtils.ts" "rulesdir/no-onyx-connect" 16 "../../src/libs/SubscriptionUtils.ts" "rulesdir/no-onyx-connect" 2 "../../src/libs/UnreadIndicatorUpdater/index.ts" "no-restricted-syntax" 1 "../../src/libs/Violations/ViolationsUtils.ts" "no-restricted-syntax" 2 diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index f637325b1b5e..e01d03cd20a8 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -36,7 +36,6 @@ import SCREENS from '@src/SCREENS'; import type { BankAccountList, Beta, - BetaConfiguration, BillingGraceEndPeriod, IntroSelected, OnyxInputOrEntry, @@ -1139,12 +1138,6 @@ Onyx.connect({ }, }); -let betaConfiguration: OnyxEntry = {}; -Onyx.connect({ - key: ONYXKEYS.BETA_CONFIGURATION, - callback: (value) => (betaConfiguration = value ?? {}), -}); - let deprecatedAllTransactions: OnyxCollection = {}; let deprecatedReportsTransactions: Record = {}; Onyx.connect({ @@ -11635,7 +11628,9 @@ type PrepareOnboardingOnyxDataParams = { isInvitedAccountant?: boolean; onboardingPurposeSelected?: OnboardingPurpose; isSelfTourViewed?: boolean; - betas: OnyxEntry; + // Accepted for backwards compatibility with existing callers β€” the bespoke direct-post + // path no longer depends on a beta gate. Safe to drop once no caller passes it. + betas?: OnyxEntry; }; function getBespokeWelcomeMessage(companySize: OnboardingCompanySize | undefined): string { @@ -11675,7 +11670,6 @@ function prepareOnboardingOnyxData({ isInvitedAccountant, onboardingPurposeSelected, isSelfTourViewed, - betas, }: PrepareOnboardingOnyxDataParams) { if (engagementChoice === CONST.ONBOARDING_CHOICES.PERSONAL_SPEND) { // eslint-disable-next-line no-param-reassign From 0fca2907dadd15f4fa55cc4194d27c648bfb458a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Tue, 5 May 2026 16:10:09 +0900 Subject: [PATCH 03/11] Update bespoke welcome placeholder copy to match server's approved wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Optimistic placeholder header changes from "Welcome to Expensify πŸ‘‹" to "Your free trial has started! Let's get you set up." across all four tiers. - 1-10 body drops the "I'm Concierge, and I'll help you get set up." opening so the placeholder mirrors what the server posts. - 11-100 / 101-1000 / 1001+ bodies unchanged. Keeps the optimisticβ†’reconciled hand-off visually continuous when the server's reportActionID reconciliation lands. --- src/libs/ReportUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e01d03cd20a8..57b43932ef75 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11643,7 +11643,7 @@ function getBespokeWelcomeMessage(companySize: OnboardingCompanySize | undefined // // Markdown, not HTML: buildOptimisticAddCommentReportAction β†’ getParsedComment escapes // entities, so raw HTML tags would render as literal text. - const header = '# Welcome to Expensify πŸ‘‹\n'; + const header = "# Your free trial has started! Let's get you set up.\n"; switch (companySize) { case CONST.ONBOARDING_COMPANY_SIZE.SMALL: case CONST.ONBOARDING_COMPANY_SIZE.MEDIUM_SMALL: @@ -11653,7 +11653,7 @@ function getBespokeWelcomeMessage(companySize: OnboardingCompanySize | undefined case CONST.ONBOARDING_COMPANY_SIZE.LARGE: return `${header}\nI'm Concierge, here for quick questions while your rollout gets planned.`; default: - return `${header}\nI'm Concierge, and I'll help you get set up. For a team your size, the fastest path is adding a few expense categories, inviting everyone, and having them snap receipts.\n\nTry one of the suggestions, or reply with any questions.`; + return `${header}\nFor a team your size, the fastest path is adding a few expense categories, inviting everyone, and having them snap receipts.\n\nTry one of the suggestions, or reply with any questions.`; } } From 7d6a4074cef231a87435c4eb7dc8feeee6e083c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Wed, 6 May 2026 15:41:18 +0900 Subject: [PATCH 04/11] =?UTF-8?q?52519:=20simplify=20getBespokeWelcomeMess?= =?UTF-8?q?age=20=E2=80=94=20single=20return,=20no=20companySize=20param?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both tiers now share identical optimistic placeholder copy (header + Concierge intro + closing CTA). The only structural difference between 1-4 and 5+ cohorts is the guide block, which the server inserts on reconciliation via optimisticConciergeReportActionID β€” not something the App needs to know about. Removes the companySize switch and the per-tier string divergence that existed only to mirror what the server would eventually generate. --- src/libs/ReportUtils.ts | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 57b43932ef75..15cadfd26c95 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11633,28 +11633,18 @@ type PrepareOnboardingOnyxDataParams = { betas?: OnyxEntry; }; -function getBespokeWelcomeMessage(companySize: OnboardingCompanySize | undefined): string { +function getBespokeWelcomeMessage(): string { // Optimistic placeholder painted while CompleteGuidedSetup is in flight. The server's // `UserAPI::buildBespokeWelcomeMessage` reconciles via `optimisticConciergeReportActionID` - // and replaces this with the final body β€” including the guide block on TEAM 11+. Mirror - // the server's per-tier skeleton (header + Concierge intro + per-tier hint) here so the - // hand-off is visually continuous; the only change on reconciliation is the guide block - // appearing for non-MICRO cohorts. + // and replaces this with the final body β€” adding the guide block for 5+ users when an + // eligible guide is assigned. Both tiers share the same visible copy in this placeholder; + // the only change on reconciliation is the guide block appearing between the intro and the + // closing CTA for non-MICRO_SMALL cohorts. // // Markdown, not HTML: buildOptimisticAddCommentReportAction β†’ getParsedComment escapes // entities, so raw HTML tags would render as literal text. const header = "# Your free trial has started! Let's get you set up.\n"; - switch (companySize) { - case CONST.ONBOARDING_COMPANY_SIZE.SMALL: - case CONST.ONBOARDING_COMPANY_SIZE.MEDIUM_SMALL: - return `${header}\nI'm Concierge, here for quick questions any time.\n\nMost teams your size start by connecting accounting, setting up approvals, and inviting everyone.`; - case CONST.ONBOARDING_COMPANY_SIZE.MEDIUM: - return `${header}\nI'm Concierge, here for quick questions any time.\n\nMost teams your size start by connecting accounting, setting up multi-level approvals, and rolling out the Expensify Card.`; - case CONST.ONBOARDING_COMPANY_SIZE.LARGE: - return `${header}\nI'm Concierge, here for quick questions while your rollout gets planned.`; - default: - return `${header}\nFor a team your size, the fastest path is adding a few expense categories, inviting everyone, and having them snap receipts.\n\nTry one of the suggestions, or reply with any questions.`; - } + return `${header}\nHey! I'm Concierge, your AI assistant. Feel free to ask me any questions as they come up.\n\nIf you're ready to get started now, here are some recommended next steps based on your selections:`; } function prepareOnboardingOnyxData({ @@ -11765,7 +11755,7 @@ function prepareOnboardingOnyxData({ let bespokeAction: OptimisticReportAction | undefined; if (shouldUseFollowupsInsteadOfTasks) { - const bespokeMarkdown = getBespokeWelcomeMessage(companySize); + const bespokeMarkdown = getBespokeWelcomeMessage(); optimisticConciergeReportActionID = rand64(); // delegateAccountIDParam: will be threaded in PR 14; buildOptimisticAddCommentReportAction falls back to module-level Onyx.connect value (https://github.com/Expensify/App/issues/66425) bespokeAction = buildOptimisticAddCommentReportAction({ From 41c1461264c76c48aba3713fe27f2a223971fdc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Thu, 7 May 2026 15:09:38 +0900 Subject: [PATCH 05/11] Remove client-side bespoke welcome message generation to eliminate flash Server is now sole source of truth for admins room welcome content. App no longer builds an optimistic bespoke action, only generates optimisticConciergeReportActionID as a dedup key for the server's addComment call. This prevents the A to B content flash that occurred when the server's generated message replaced the App's optimistic message. --- .../parameters/CompleteGuidedSetupParams.ts | 1 - .../API/parameters/CreateWorkspaceParams.ts | 1 - src/libs/ReportUtils.ts | 85 ++----------------- src/libs/actions/Policy/Policy.ts | 10 +-- src/libs/actions/Report/index.ts | 3 +- tests/unit/ReportUtilsTest.ts | 21 ++--- 6 files changed, 13 insertions(+), 108 deletions(-) diff --git a/src/libs/API/parameters/CompleteGuidedSetupParams.ts b/src/libs/API/parameters/CompleteGuidedSetupParams.ts index 132e1d5eda68..f23ff59987f0 100644 --- a/src/libs/API/parameters/CompleteGuidedSetupParams.ts +++ b/src/libs/API/parameters/CompleteGuidedSetupParams.ts @@ -14,7 +14,6 @@ type CompleteGuidedSetupParams = { policyID?: string; selfDMReportID?: string; selfDMCreatedReportActionID?: string; - bespokeWelcomeMessage?: string; optimisticConciergeReportActionID?: string; /** Feature ids the user toggled on the InterestedFeatures onboarding page; shapes the #admins welcome followups on this single request. */ selectedInterestedFeatures?: string; diff --git a/src/libs/API/parameters/CreateWorkspaceParams.ts b/src/libs/API/parameters/CreateWorkspaceParams.ts index 557de5ccd744..1c0180e2939e 100644 --- a/src/libs/API/parameters/CreateWorkspaceParams.ts +++ b/src/libs/API/parameters/CreateWorkspaceParams.ts @@ -20,7 +20,6 @@ type CreateWorkspaceParams = { features?: string; shouldAddGuideWelcomeMessage?: boolean; areDistanceRatesEnabled?: boolean; - bespokeWelcomeMessage?: string; optimisticConciergeReportActionID?: string; }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 15cadfd26c95..05779b84cf3b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11633,20 +11633,6 @@ type PrepareOnboardingOnyxDataParams = { betas?: OnyxEntry; }; -function getBespokeWelcomeMessage(): string { - // Optimistic placeholder painted while CompleteGuidedSetup is in flight. The server's - // `UserAPI::buildBespokeWelcomeMessage` reconciles via `optimisticConciergeReportActionID` - // and replaces this with the final body β€” adding the guide block for 5+ users when an - // eligible guide is assigned. Both tiers share the same visible copy in this placeholder; - // the only change on reconciliation is the guide block appearing between the intro and the - // closing CTA for non-MICRO_SMALL cohorts. - // - // Markdown, not HTML: buildOptimisticAddCommentReportAction β†’ getParsedComment escapes - // entities, so raw HTML tags would render as literal text. - const header = "# Your free trial has started! Let's get you set up.\n"; - return `${header}\nHey! I'm Concierge, your AI assistant. Feel free to ask me any questions as they come up.\n\nIf you're ready to get started now, here are some recommended next steps based on your selections:`; -} - function prepareOnboardingOnyxData({ introSelected, engagementChoice, @@ -11748,29 +11734,10 @@ function prepareOnboardingOnyxData({ reportComment: textComment.commentText, }; - // When using followups instead of tasks, generate a bespoke welcome message from Concierge. - // The frontend displays it optimistically; the server uses it to generate suggested followups. - let bespokeWelcomeMessage: string | undefined; - let optimisticConciergeReportActionID: string | undefined; - let bespokeAction: OptimisticReportAction | undefined; - - if (shouldUseFollowupsInsteadOfTasks) { - const bespokeMarkdown = getBespokeWelcomeMessage(); - optimisticConciergeReportActionID = rand64(); - // delegateAccountIDParam: will be threaded in PR 14; buildOptimisticAddCommentReportAction falls back to module-level Onyx.connect value (https://github.com/Expensify/App/issues/66425) - bespokeAction = buildOptimisticAddCommentReportAction({ - text: bespokeMarkdown, - actorAccountID: CONST.ACCOUNT_ID.CONCIERGE, - createdOffset: 2, - reportID: targetChatReportID, - reportActionID: optimisticConciergeReportActionID, - delegateAccountIDParam: undefined, - }); - // Reuse the HTML that buildOptimisticAddCommentReportAction already parsed via getParsedComment, - // so we avoid calling getParsedComment a second time with the same input. - // The backend passes this to the LLM as HTML for AddComment, which expects HTML. - bespokeWelcomeMessage = bespokeAction.commentText; - } + // Generate a dedup ID for the server-side bespoke welcome. The server posts directly using + // this ID via addComment (idempotent on reportActionID), so we never add an optimistic action + // here β€” the real message arrives from the server without a flash. + const optimisticConciergeReportActionID: string | undefined = shouldUseFollowupsInsteadOfTasks ? rand64() : undefined; let createWorkspaceTaskReportID; let addExpenseApprovalsTaskReportID; @@ -12091,26 +12058,6 @@ function prepareOnboardingOnyxData({ }); } - if (bespokeAction && optimisticConciergeReportActionID) { - optimisticData.push( - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, - value: { - [optimisticConciergeReportActionID]: bespokeAction.reportAction as ReportAction, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${targetChatReportID}`, - value: { - lastVisibleActionCreated: bespokeAction.reportAction.created, - lastActorAccountID: CONST.ACCOUNT_ID.CONCIERGE, - }, - }, - ); - } - if (!wasInvited) { optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -12133,16 +12080,6 @@ function prepareOnboardingOnyxData({ }); } - if (bespokeAction && optimisticConciergeReportActionID) { - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, - value: { - [optimisticConciergeReportActionID]: {pendingAction: null, isOptimisticAction: null}, - }, - }); - } - let failureReport: Partial = { lastMessageText: '', lastVisibleActionCreated: '', @@ -12197,18 +12134,6 @@ function prepareOnboardingOnyxData({ }); } - if (bespokeAction && optimisticConciergeReportActionID) { - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${targetChatReportID}`, - value: { - [optimisticConciergeReportActionID]: { - errors: getMicroSecondOnyxErrorWithTranslationKey('report.genericAddCommentFailureMessage'), - } as ReportAction, - }, - }); - } - if (!wasInvited) { failureData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -12382,7 +12307,7 @@ function prepareOnboardingOnyxData({ }); } - return {optimisticData, successData, failureData, guidedSetupData, actorAccountID, selfDMParameters, bespokeWelcomeMessage, optimisticConciergeReportActionID}; + return {optimisticData, successData, failureData, guidedSetupData, actorAccountID, selfDMParameters, optimisticConciergeReportActionID}; } /** diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 23a76317178e..f3ee48cf2959 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -2890,18 +2890,10 @@ function buildPolicyData(options: BuildPolicyDataOptions): OnyxData 0 ? JSON.stringify(selectedInterestedFeatures) : undefined, }; diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index a1b044df60b6..9a7324abf705 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -652,13 +652,9 @@ describe('ReportUtils', () => { companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, }); expect(result?.guidedSetupData).toHaveLength(0); - // suggestedFollowups beta adds a bespoke Concierge welcome action optimistically for all company sizes + // Server is sole source of truth for the bespoke welcome β€” no optimistic action added here. const reportActionsEntries = result?.optimisticData.filter((i) => i.key === `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`); - expect(reportActionsEntries).toHaveLength(1); - expect(result?.bespokeWelcomeMessage).toBeDefined(); - // The bespoke message sent to the backend should be HTML (not raw markdown) - // so the server can pass it through to AddComment without formatting loss. - expect(result?.bespokeWelcomeMessage).toMatch(/<[^>]+>/); + expect(reportActionsEntries).toHaveLength(0); expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); @@ -680,7 +676,6 @@ describe('ReportUtils', () => { companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, }); expect(result?.guidedSetupData).toHaveLength(0); - expect(result?.bespokeWelcomeMessage).toContain('growing team'); expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); @@ -702,7 +697,6 @@ describe('ReportUtils', () => { companySize: CONST.ONBOARDING_COMPANY_SIZE.LARGE, }); expect(result?.guidedSetupData).toHaveLength(0); - expect(result?.bespokeWelcomeMessage).toContain('organization your size'); expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); @@ -724,7 +718,6 @@ describe('ReportUtils', () => { companySize: CONST.ONBOARDING_COMPANY_SIZE.MEDIUM_SMALL, }); expect(result?.guidedSetupData).toHaveLength(0); - expect(result?.bespokeWelcomeMessage).toContain('growing team'); expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); @@ -746,7 +739,6 @@ describe('ReportUtils', () => { companySize: CONST.ONBOARDING_COMPANY_SIZE.MEDIUM, }); expect(result?.guidedSetupData).toHaveLength(0); - expect(result?.bespokeWelcomeMessage).toContain('organization your size'); expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); @@ -768,8 +760,8 @@ describe('ReportUtils', () => { companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, userReportedIntegration: 'quickbooksOnline', }); - expect(result?.bespokeWelcomeMessage).toContain('QuickBooks Online'); - expect(result?.bespokeWelcomeMessage).toContain('expenses sync automatically'); + // Message content is now generated server-side; client only generates the dedup ID. + expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); it('should add guidedSetupData when posting into admin room WITHOUT suggestedFollowups beta', async () => { @@ -826,12 +818,11 @@ describe('ReportUtils', () => { selectedInterestedFeatures: ['areCompanyCardsEnabled'], companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, }); - // Followups path active: no tasks generated, bespoke welcome posted optimistically to #admins. + // Followups path active: no tasks generated; server posts bespoke welcome (no optimistic action). expect(result?.guidedSetupData.filter((data) => data.type === 'task')).toHaveLength(0); - expect(result?.bespokeWelcomeMessage).toBeDefined(); expect(result?.optimisticConciergeReportActionID).toBeDefined(); const adminsRoomActions = result?.optimisticData.filter((i) => i.key === `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`); - expect(adminsRoomActions?.length).toBeGreaterThan(0); + expect(adminsRoomActions).toHaveLength(0); }); it('should not create tasks if the task feature is not in the selected interested features', () => { From 0ffa9dbb1b2d6c14d1d706de5ea52a69ca9004bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Wed, 20 May 2026 13:34:46 +0800 Subject: [PATCH 06/11] Fix failing tests and spellcheck after shouldUseFollowupsInsteadOfTasks change - Update tests that used MANAGE_TEAM + SMALL to stay on the tasks path; they now use LOOKING_AROUND since every MANAGE_TEAM signup uses followups - Rewrite "without suggestedFollowups beta" test to verify the new behavior (MANAGE_TEAM always produces followups, never tasks) - Replace "dedup" with "deduplication" in two comments to pass cspell --- src/libs/ReportUtils.ts | 2 +- tests/unit/ReportUtilsTest.ts | 42 +++++++++-------------------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 42c3e343292e..378500f374b7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11690,7 +11690,7 @@ function prepareOnboardingOnyxData({ reportComment: textComment.commentText, }; - // Generate a dedup ID for the server-side bespoke welcome. The server posts directly using + // Generate a deduplication ID for the server-side bespoke welcome. The server posts directly using // this ID via addComment (idempotent on reportActionID), so we never add an optimistic action // here β€” the real message arrives from the server without a flash. const optimisticConciergeReportActionID: string | undefined = shouldUseFollowupsInsteadOfTasks ? rand64() : undefined; diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 5ae7a7402380..023b0df378e1 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -580,7 +580,7 @@ describe('ReportUtils', () => { prepareOnboardingOnyxData({ introSelected: undefined, betas: undefined, - engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', tasks: [ @@ -593,7 +593,6 @@ describe('ReportUtils', () => { ], }, adminsChatReportID: '1', - // SMALL keeps this in the tasks path; MICRO routes through Phase 1 followups (no tasks generated). companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, }); @@ -611,7 +610,7 @@ describe('ReportUtils', () => { prepareOnboardingOnyxData({ introSelected: undefined, betas: undefined, - engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', tasks: [ @@ -624,7 +623,6 @@ describe('ReportUtils', () => { ], }, adminsChatReportID: '1', - // SMALL keeps this in the tasks path; MICRO routes through Phase 1 followups (no tasks generated). companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, }); @@ -763,14 +761,12 @@ describe('ReportUtils', () => { companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, userReportedIntegration: 'quickbooksOnline', }); - // Message content is now generated server-side; client only generates the dedup ID. + // Message content is now generated server-side; client only generates the deduplication ID. expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); - it('should add guidedSetupData when posting into admin room WITHOUT suggestedFollowups beta', async () => { + it('should use followups for MANAGE_TEAM regardless of betas or company size', async () => { const adminsChatReportID = '1'; - // Not having `+` in the email allows for `isPostingTasksInAdminsRoom` flow - await Onyx.merge(ONYXKEYS.SESSION, {email: 'test@example.com'}); await waitForBatchedUpdates(); const result = prepareOnboardingOnyxData({ @@ -785,21 +781,9 @@ describe('ReportUtils', () => { selectedInterestedFeatures: ['areCompanyCardsEnabled'], companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, }); - // Without the beta, tasks SHOULD be generated (old behavior) β€” uses SMALL (not MICRO) - // because MICRO + MANAGE_TEAM users bypass the beta gate and get followups directly - expect(result?.guidedSetupData).toHaveLength(3); - const taskReportIDs = - result?.guidedSetupData.reduce((acc, item) => { - if (item.type === 'task' && typeof item.taskReportID === 'string') { - acc.push(item.taskReportID); - } - return acc; - }, []) ?? []; - expect(taskReportIDs.length).toBeGreaterThan(0); - for (const taskReportID of taskReportIDs) { - const taskReportUpdate = result?.optimisticData.find((update) => update.key === `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`); - expect((taskReportUpdate?.value as Report | undefined)?.chatType).toBe(CONST.REPORT.CHAT_TYPE.POLICY_ADMINS); - } + // Every MANAGE_TEAM signup uses the bespoke followups path β€” tasks are never generated + expect(result?.guidedSetupData.filter((data) => data.type === 'task')).toHaveLength(0); + expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); it('should generate followups (not tasks) for `+` email users in the MANAGE_TEAM + MICRO Phase 1 cohort', async () => { @@ -885,7 +869,7 @@ describe('ReportUtils', () => { prepareOnboardingOnyxData({ introSelected: undefined, betas: undefined, - engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', tasks: [ @@ -898,7 +882,6 @@ describe('ReportUtils', () => { ], }, adminsChatReportID: '1', - // SMALL keeps the tasks path active; MICRO routes through Phase 1 followups (no tasks generated). companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, }); @@ -977,7 +960,7 @@ describe('ReportUtils', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, betas: undefined, - engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', tasks: [ @@ -990,7 +973,6 @@ describe('ReportUtils', () => { ], }, adminsChatReportID: '1', - // SMALL keeps the tasks path active; MANAGE_TEAM + MICRO routes through Phase 1 followups. companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, isSelfTourViewed: true, }); @@ -1006,7 +988,7 @@ describe('ReportUtils', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, betas: undefined, - engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', tasks: [ @@ -1019,7 +1001,6 @@ describe('ReportUtils', () => { ], }, adminsChatReportID: '1', - // SMALL keeps the tasks path active; MANAGE_TEAM + MICRO routes through Phase 1 followups. companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, isSelfTourViewed: false, }); @@ -1038,7 +1019,7 @@ describe('ReportUtils', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, betas: undefined, - engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', tasks: [ @@ -1051,7 +1032,6 @@ describe('ReportUtils', () => { ], }, adminsChatReportID: '1', - // SMALL keeps the tasks path active; MANAGE_TEAM + MICRO routes through Phase 1 followups. companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, isSelfTourViewed: undefined, }); From 4f17b1a47739d3a562269f186017a7dd4e87e12c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Wed, 20 May 2026 13:57:32 +0800 Subject: [PATCH 07/11] tests(PolicyTest): fix VIEW_TOUR tests broken by MANAGE_TEAM followups migration --- tests/actions/PolicyTest.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index eaa99cb4beef..55a2d56c5287 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -1397,6 +1397,9 @@ describe('actions/Policy', () => { it('should mark VIEW_TOUR task as completed in guidedSetupData when isSelfTourViewed is true', async () => { await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID}); + // EMPLOYER posts onboarding tasks to the Concierge chat (not #admins). Set a concierge + // report ID so prepareOnboardingOnyxData can resolve a target chat and does not early-return. + await Onyx.set(ONYXKEYS.CONCIERGE_REPORT_ID, 'concierge-report-1'); await waitForBatchedUpdates(); const apiWriteSpy = jest.spyOn(require('@libs/API'), 'write').mockImplementation(() => Promise.resolve()); @@ -1405,12 +1408,14 @@ describe('actions/Policy', () => { // When creating a workspace with isSelfTourViewed set to true. // introSelected.choice is left undefined to simulate a user who hasn't completed onboarding yet β€” // that's the only state in which the onboarding-tasks block runs (see buildPolicyData guard). + // EMPLOYER is used because it has a VIEW_TOUR task (testDriveEmployeeTask); MANAGE_TEAM now uses + // the bespoke followups path (no tasks) so it no longer exercises this code path. Policy.createWorkspace({ policyOwnerEmail: ESH_EMAIL, makeMeAdmin: true, policyName: WORKSPACE_NAME, policyID, - engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER, introSelected: {}, currentUserAccountIDParam: ESH_ACCOUNT_ID, currentUserEmailParam: ESH_EMAIL, @@ -1441,6 +1446,9 @@ describe('actions/Policy', () => { it('should not mark VIEW_TOUR task as completed in guidedSetupData when isSelfTourViewed is false', async () => { await Onyx.set(ONYXKEYS.SESSION, {email: ESH_EMAIL, accountID: ESH_ACCOUNT_ID}); + // EMPLOYER posts onboarding tasks to the Concierge chat (not #admins). Set a concierge + // report ID so prepareOnboardingOnyxData can resolve a target chat and does not early-return. + await Onyx.set(ONYXKEYS.CONCIERGE_REPORT_ID, 'concierge-report-1'); await waitForBatchedUpdates(); const apiWriteSpy = jest.spyOn(require('@libs/API'), 'write').mockImplementation(() => Promise.resolve()); @@ -1449,12 +1457,14 @@ describe('actions/Policy', () => { // When creating a workspace with isSelfTourViewed set to false. // introSelected.choice is left undefined to simulate a user who hasn't completed onboarding yet β€” // that's the only state in which the onboarding-tasks block runs (see buildPolicyData guard). + // EMPLOYER is used because it has a VIEW_TOUR task (testDriveEmployeeTask); MANAGE_TEAM now uses + // the bespoke followups path (no tasks) so it no longer exercises this code path. Policy.createWorkspace({ policyOwnerEmail: ESH_EMAIL, makeMeAdmin: true, policyName: WORKSPACE_NAME, policyID, - engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, + engagementChoice: CONST.ONBOARDING_CHOICES.EMPLOYER, introSelected: {}, currentUserAccountIDParam: ESH_ACCOUNT_ID, currentUserEmailParam: ESH_EMAIL, From 81ffd5bbfee2381ec727293994a36b5a4080fc09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Wed, 20 May 2026 14:32:23 +0800 Subject: [PATCH 08/11] Remove dead code in task filter/map that is unreachable for MANAGE_TEAM MANAGE_TEAM always produces an empty `tasks` array, so the `.filter()` and `.map()` callbacks never execute for that choice. Three blocks were unreachable: - Feature-gating filter based on `selectedInterestedFeatures` (MANAGE_TEAM branch) - VIEW_TOUR skip guarded by `engagementChoice === MANAGE_TEAM` - `emailCreatingAction` ternary that always resolved to CONCIERGE for non-MANAGE_TEAM callers --- src/libs/ReportUtils.ts | 31 +------------------------------ 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 378500f374b7..845f6603ca97 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11703,12 +11703,6 @@ function prepareOnboardingOnyxData({ const tasks = shouldUseFollowupsInsteadOfTasks ? [] : onboardingMessage.tasks; const tasksData = tasks .filter((task) => { - if (engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM) { - if (!!selectedInterestedFeatures && TASK_TO_FEATURE[task.type] && !selectedInterestedFeatures.includes(TASK_TO_FEATURE[task.type])) { - return false; - } - } - if (([CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES, CONST.ONBOARDING_TASK_TYPE.SETUP_TAGS] as string[]).includes(task.type) && userReportedIntegration) { return false; } @@ -11716,28 +11710,6 @@ function prepareOnboardingOnyxData({ if (([CONST.ONBOARDING_TASK_TYPE.ADD_ACCOUNTING_INTEGRATION, CONST.ONBOARDING_TASK_TYPE.SETUP_CATEGORIES_AND_TAGS] as string[]).includes(task.type) && !userReportedIntegration) { return false; } - type SkipViewTourOnboardingChoices = - | typeof CONST.ONBOARDING_CHOICES.SUBMIT - | typeof CONST.ONBOARDING_CHOICES.CHAT_SPLIT - | typeof CONST.ONBOARDING_CHOICES.PERSONAL_SPEND - | typeof CONST.ONBOARDING_CHOICES.EMPLOYER - | typeof CONST.ONBOARDING_CHOICES.TRACK_PERSONAL - | typeof CONST.ONBOARDING_CHOICES.MANAGE_TEAM; - if ( - task.type === CONST.ONBOARDING_TASK_TYPE.VIEW_TOUR && - [ - CONST.ONBOARDING_CHOICES.EMPLOYER, - CONST.ONBOARDING_CHOICES.PERSONAL_SPEND, - CONST.ONBOARDING_CHOICES.TRACK_PERSONAL, - CONST.ONBOARDING_CHOICES.SUBMIT, - CONST.ONBOARDING_CHOICES.CHAT_SPLIT, - CONST.ONBOARDING_CHOICES.MANAGE_TEAM, - ].includes(introSelected?.choice as SkipViewTourOnboardingChoices) && - engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM - ) { - return false; - } - // Exclude createWorkspace and viewTour tasks from #admin room, for test drive receivers, // since these users already have them in concierge if ( @@ -11763,8 +11735,7 @@ function prepareOnboardingOnyxData({ CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, targetChatType, ); - const emailCreatingAction = - engagementChoice === CONST.ONBOARDING_CHOICES.MANAGE_TEAM ? (allPersonalDetails?.[actorAccountID]?.login ?? CONST.EMAIL.CONCIERGE) : CONST.EMAIL.CONCIERGE; + const emailCreatingAction = CONST.EMAIL.CONCIERGE; const taskCreatedAction = buildOptimisticCreatedReportAction({emailCreatingAction}); const taskReportAction = buildOptimisticTaskCommentReportAction(currentTask.reportID, taskTitle, 0, `task for ${taskTitle}`, targetChatReportID, actorAccountID, index + 3); currentTask.parentReportActionID = taskReportAction.reportAction.reportActionID; From 2403e58978bcd993cb1a38e8091ca444443c2ddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Wed, 20 May 2026 15:02:59 +0800 Subject: [PATCH 09/11] Address PR review: fix phantom-message bug and remove unused betas param MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix lastVisibleActionCreated advancing without an optimistic action on the MANAGE_TEAM path: add `shouldUseFollowupsInsteadOfTasks ||` to the skip condition so the field is never set when followups replace tasks. - Remove `betas` from PrepareOnboardingOnyxDataParams, CompleteOnboardingProps, and getGuidedSetupDataForOpenReport β€” the field was accepted but never consumed once the beta gate was removed. Drop from all callers and tests. --- src/hooks/useAutoCreateSubmitWorkspace.ts | 1 - src/hooks/useAutoCreateTrackWorkspace.ts | 1 - src/libs/ReportUtils.ts | 5 +-- src/libs/actions/IOU/PayMoneyRequest.ts | 1 - src/libs/actions/IOU/TrackExpense.ts | 1 - src/libs/actions/Policy/Policy.ts | 1 - src/libs/actions/Report/index.ts | 9 +---- .../BaseOnboardingInterestedFeatures.tsx | 1 - .../BaseOnboardingPersonalDetails.tsx | 3 -- .../BaseOnboardingPurpose.tsx | 2 - .../BaseOnboardingWorkspaceInvite.tsx | 2 - .../BaseOnboardingWorkspaceOptional.tsx | 1 - .../BaseOnboardingWorkspaces.tsx | 2 - tests/actions/ReportTest.ts | 10 ++--- tests/unit/ReportUtilsTest.ts | 37 +++++-------------- 15 files changed, 15 insertions(+), 62 deletions(-) diff --git a/src/hooks/useAutoCreateSubmitWorkspace.ts b/src/hooks/useAutoCreateSubmitWorkspace.ts index 48273c938bfd..950dfa08cd0d 100644 --- a/src/hooks/useAutoCreateSubmitWorkspace.ts +++ b/src/hooks/useAutoCreateSubmitWorkspace.ts @@ -80,7 +80,6 @@ function useAutoCreateSubmitWorkspace() { onboardingPolicyID: newPolicyID, introSelected, isSelfTourViewed, - betas, }); setOnboardingAdminsChatReportID(); diff --git a/src/hooks/useAutoCreateTrackWorkspace.ts b/src/hooks/useAutoCreateTrackWorkspace.ts index 5736ab802ebd..9791e5c3a5e1 100644 --- a/src/hooks/useAutoCreateTrackWorkspace.ts +++ b/src/hooks/useAutoCreateTrackWorkspace.ts @@ -99,7 +99,6 @@ function useAutoCreateTrackWorkspace() { shouldWaitForRHPVariantInitialization: isSidePanelReportSupported, introSelected, isSelfTourViewed, - betas, }); if (isSidePanelReportSupported) { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 845f6603ca97..af5a7b76b7ce 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11582,9 +11582,6 @@ type PrepareOnboardingOnyxDataParams = { onboardingPurposeSelected?: OnboardingPurpose; // TODO: isSelfTourViewed will be required eventually. Refactor issue: https://github.com/Expensify/App/issues/66424 isSelfTourViewed?: boolean; - // Accepted for backwards compatibility with existing callers β€” the bespoke direct-post - // path no longer depends on a beta gate. Safe to drop once no caller passes it. - betas?: OnyxEntry; // TODO: hasCompletedGuidedSetupFlow will be required eventually. Refactor issue: https://github.com/Expensify/App/issues/66424 hasCompletedGuidedSetupFlow?: boolean; }; @@ -11965,7 +11962,7 @@ function prepareOnboardingOnyxData({ key: `${ONYXKEYS.COLLECTION.REPORT}${targetChatReportID}`, value: { hasOutstandingChildTask, - ...(skipSignOff && !message ? {} : {lastVisibleActionCreated}), + ...(shouldUseFollowupsInsteadOfTasks || (skipSignOff && !message) ? {} : {lastVisibleActionCreated}), lastActorAccountID: actorAccountID, }, }, diff --git a/src/libs/actions/IOU/PayMoneyRequest.ts b/src/libs/actions/IOU/PayMoneyRequest.ts index 79e43c1efbed..1f9f454f19e8 100644 --- a/src/libs/actions/IOU/PayMoneyRequest.ts +++ b/src/libs/actions/IOU/PayMoneyRequest.ts @@ -731,7 +731,6 @@ function completePaymentOnboarding( companySize: introSelected?.companySize as OnboardingCompanySize, introSelected, isSelfTourViewed, - betas, }); } diff --git a/src/libs/actions/IOU/TrackExpense.ts b/src/libs/actions/IOU/TrackExpense.ts index 4248c9a81833..3060049f4780 100644 --- a/src/libs/actions/IOU/TrackExpense.ts +++ b/src/libs/actions/IOU/TrackExpense.ts @@ -1801,7 +1801,6 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation): {iouRep onboardingMessage: getOnboardingMessages().onboardingMessages[CONST.ONBOARDING_CHOICES.TEST_DRIVE_RECEIVER], companySize: undefined, isSelfTourViewed, - betas, })?.guidedSetupData : undefined; diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 63af4c9edf1e..80e298d86f2e 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -3011,7 +3011,6 @@ function buildPolicyData(options: BuildPolicyDataOptions): OnyxData, - betas: OnyxEntry, // TODO: This will be required eventually. Refactor issue: https://github.com/Expensify/App/issues/66424 isSelfTourViewed?: boolean, // TODO: This will be required eventually. Refactor issue: https://github.com/Expensify/App/issues/66424 @@ -1417,7 +1416,6 @@ function getGuidedSetupDataForOpenReport( companySize: introSelected?.companySize as OnboardingCompanySize, isSelfTourViewed, hasCompletedGuidedSetupFlow, - betas, }); if (!onboardingData) { @@ -1695,7 +1693,7 @@ function openReport(params: OpenReportActionParams) { }); } - const guidedSetup = getGuidedSetupDataForOpenReport(introSelected, betas, isSelfTourViewed, hasCompletedGuidedSetupFlow); + const guidedSetup = getGuidedSetupDataForOpenReport(introSelected, isSelfTourViewed, hasCompletedGuidedSetupFlow); if (guidedSetup) { optimisticData.push(...guidedSetup.optimisticData); successData.push(...guidedSetup.successData); @@ -2056,7 +2054,7 @@ function createGroupChat( } // Preserve guided setup data when creating group chats - const guidedSetup = getGuidedSetupDataForOpenReport(introSelected, betas, isSelfTourViewed); + const guidedSetup = getGuidedSetupDataForOpenReport(introSelected, isSelfTourViewed); if (guidedSetup) { optimisticData.push(...guidedSetup.optimisticData); successData.push(...guidedSetup.successData); @@ -5389,7 +5387,6 @@ type CompleteOnboardingProps = { shouldWaitForRHPVariantInitialization?: boolean; introSelected: OnyxEntry; isSelfTourViewed: boolean | undefined; - betas: OnyxEntry; }; async function completeOnboarding({ @@ -5409,7 +5406,6 @@ async function completeOnboarding({ shouldWaitForRHPVariantInitialization = false, introSelected, isSelfTourViewed, - betas, }: CompleteOnboardingProps) { const onboardingData = prepareOnboardingOnyxData({ introSelected, @@ -5424,7 +5420,6 @@ async function completeOnboarding({ isInvitedAccountant, onboardingPurposeSelected, isSelfTourViewed, - betas, }); if (!onboardingData) { return; diff --git a/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx b/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx index 04087edb88e3..b717ade6c111 100644 --- a/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx +++ b/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx @@ -237,7 +237,6 @@ function BaseOnboardingInterestedFeatures({shouldUseNativeStyles}: BaseOnboardin shouldWaitForRHPVariantInitialization: isSidePanelReportSupported, introSelected, isSelfTourViewed, - betas, }); // Avoid creating new WS because onboardingPolicyID is cleared before unmounting diff --git a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx index d726e8e7be08..72e133312b6c 100644 --- a/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx +++ b/src/pages/OnboardingPersonalDetails/BaseOnboardingPersonalDetails.tsx @@ -46,7 +46,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat const [onboardingAdminsChatReportID] = useOnyx(ONYXKEYS.ONBOARDING_ADMINS_CHAT_REPORT_ID); const [account] = useOnyx(ONYXKEYS.ACCOUNT); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); - const [betas] = useOnyx(ONYXKEYS.BETAS); const archivedReportsIdSet = useArchivedReportsIdSet(); const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); const [onboardingValues] = useOnyx(ONYXKEYS.NVP_ONBOARDING); @@ -94,7 +93,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat onboardingPolicyID, introSelected, isSelfTourViewed, - betas, }); setOnboardingAdminsChatReportID(); @@ -122,7 +120,6 @@ function BaseOnboardingPersonalDetails({currentUserPersonalDetails, shouldUseNat conciergeChatReportID, introSelected, isSelfTourViewed, - betas, ], ); diff --git a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx index 57aade65e730..a98ac0e21089 100644 --- a/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx +++ b/src/pages/OnboardingPurpose/BaseOnboardingPurpose.tsx @@ -76,7 +76,6 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro const [onboardingCompanySize] = useOnyx(ONYXKEYS.ONBOARDING_COMPANY_SIZE); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); - const [betas] = useOnyx(ONYXKEYS.BETAS); const {isBetaEnabled} = usePermissions(); const canUseSubmit2026 = isBetaEnabled(CONST.BETAS.SUBMIT_2026); const autoCreateSubmitWorkspace = useAutoCreateSubmitWorkspace(); @@ -133,7 +132,6 @@ function BaseOnboardingPurpose({shouldUseNativeStyles, shouldEnableMaxHeight, ro companySize: onboardingCompanySize, introSelected, isSelfTourViewed, - betas, }); return; diff --git a/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx b/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx index 9c93e044f100..78c27eda2e32 100644 --- a/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx +++ b/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx @@ -50,7 +50,6 @@ function BaseOnboardingWorkspaceInvite({shouldUseNativeStyles}: BaseOnboardingWo const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); - const [betas] = useOnyx(ONYXKEYS.BETAS); const policy = usePolicy(onboardingPolicyID); const {onboardingMessages} = useOnboardingMessages(); // We need to use isSmallScreenWidth, see navigateAfterOnboarding function comment @@ -136,7 +135,6 @@ function BaseOnboardingWorkspaceInvite({shouldUseNativeStyles}: BaseOnboardingWo onboardingPurposeSelected, introSelected, isSelfTourViewed, - betas, }); setOnboardingAdminsChatReportID(); diff --git a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx index 520dca0abc9b..7b32fd8df6c8 100644 --- a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx +++ b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx @@ -119,7 +119,6 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding adminsChatReportID: resolvedAdminsChatReportID, onboardingPolicyID: resolvedPolicyID, introSelected, - betas, isSelfTourViewed, }); diff --git a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx index 1e44ff822506..da170866909b 100644 --- a/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx +++ b/src/pages/OnboardingWorkspaces/BaseOnboardingWorkspaces.tsx @@ -59,7 +59,6 @@ function BaseOnboardingWorkspaces({route, shouldUseNativeStyles}: BaseOnboarding const [session] = useOnyx(ONYXKEYS.SESSION); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); - const [betas] = useOnyx(ONYXKEYS.BETAS); const archivedReportsIdSet = useArchivedReportsIdSet(); const isValidated = isCurrentUserValidated(loginList, session?.email); @@ -95,7 +94,6 @@ function BaseOnboardingWorkspaces({route, shouldUseNativeStyles}: BaseOnboarding companySize: onboardingCompanySize, introSelected, isSelfTourViewed, - betas, }); setOnboardingAdminsChatReportID(); setOnboardingPolicyID(policy.policyID); diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index f86be37425f6..939eb79f09e7 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -2780,7 +2780,6 @@ describe('actions/Report', () => { userReportedIntegration: null, introSelected: {choice: engagementChoice}, isSelfTourViewed: false, - betas: [CONST.BETAS.ALL], }); await waitForBatchedUpdates(); @@ -2820,7 +2819,6 @@ describe('actions/Report', () => { selectedInterestedFeatures, introSelected: {choice: engagementChoice}, isSelfTourViewed: false, - betas: [CONST.BETAS.ALL], }); await waitForBatchedUpdates(); @@ -4390,7 +4388,6 @@ describe('actions/Report', () => { userReportedIntegration: null, introSelected: {choice: engagementChoice}, isSelfTourViewed: false, - betas: [CONST.BETAS.ALL], }); await waitForBatchedUpdates(); @@ -7343,14 +7340,13 @@ describe('actions/Report', () => { expect(result?.failureData).toBeDefined(); }); - it('should return guided setup data when betas are explicitly passed', async () => { + it('should return guided setup data for ADMIN choice', async () => { await setupUserWithConciergeChat(); await Onyx.merge(ONYXKEYS.NVP_ONBOARDING, {hasCompletedGuidedSetupFlow: false}); await waitForBatchedUpdates(); const introSelected: OnyxTypes.IntroSelected = {choice: CONST.ONBOARDING_CHOICES.ADMIN, isInviteOnboardingComplete: false}; - const betas: OnyxTypes.Beta[] = [CONST.BETAS.SUGGESTED_FOLLOWUPS]; - const result = Report.getGuidedSetupDataForOpenReport(introSelected, betas); + const result = Report.getGuidedSetupDataForOpenReport(introSelected); expect(result).toBeDefined(); expect(result?.guidedSetupData).toBeDefined(); @@ -7368,7 +7364,7 @@ describe('actions/Report', () => { await waitForBatchedUpdates(); const introSelected: OnyxTypes.IntroSelected = {choice: CONST.ONBOARDING_CHOICES.SUBMIT, isInviteOnboardingComplete: false}; - const result = Report.getGuidedSetupDataForOpenReport(introSelected, undefined, isSelfTourViewed); + const result = Report.getGuidedSetupDataForOpenReport(introSelected, isSelfTourViewed); expect(result).toBeDefined(); const guidedSetupData = JSON.parse(result?.guidedSetupData ?? '[]') as Array<{type: string; task?: string; completedTaskReportActionID?: string}>; diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 023b0df378e1..3a54a6636591 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -579,7 +579,6 @@ describe('ReportUtils', () => { prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', @@ -609,7 +608,6 @@ describe('ReportUtils', () => { prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', @@ -634,7 +632,7 @@ describe('ReportUtils', () => { ); }); - it('should not add anything to guidedSetupData when posting into the admin room with suggestedFollowups beta', async () => { + it('should not add anything to guidedSetupData when posting into the admin room for MANAGE_TEAM', async () => { const adminsChatReportID = '1'; // Not having `+` in the email allows for `isPostingTasksInAdminsRoom` flow await Onyx.merge(ONYXKEYS.SESSION, {email: 'test@example.com'}); @@ -642,7 +640,6 @@ describe('ReportUtils', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: [CONST.BETAS.SUGGESTED_FOLLOWUPS], engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, onboardingMessage: { message: 'This is a test', @@ -659,14 +656,13 @@ describe('ReportUtils', () => { expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); - it('should generate bespoke welcome message for SMALL company sizes with suggestedFollowups beta', async () => { + it('should generate bespoke welcome message for SMALL company sizes', async () => { const adminsChatReportID = '1'; await Onyx.merge(ONYXKEYS.SESSION, {email: 'test@example.com'}); await waitForBatchedUpdates(); const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: [CONST.BETAS.SUGGESTED_FOLLOWUPS], engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, onboardingMessage: { message: 'This is a test', @@ -680,14 +676,13 @@ describe('ReportUtils', () => { expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); - it('should generate bespoke welcome message for LARGE company sizes with suggestedFollowups beta', async () => { + it('should generate bespoke welcome message for LARGE company sizes', async () => { const adminsChatReportID = '1'; await Onyx.merge(ONYXKEYS.SESSION, {email: 'test@example.com'}); await waitForBatchedUpdates(); const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: [CONST.BETAS.SUGGESTED_FOLLOWUPS], engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, onboardingMessage: { message: 'This is a test', @@ -701,14 +696,13 @@ describe('ReportUtils', () => { expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); - it('should generate bespoke welcome message for MEDIUM_SMALL company sizes with suggestedFollowups beta', async () => { + it('should generate bespoke welcome message for MEDIUM_SMALL company sizes', async () => { const adminsChatReportID = '1'; await Onyx.merge(ONYXKEYS.SESSION, {email: 'test@example.com'}); await waitForBatchedUpdates(); const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: [CONST.BETAS.SUGGESTED_FOLLOWUPS], engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, onboardingMessage: { message: 'This is a test', @@ -722,14 +716,13 @@ describe('ReportUtils', () => { expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); - it('should generate bespoke welcome message for MEDIUM company sizes with suggestedFollowups beta', async () => { + it('should generate bespoke welcome message for MEDIUM company sizes', async () => { const adminsChatReportID = '1'; await Onyx.merge(ONYXKEYS.SESSION, {email: 'test@example.com'}); await waitForBatchedUpdates(); const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: [CONST.BETAS.SUGGESTED_FOLLOWUPS], engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, onboardingMessage: { message: 'This is a test', @@ -750,7 +743,6 @@ describe('ReportUtils', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: [CONST.BETAS.SUGGESTED_FOLLOWUPS], engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, onboardingMessage: { message: 'This is a test', @@ -765,13 +757,12 @@ describe('ReportUtils', () => { expect(result?.optimisticConciergeReportActionID).toBeDefined(); }); - it('should use followups for MANAGE_TEAM regardless of betas or company size', async () => { + it('should use followups for MANAGE_TEAM unconditionally', async () => { const adminsChatReportID = '1'; await waitForBatchedUpdates(); const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: [], engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, onboardingMessage: { message: 'This is a test', @@ -795,7 +786,6 @@ describe('ReportUtils', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, onboardingMessage: { message: 'This is a test', @@ -815,7 +805,6 @@ describe('ReportUtils', () => { it('should not create tasks if the task feature is not in the selected interested features', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, onboardingMessage: { message: 'This is a test', @@ -839,7 +828,6 @@ describe('ReportUtils', () => { prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.MANAGE_TEAM, onboardingMessage: { message: 'This is a test', @@ -868,7 +856,6 @@ describe('ReportUtils', () => { prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', @@ -901,7 +888,6 @@ describe('ReportUtils', () => { it('should produce empty guidedSetupData for LOOKING_AROUND intent with empty message', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: '', @@ -917,7 +903,6 @@ describe('ReportUtils', () => { it('should not include sign-off message for LOOKING_AROUND intent', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: '', @@ -939,7 +924,6 @@ describe('ReportUtils', () => { it('should include guidedSetupData for non-LOOKING_AROUND intents', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.PERSONAL_SPEND, onboardingMessage: { message: 'Here is how to track your spend in a few clicks.', @@ -959,7 +943,6 @@ describe('ReportUtils', () => { it('should auto-complete VIEW_TOUR task when isSelfTourViewed is true', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', @@ -987,7 +970,6 @@ describe('ReportUtils', () => { it('should not auto-complete VIEW_TOUR task when isSelfTourViewed is false', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', @@ -1018,7 +1000,6 @@ describe('ReportUtils', () => { const result = prepareOnboardingOnyxData({ introSelected: undefined, - betas: undefined, engagementChoice: CONST.ONBOARDING_CHOICES.LOOKING_AROUND, onboardingMessage: { message: 'This is a test', @@ -6564,10 +6545,10 @@ describe('ReportUtils', () => { chatReport: mockedChatReport, currentReportId: '', isInFocusMode: false, - betas: [], doesReportHaveViolations: false, excludeEmptyChats: true, draftComment: '', + betas: undefined, isReportArchived: undefined, }), ).toBeFalsy(); @@ -12761,10 +12742,10 @@ describe('ReportUtils', () => { chatReport: undefined, currentReportId: undefined, isInFocusMode: true, - betas: undefined, doesReportHaveViolations: false, excludeEmptyChats: false, draftComment: undefined, + betas: undefined, isReportArchived: undefined, }); @@ -12804,10 +12785,10 @@ describe('ReportUtils', () => { chatReport: undefined, currentReportId: undefined, isInFocusMode: true, - betas: undefined, doesReportHaveViolations: false, excludeEmptyChats: false, draftComment: undefined, + betas: undefined, isReportArchived: undefined, }); From a25fb1a33d182963699175ccd5bae14aa0de60df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Wed, 20 May 2026 15:12:16 +0800 Subject: [PATCH 10/11] Fix React Compiler regression: remove stale betas dep from completeOnboarding --- .../BaseOnboardingWorkspaceOptional.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx index 7b32fd8df6c8..c6101013eef3 100644 --- a/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx +++ b/src/pages/OnboardingWorkspaceOptional/BaseOnboardingWorkspaceOptional.tsx @@ -148,7 +148,6 @@ function BaseOnboardingWorkspaceOptional({shouldUseNativeStyles}: BaseOnboarding mergedAccountConciergeReportID, introSelected, conciergeChatReportID, - betas, isSelfTourViewed, ], ); From 86e23d6caf39fb195146a0dfba5d8645db6a2665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marco=20Ch=C3=A1vez?= Date: Wed, 20 May 2026 15:48:42 +0800 Subject: [PATCH 11/11] Fix ESLint no-unused-vars errors Remove unused TASK_TO_FEATURE import from ReportUtils.ts, remove unused selectedInterestedFeatures from PrepareOnboardingOnyxDataParams and its consumers, remove unused betas destructuring from buildPolicyData and openReport while keeping betas in BuildPolicyDataOptions type so existing callers are unaffected. --- src/libs/ReportUtils.ts | 4 +--- src/libs/actions/Policy/Policy.ts | 3 +-- src/libs/actions/Report/index.ts | 2 -- .../BaseOnboardingWorkspaceOptional.tsx | 3 --- tests/unit/ReportUtilsTest.ts | 9 --------- 5 files changed, 2 insertions(+), 19 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index af5a7b76b7ce..8508e2b65beb 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -25,7 +25,7 @@ import type PolicyData from '@hooks/usePolicyData/types'; import type {PolicyTagList} from '@pages/workspace/tags/types'; import type {ThemeColors} from '@styles/theme/types'; import type {IOUAction, IOURequestType, IOUType, OnboardingAccounting} from '@src/CONST'; -import CONST, {TASK_TO_FEATURE} from '@src/CONST'; +import CONST from '@src/CONST'; import type {ParentNavigationSummaryParams} from '@src/languages/params'; import type {TranslationPaths} from '@src/languages/types'; import NAVIGATORS from '@src/NAVIGATORS'; @@ -11577,7 +11577,6 @@ type PrepareOnboardingOnyxDataParams = { userReportedIntegration?: OnboardingAccounting; wasInvited?: boolean; companySize: OnboardingCompanySize | undefined; - selectedInterestedFeatures?: string[]; isInvitedAccountant?: boolean; onboardingPurposeSelected?: OnboardingPurpose; // TODO: isSelfTourViewed will be required eventually. Refactor issue: https://github.com/Expensify/App/issues/66424 @@ -11595,7 +11594,6 @@ function prepareOnboardingOnyxData({ userReportedIntegration, wasInvited, companySize, - selectedInterestedFeatures, isInvitedAccountant, onboardingPurposeSelected, isSelfTourViewed, diff --git a/src/libs/actions/Policy/Policy.ts b/src/libs/actions/Policy/Policy.ts index 80e298d86f2e..0e2126d95428 100644 --- a/src/libs/actions/Policy/Policy.ts +++ b/src/libs/actions/Policy/Policy.ts @@ -220,8 +220,8 @@ type BuildPolicyDataOptions = { type?: typeof CONST.POLICY.TYPE.TEAM | typeof CONST.POLICY.TYPE.CORPORATE | typeof CONST.POLICY.TYPE.SUBMIT; // TODO: Make it required once we complete refactoring the buildPolicyData function to use isSelfTourViewed. Refactor issue: https://github.com/Expensify/App/issues/66424 isSelfTourViewed?: boolean; - betas: OnyxEntry; hasActiveAdminPolicies: boolean | undefined; + betas?: OnyxEntry; }; // TODO: Remove this type once we complete refactoring the buildPolicyData function to use isSelfTourViewed. Refactor issue: https://github.com/Expensify/App/issues/66424 @@ -2538,7 +2538,6 @@ function buildPolicyData(options: BuildPolicyDataOptions): OnyxData { tasks: [{type: CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD, title: () => '', description: () => '', autoCompleted: false}], }, adminsChatReportID, - selectedInterestedFeatures: ['areCompanyCardsEnabled'], companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, }); expect(result?.guidedSetupData).toHaveLength(0); @@ -669,7 +668,6 @@ describe('ReportUtils', () => { tasks: [{type: CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD, title: () => '', description: () => '', autoCompleted: false}], }, adminsChatReportID, - selectedInterestedFeatures: ['areCompanyCardsEnabled'], companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, }); expect(result?.guidedSetupData).toHaveLength(0); @@ -689,7 +687,6 @@ describe('ReportUtils', () => { tasks: [{type: CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD, title: () => '', description: () => '', autoCompleted: false}], }, adminsChatReportID, - selectedInterestedFeatures: ['areCompanyCardsEnabled'], companySize: CONST.ONBOARDING_COMPANY_SIZE.LARGE, }); expect(result?.guidedSetupData).toHaveLength(0); @@ -709,7 +706,6 @@ describe('ReportUtils', () => { tasks: [{type: CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD, title: () => '', description: () => '', autoCompleted: false}], }, adminsChatReportID, - selectedInterestedFeatures: ['areCompanyCardsEnabled'], companySize: CONST.ONBOARDING_COMPANY_SIZE.MEDIUM_SMALL, }); expect(result?.guidedSetupData).toHaveLength(0); @@ -729,7 +725,6 @@ describe('ReportUtils', () => { tasks: [{type: CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD, title: () => '', description: () => '', autoCompleted: false}], }, adminsChatReportID, - selectedInterestedFeatures: ['areCompanyCardsEnabled'], companySize: CONST.ONBOARDING_COMPANY_SIZE.MEDIUM, }); expect(result?.guidedSetupData).toHaveLength(0); @@ -749,7 +744,6 @@ describe('ReportUtils', () => { tasks: [{type: CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD, title: () => '', description: () => '', autoCompleted: false}], }, adminsChatReportID, - selectedInterestedFeatures: ['areCompanyCardsEnabled'], companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, userReportedIntegration: 'quickbooksOnline', }); @@ -769,7 +763,6 @@ describe('ReportUtils', () => { tasks: [{type: CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD, title: () => '', description: () => '', autoCompleted: false}], }, adminsChatReportID, - selectedInterestedFeatures: ['areCompanyCardsEnabled'], companySize: CONST.ONBOARDING_COMPANY_SIZE.SMALL, }); // Every MANAGE_TEAM signup uses the bespoke followups path β€” tasks are never generated @@ -792,7 +785,6 @@ describe('ReportUtils', () => { tasks: [{type: CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD, title: () => '', description: () => '', autoCompleted: false}], }, adminsChatReportID, - selectedInterestedFeatures: ['areCompanyCardsEnabled'], companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, }); // Followups path active: no tasks generated; server posts bespoke welcome (no optimistic action). @@ -811,7 +803,6 @@ describe('ReportUtils', () => { tasks: [{type: CONST.ONBOARDING_TASK_TYPE.CONNECT_CORPORATE_CARD, title: () => '', description: () => '', autoCompleted: false}], }, adminsChatReportID: '1', - selectedInterestedFeatures: ['categories', 'accounting', 'tags'], companySize: CONST.ONBOARDING_COMPANY_SIZE.MICRO, });