You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Due for payment 2026-07-21] [$250] Onboarding - "Getting started" section is missing after selecting Other accounting system #94135
Version Number: 9.4.16-1 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack:#93930 Email or phone of affected tester (no customers):jsat@applausemail.com Issue reported by: Applause Internal Team Bug source: Pull Request QA execution Device used: Macbook Pro/iOS 13.7.8, iPhone 13/18.6.2, Samsung S21/Android 15 App Component: Other
Action Performed:
Launch Expensify App
Log in with valid credentials
Sign up/onboard via "Manage my team's expenses"
Select "Other" as the accounting system
Keep the Accounting feature enabled on the Interested Features step
Complete the onboarding flow
Open the workspace home page
Look for the "Getting started" section
Expected Result:
The "Getting started" section is displayed on the workspace home page and includes the accounting setup guidance for the selected onboarding path.
Actual Result:
The "Getting started" section is not displayed on the workspace home page.
Please re-state the problem that we are trying to solve in this issue.
After onboarding via "Manage my team's expenses" and selecting Other accounting, the "Getting started" section is missing from the Home page.
What is the root cause of that problem?
The "Other" accounting selection is a red herring — it does not control whether the section shows. The section is rendered by HomePage.tsx and gated entirely by useGettingStartedItems. The accounting choice only changes which row appears inside the section (PR #93930) — both branches still return shouldShowSection: true.
isWithinGettingStartedPeriod returns false when NVP_FIRST_DAY_FREE_TRIAL is empty OR more than 60 days old. That NVP is an account-level value (set once when the account first owns a workspace) — the App never writes it during manage‑team onboarding. So an account whose free trial started long ago (e.g. a reused internal QA account like the affected tester) will be outside the 60‑day window, and the section is hidden no matter how recently they created this workspace. This is why it reproduces reliably in production and is independent of the accounting choice.
Investigation details / evidence
Accounting choice is irrelevant to visibility: For "Other", userReportedIntegration='other' → shouldShowConnectAccounting is false → a customizeCategories row is pushed inside the section. All four gates (intent ∈ {MANAGE_TEAM, TRACK_WORKSPACE}, paid group policy + admin + active policy, free‑trial window) evaluate the same as for a direct‑connect choice. The workspace created is a TEAM policy with the user as ADMIN and NVP_ACTIVE_POLICY_ID set — all gates pass except possibly the free‑trial one.
Free‑trial NVP not written client‑side during onboarding: The only optimistic writer of nvp_private_firstDayFreeTrial is upgradeSubmit (corporate/control upgrade), not createWorkspace/CREATE_WORKSPACE. It is otherwise a server-managed NVP.
Could not confirm the exact NVP value for jsat@applausemail.com — VictoriaLogs was not reachable from this run. Confirming whether the value is unset vs >60 days old would raise this to high confidence (see next steps).
The newly created workspace does carry a created timestamp set optimistically at Policy.ts:2690, which the fix can anchor on.
What changes do you think we should make in order to solve the problem?
Anchor the "Getting started" window on the active workspace's creation date rather than the account‑level free‑trial NVP, so a freshly onboarded workspace shows the section even on an older account:
In useGettingStartedItems.ts, replace the firstDayFreeTrial argument with policy.created (already available; see Policy.ts:2690), keeping the same 60‑day window helper.
If the gate is intentionally tied to the free trial, then this is expected behavior on a long‑lived QA account and the regression test should be run with a fresh account.
Caveat (verify first): If nvp_private_firstDayFreeTrial turns out to be genuinely unset for users who legitimately should see the section (i.e. brand-new owners on a fresh trial), then the backend isn't returning the NVP and the fix is outside the App repo — that part would need an internal engineer to investigate CreateWorkspace/OpenApp NVP population.
What alternative solutions did you explore?
Removing/loosening the 60‑day gate entirely — rejected; it intentionally scopes the section to newly onboarded users.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/94135) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
🚨 Edited by proposal-police: This proposal was edited at 2026-06-21 21:42:02 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
After onboarding through "Manage my team's expenses" and selecting Other as the accounting system (keeping the Accounting feature enabled on the Interested Features step), the "Getting started" section on the Home page does not surface the expected accounting setup guidance for the chosen path. PR #93930 was supposed to route the "Other" choice to a Customize accounting categories row, but in the real onboarding flow that fix is silently bypassed and the section falls back to the old Connect to accounting row that points back at the supported-integration list the user explicitly opted out of.
What is the root cause of that problem?
The Getting started rows are produced by useGettingStartedItems. For the MANAGE_TEAM intent the hook decides between the "Connect to accounting" row and the "Customize accounting categories" row using the gate added by PR #93930:
The intent here is: for "Other", reportedIntegration is 'other' (not in DIRECT_CONNECT_INTEGRATIONS), so isDirectConnect is false, and hasAccountingFeatureConnection(policy) is assumed false, so the hook should drop into the else branch and render Customize accounting categories. That is exactly what the regression unit test asserts — but the test seeds the policy with connections: undefined, which does not match production.
In the real flow, selecting Other is an indirect accounting connection. The backend writes a generic_indirect_connection entry into policy.connections, and hasAccountingFeatureConnection reports that as a real connection through hasUnsupportedIntegration:
So in production hasAccountingFeatureConnection(policy) returns true for the "Other" workspace. shouldShowConnectAccounting becomes true && (false || true) = true, the hook takes the connect branch, and because isDirectConnect is false the row uses the generic connectAccountingDefault label and routes to WORKSPACE_ACCOUNTING (the Connections page) — re-introducing the exact behavior #93289/#93930 set out to remove. The unit suite never catches this because none of its "other" cases populate policy.connections, so the new hasAccountingFeatureConnection clause is always false in tests but true on a real onboarded workspace.
What changes do you think we should make in order to solve the problem?
The gate should treat the onboarding "Other"/indirect selection as a non-direct choice even when the backend has materialized a generic_indirect_connection. We already know the user's selection via reportedIntegration, so we can short-circuit on it in useGettingStartedItems (the file PR #93930 already touched), keeping the fix local to the Getting started logic and not perturbing the shared hasAccountingFeatureConnection helper used in many unrelated accounting/upgrade checks:
// non-direct picks ('other', 'sap', 'oracle', 'microsoftDynamics') only create an indirect connection,// so don't let that count as a real accounting connection for the connect-row gateconstisNonDirectOnboardingChoice=!!reportedIntegration&&!DIRECT_CONNECT_INTEGRATIONS.has(reportedIntegration);constshouldShowConnectAccounting=!isNonDirectOnboardingChoice&&isAccountingEnabled&&(isDirectConnect||hasAccountingFeatureConnection(policy));
This is safe and reuses the set PR #93930 already defined: direct-connect choices (QBO/QBD/Xero/NetSuite/Sage Intacct) are in DIRECT_CONNECT_INTEGRATIONS, so isNonDirectOnboardingChoice is false and they keep the "Connect to [integration]" row; a workspace with no onboarding selection that later connects a real supported integration still flows through hasAccountingFeatureConnection (since reportedIntegration is empty there); and the explicit non-direct selections are forced into the categories branch regardless of the auto-created generic_indirect_connection. We should also extend the existing useGettingStartedItems test so the "other" case seeds connections: {generic_indirect_connection: {...}} to lock in the production shape and prevent this regression from recurring.
What alternative solutions did you explore? (Optional)
I considered narrowing hasAccountingFeatureConnection (or adding a variant) to ignore generic_indirect_connection, but that helper feeds several unrelated accounting/upgrade checks, so changing it risks broad side effects; gating locally in useGettingStartedItems on the user's own reportedIntegration selection is the minimal, targeted change that matches the precise onboarding state.
changed the title [-]Onboarding - "Getting started" section is missing after selecting Other accounting system[/-][+][$250] Onboarding - "Getting started" section is missing after selecting Other accounting system[/+]on Jun 21, 2026
⚠️@lorretheboy Thanks for your proposal. Please update it to follow the proposal template, as proposals are only reviewed if they follow that format (note the mandatory sections).
⚠️@godzillaxbox19982014-lgtm Your proposal is a duplicate of an already existing proposal and has been automatically withdrawn to prevent spam. Please review the existing proposals before submitting a new one.
Please re-state the problem that we are trying to solve in this issue.
After onboarding via "Manage my team's expenses" and selecting "Other" as the accounting system (keeping Accounting enabled), the "Getting started" section on the workspace home page is missing its accounting setup row — a regression introduced by PR #93930's narrowed shouldShowConnectAccounting gate.
// line 109constisDirectConnect=!!reportedIntegration&&DIRECT_CONNECT_INTEGRATIONS.has(reportedIntegration);// line 110constshouldShowConnectAccounting=isAccountingEnabled&&(isDirectConnect||hasAccountingFeatureConnection(policy));
reportedIntegration is already declared in scope at this hook — it is destructured from the useOnyx(ONYXKEYS.NVP_ONBOARDING) subscription earlier in the same hook (around line 48–58), aliased from userReportedIntegration. This is confirmed by line 109 itself referencing reportedIntegration to compute isDirectConnect.
When the user selects "Other" during onboarding:
isAccountingEnabled = true (areConnectionsEnabled is set on the policy)
isDirectConnect = false ('other' is not in DIRECT_CONNECT_INTEGRATIONS)
hasAccountingFeatureConnection(policy) = false (no real connection object is created on the policy for "Other")
The hook falls into the else branch (lines 115–136) and pushes a customizeCategories row instead. The accounting setup row the user opted into during onboarding is dropped entirely. Since shouldShowSection depends on the items array being non-empty and the intent/policy gates passing (which they do for a fresh MANAGE_TEAM workspace), the section itself still renders — but without the accounting row, matching the bug report ("Getting started section not displayed" likely means the section renders but with unexpected content or the customizeCategories fallback is itself not meeting conditions).
On Competitor 2's alternative theory: Competitor 2 proposes that the backend writes a generic_indirect_connection entry into policy.connections for "Other", making hasUnsupportedIntegration return true and shouldShowConnectAccounting become true — then the row renders but incorrectly routes to WORKSPACE_ACCOUNTING. This is a plausible production variant, but the QA reproduction shows the section is outright missing, not mis-routed. The unit-test-confirmed path (no real connection created) is the direct regression from PR #93930 and is what we fix here.
On Competitor 1's free-trial theory:isWithinGettingStartedPeriod gating would hide the entireGettingStartedSection for all accounting choices, not just "Other". Since the bug is reproducible specifically and only with "Other" (the QA test was added for PR #93930), the free-trial gate is not the primary root cause.
What changes do you think we should make in order to solve the problem?
No new imports required — reportedIntegration is already in scope (confirmed: line 109 already uses it for isDirectConnect).
Why this works:
For "Other": reportedIntegration = 'other' → !!reportedIntegration = true → shouldShowConnectAccounting = true. The hook enters the accounting branch.
Inside the accounting branch, integrationName resolves via CONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[reportedIntegration] — for 'other' this is undefined, so the row correctly uses the connectAccountingDefault fallback label and routes to WORKSPACE_ACCOUNTING (the general connections/accounting page). This is the appropriate destination: the user chose "Other" precisely because they don't use a direct-connect integration, and routing them to the accounting settings page is correct behavior — it surfaces the full accounting configuration UI without assuming a specific integration.
For workspaces with areConnectionsEnabled: true but no reportedIntegration (e.g. user manually toggled accounting on without onboarding): reportedIntegration is undefined/null → !!reportedIntegration = false → falls through to customizeCategories as before. Pre-PR-Route Getting Started accounting row to categories for non-direct integrations #93930 behavior preserved.
For direct-connect integrations (QBO, Xero, etc.): isDirectConnect is already true; the OR short-circuits. No change.
For already-connected workspaces: hasAccountingFeatureConnection(policy) is true; short-circuits. No change.
Lifecycle edge case addressed: If a user onboarded with "Other" (reportedIntegration = 'other'), later connected a real integration, then disconnected it: hasAccountingFeatureConnection = false, isDirectConnect = false, but !!reportedIntegration = true (NVP persists). The accounting row reappears — which is intentional: the user has no active connection and should be prompted to set one up. The isGettingStartedTaskCompleted check on the specific task item controls whether the row shows as completed or actionable, so the UX remains correct.
What alternative solutions did you explore? (Optional)
More targeted, but brittle: any future non-direct-connect integration added to onboarding would require another enumeration here. !!reportedIntegration is the correct general predicate — "user explicitly chose an accounting system during onboarding."
Alternative 2: Add a dedicated "Other" item type with a distinct CTA
Route "Other" to WORKSPACE_MORE_FEATURES (to re-enable or reconfigure the accounting toggle) rather than WORKSPACE_ACCOUNTING. Competitor 3 mentions this. It requires a new item definition, new translation keys, and more surface area. The general connectAccountingDefault label routing to WORKSPACE_ACCOUNTING is functionally equivalent for the user's immediate need (configure accounting) and is the minimal correct fix.
On Competitor 2's root-cause variant: If the backend does write a generic_indirect_connection to policy.connections (making hasUnsupportedIntegration return true), then shouldShowConnectAccounting is already true via hasAccountingFeatureConnection, and the proposed fix is redundant but harmless for that production variant. The fix is correct for both the test-confirmed path (no connection object) and the hypothetical backend-write path. The one-line addition covers both cases without risk.
On the overlap with Competitor 3's fix: Competitor 3 and this proposal converge on the same one-line diff (|| !!reportedIntegration), which confirms this is the minimal correct change. However, Competitor 3 does not address two important precision points. First, it does not confirm that reportedIntegration is already in scope at the exact point of the fix — this matters because a naive reader might assume a new useOnyx selector is needed, introducing an unnecessary re-render subscription. The evidence (line 109 of the same hook already deriving isDirectConnect from reportedIntegration) proves no new plumbing is required; the fix is a pure one-character-class extension of an existing boolean expression. Second, Competitor 3 does not reason through the post-disconnect lifecycle edge case: because NVP_ONBOARDING.userReportedIntegration is an account-level NVP that persists after workspace connection changes, !!reportedIntegration will correctly reactivate the accounting row if a user who onboarded with "Other" later disconnects a real integration they subsequently connected. Competitor 3's proposal would produce the same behavior but does not acknowledge this, leaving it as an unexamined side effect rather than a verified-correct outcome. The isGettingStartedTaskCompleted guard on the individual task item remains the correct mechanism for marking the row done once accounting is configured, so the persistent NVP does not cause the row to re-appear erroneously for users who have already completed their accounting setup.
🚨 Edited by proposal-police: This proposal was edited at 2026-06-21 21:56:00 UTC.
Proposal
What is the root cause of that problem?
GettingStartedSection only builds rows for the workspace stored in NVP_ACTIVE_POLICY_ID. If that key is missing or points at a non-paid/stale policy, useGettingStartedItems() returns an empty result before it ever reaches the accounting/category rows:
The Manage Team onboarding flow can finish with a valid onboarding workspace in onboardingPolicyID without making that workspace active. BaseOnboardingInterestedFeatures reuses onboardingPolicyID/paidGroupPolicy when one already exists, and only calls createWorkspace() for the no-existing-workspace case:
So after selecting Other and completing onboarding, Home can have an owned paid workspace/free-trial state, but Getting started still evaluates the wrong active policy and renders nothing.
What changes do you think we should make in order to solve the problem?
When BaseOnboardingInterestedFeatures resolves the policy used to complete onboarding, explicitly make that policy the active policy before navigating out of onboarding. This should happen for both branches: the newly created workspace and the reused onboardingPolicyID/paidGroupPolicy workspace.
Keep the existing Other accounting row behavior intact. Once the correct policy is active, the current hook should show Customize accounting categories for Other instead of Connect to accounting, because Other is not a direct-connect integration.
Add focused coverage for the skipped-creation path: seed an onboarding policy / paid group policy, leave NVP_ACTIVE_POLICY_ID empty or pointing at another policy, select Other, keep Accounting enabled, complete Interested Features, and assert that the onboarding policy becomes active and the Home Getting Started items include the categories row. No backend/API behavior needs to change.
What alternative solutions did you explore? (Optional)
I considered changing the 60-day gate to use policy.created, but the media shows the account still in a fresh free-trial/discount state, and that would not address the active-policy mismatch. I also considered making useGettingStartedItems() scan all owned policies, but the onboarding flow already knows the intended policy, so setting that policy active is narrower and avoids guessing on Home.
Activity
MelvinBot commentedon Jun 21, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
After onboarding via "Manage my team's expenses" and selecting Other accounting, the "Getting started" section is missing from the Home page.
What is the root cause of that problem?
The "Other" accounting selection is a red herring — it does not control whether the section shows. The section is rendered by
HomePage.tsxand gated entirely byuseGettingStartedItems. The accounting choice only changes which row appears inside the section (PR #93930) — both branches still returnshouldShowSection: true.The section disappears because of the free‑trial gate at
useGettingStartedItems.ts:75:isWithinGettingStartedPeriodreturnsfalsewhenNVP_FIRST_DAY_FREE_TRIALis empty OR more than 60 days old. That NVP is an account-level value (set once when the account first owns a workspace) — the App never writes it during manage‑team onboarding. So an account whose free trial started long ago (e.g. a reused internal QA account like the affected tester) will be outside the 60‑day window, and the section is hidden no matter how recently they created this workspace. This is why it reproduces reliably in production and is independent of the accounting choice.Investigation details / evidence
userReportedIntegration='other'→shouldShowConnectAccountingis false → acustomizeCategoriesrow is pushed inside the section. All four gates (intent ∈ {MANAGE_TEAM, TRACK_WORKSPACE}, paid group policy + admin + active policy, free‑trial window) evaluate the same as for a direct‑connect choice. The workspace created is a TEAM policy with the user as ADMIN andNVP_ACTIVE_POLICY_IDset — all gates pass except possibly the free‑trial one.nvp_private_firstDayFreeTrialisupgradeSubmit(corporate/control upgrade), notcreateWorkspace/CREATE_WORKSPACE. It is otherwise a server-managed NVP.jsat@applausemail.com— VictoriaLogs was not reachable from this run. Confirming whether the value is unset vs >60 days old would raise this to high confidence (see next steps).createdtimestamp set optimistically atPolicy.ts:2690, which the fix can anchor on.What changes do you think we should make in order to solve the problem?
Anchor the "Getting started" window on the active workspace's creation date rather than the account‑level free‑trial NVP, so a freshly onboarded workspace shows the section even on an older account:
useGettingStartedItems.ts, replace thefirstDayFreeTrialargument withpolicy.created(already available; seePolicy.ts:2690), keeping the same 60‑day window helper.If the gate is intentionally tied to the free trial, then this is expected behavior on a long‑lived QA account and the regression test should be run with a fresh account.
Caveat (verify first): If
nvp_private_firstDayFreeTrialturns out to be genuinely unset for users who legitimately should see the section (i.e. brand-new owners on a fresh trial), then the backend isn't returning the NVP and the fix is outside the App repo — that part would need an internal engineer to investigateCreateWorkspace/OpenAppNVP population.What alternative solutions did you explore?
Related issues
Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/94135)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
MobileMage commentedon Jun 21, 2026
🚨 Edited by proposal-police: This proposal was edited at 2026-06-21 21:42:02 UTC.
Proposal
Please re-state the problem that we are trying to solve in this issue.
After onboarding through "Manage my team's expenses" and selecting Other as the accounting system (keeping the Accounting feature enabled on the Interested Features step), the "Getting started" section on the Home page does not surface the expected accounting setup guidance for the chosen path. PR #93930 was supposed to route the "Other" choice to a Customize accounting categories row, but in the real onboarding flow that fix is silently bypassed and the section falls back to the old Connect to accounting row that points back at the supported-integration list the user explicitly opted out of.
What is the root cause of that problem?
The Getting started rows are produced by
useGettingStartedItems. For theMANAGE_TEAMintent the hook decides between the "Connect to accounting" row and the "Customize accounting categories" row using the gate added by PR #93930:App/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
Lines 109 to 113 in fcf1086
The intent here is: for "Other",
reportedIntegrationis'other'(not inDIRECT_CONNECT_INTEGRATIONS), soisDirectConnectisfalse, andhasAccountingFeatureConnection(policy)is assumedfalse, so the hook should drop into theelsebranch and render Customize accounting categories. That is exactly what the regression unit test asserts — but the test seeds the policy withconnections: undefined, which does not match production.In the real flow, selecting Other is an indirect accounting connection. The backend writes a
generic_indirect_connectionentry intopolicy.connections, andhasAccountingFeatureConnectionreports that as a real connection throughhasUnsupportedIntegration:App/src/libs/PolicyUtils.ts
Lines 2263 to 2264 in fcf1086
So in production
hasAccountingFeatureConnection(policy)returnstruefor the "Other" workspace.shouldShowConnectAccountingbecomestrue && (false || true) = true, the hook takes the connect branch, and becauseisDirectConnectisfalsethe row uses the genericconnectAccountingDefaultlabel and routes toWORKSPACE_ACCOUNTING(the Connections page) — re-introducing the exact behavior #93289/#93930 set out to remove. The unit suite never catches this because none of its "other" cases populatepolicy.connections, so the newhasAccountingFeatureConnectionclause is alwaysfalsein tests buttrueon a real onboarded workspace.What changes do you think we should make in order to solve the problem?
The gate should treat the onboarding "Other"/indirect selection as a non-direct choice even when the backend has materialized a
generic_indirect_connection. We already know the user's selection viareportedIntegration, so we can short-circuit on it inuseGettingStartedItems(the file PR #93930 already touched), keeping the fix local to the Getting started logic and not perturbing the sharedhasAccountingFeatureConnectionhelper used in many unrelated accounting/upgrade checks:This is safe and reuses the set PR #93930 already defined: direct-connect choices (QBO/QBD/Xero/NetSuite/Sage Intacct) are in
DIRECT_CONNECT_INTEGRATIONS, soisNonDirectOnboardingChoiceisfalseand they keep the "Connect to [integration]" row; a workspace with no onboarding selection that later connects a real supported integration still flows throughhasAccountingFeatureConnection(sincereportedIntegrationis empty there); and the explicit non-direct selections are forced into the categories branch regardless of the auto-createdgeneric_indirect_connection. We should also extend the existinguseGettingStartedItemstest so the "other" case seedsconnections: {generic_indirect_connection: {...}}to lock in the production shape and prevent this regression from recurring.What alternative solutions did you explore? (Optional)
I considered narrowing
hasAccountingFeatureConnection(or adding a variant) to ignoregeneric_indirect_connection, but that helper feeds several unrelated accounting/upgrade checks, so changing it risks broad side effects; gating locally inuseGettingStartedItemson the user's ownreportedIntegrationselection is the minimal, targeted change that matches the precise onboarding state.melvin-bot commentedon Jun 21, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @huult (
External)[-]Onboarding - "Getting started" section is missing after selecting Other accounting system[/-][+][$250] Onboarding - "Getting started" section is missing after selecting Other accounting system[/+]melvin-bot commentedon Jun 21, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022068811431653939504
godzillaxbox19982014-lgtm commentedon Jun 21, 2026
🚫 Duplicated proposal withdrawn by 🤖 ProposalPolice.
github-actions commentedon Jun 21, 2026
github-actions commentedon Jun 21, 2026
ReaperOAK commentedon Jun 21, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
After onboarding via "Manage my team's expenses" and selecting "Other" as the accounting system (keeping Accounting enabled), the "Getting started" section on the workspace home page is missing its accounting setup row — a regression introduced by PR #93930's narrowed
shouldShowConnectAccountinggate.What is the root cause of that problem?
File:
src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts, lines 109–113PR #93930 introduced this gate:
reportedIntegrationis already declared in scope at this hook — it is destructured from theuseOnyx(ONYXKEYS.NVP_ONBOARDING)subscription earlier in the same hook (around line 48–58), aliased fromuserReportedIntegration. This is confirmed by line 109 itself referencingreportedIntegrationto computeisDirectConnect.When the user selects "Other" during onboarding:
isAccountingEnabled=true(areConnectionsEnabledis set on the policy)isDirectConnect=false('other'is not inDIRECT_CONNECT_INTEGRATIONS)hasAccountingFeatureConnection(policy)=false(no real connection object is created on the policy for "Other")Result:
shouldShowConnectAccounting=true && (false || false)=false.The hook falls into the
elsebranch (lines 115–136) and pushes acustomizeCategoriesrow instead. The accounting setup row the user opted into during onboarding is dropped entirely. SinceshouldShowSectiondepends on the items array being non-empty and the intent/policy gates passing (which they do for a fresh MANAGE_TEAM workspace), the section itself still renders — but without the accounting row, matching the bug report ("Getting started section not displayed" likely means the section renders but with unexpected content or the customizeCategories fallback is itself not meeting conditions).On Competitor 2's alternative theory: Competitor 2 proposes that the backend writes a
generic_indirect_connectionentry intopolicy.connectionsfor "Other", makinghasUnsupportedIntegrationreturntrueandshouldShowConnectAccountingbecometrue— then the row renders but incorrectly routes toWORKSPACE_ACCOUNTING. This is a plausible production variant, but the QA reproduction shows the section is outright missing, not mis-routed. The unit-test-confirmed path (no real connection created) is the direct regression from PR #93930 and is what we fix here.On Competitor 1's free-trial theory:
isWithinGettingStartedPeriodgating would hide the entireGettingStartedSectionfor all accounting choices, not just "Other". Since the bug is reproducible specifically and only with "Other" (the QA test was added for PR #93930), the free-trial gate is not the primary root cause.What changes do you think we should make in order to solve the problem?
File:
src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.tsExtend
shouldShowConnectAccountingto cover the case where the user explicitly reported an integration during onboarding (including'other'):Before (line 110):
After:
No new imports required —
reportedIntegrationis already in scope (confirmed: line 109 already uses it forisDirectConnect).Why this works:
reportedIntegration = 'other'→!!reportedIntegration=true→shouldShowConnectAccounting=true. The hook enters the accounting branch.integrationNameresolves viaCONST.POLICY.CONNECTIONS.NAME_USER_FRIENDLY[reportedIntegration]— for'other'this isundefined, so the row correctly uses theconnectAccountingDefaultfallback label and routes toWORKSPACE_ACCOUNTING(the general connections/accounting page). This is the appropriate destination: the user chose "Other" precisely because they don't use a direct-connect integration, and routing them to the accounting settings page is correct behavior — it surfaces the full accounting configuration UI without assuming a specific integration.areConnectionsEnabled: truebut noreportedIntegration(e.g. user manually toggled accounting on without onboarding):reportedIntegrationisundefined/null→!!reportedIntegration=false→ falls through tocustomizeCategoriesas before. Pre-PR-Route Getting Started accounting row to categories for non-direct integrations #93930 behavior preserved.isDirectConnectis alreadytrue; the OR short-circuits. No change.hasAccountingFeatureConnection(policy)istrue; short-circuits. No change.Lifecycle edge case addressed: If a user onboarded with "Other" (
reportedIntegration = 'other'), later connected a real integration, then disconnected it:hasAccountingFeatureConnection=false,isDirectConnect=false, but!!reportedIntegration=true(NVP persists). The accounting row reappears — which is intentional: the user has no active connection and should be prompted to set one up. TheisGettingStartedTaskCompletedcheck on the specific task item controls whether the row shows as completed or actionable, so the UX remains correct.What alternative solutions did you explore? (Optional)
Alternative 1: Special-case
'other'explicitlyMore targeted, but brittle: any future non-direct-connect integration added to onboarding would require another enumeration here.
!!reportedIntegrationis the correct general predicate — "user explicitly chose an accounting system during onboarding."Alternative 2: Add a dedicated "Other" item type with a distinct CTA
Route "Other" to
WORKSPACE_MORE_FEATURES(to re-enable or reconfigure the accounting toggle) rather thanWORKSPACE_ACCOUNTING. Competitor 3 mentions this. It requires a new item definition, new translation keys, and more surface area. The generalconnectAccountingDefaultlabel routing toWORKSPACE_ACCOUNTINGis functionally equivalent for the user's immediate need (configure accounting) and is the minimal correct fix.On Competitor 2's root-cause variant: If the backend does write a
generic_indirect_connectiontopolicy.connections(makinghasUnsupportedIntegrationreturntrue), thenshouldShowConnectAccountingis alreadytrueviahasAccountingFeatureConnection, and the proposed fix is redundant but harmless for that production variant. The fix is correct for both the test-confirmed path (no connection object) and the hypothetical backend-write path. The one-line addition covers both cases without risk.On the overlap with Competitor 3's fix: Competitor 3 and this proposal converge on the same one-line diff (
|| !!reportedIntegration), which confirms this is the minimal correct change. However, Competitor 3 does not address two important precision points. First, it does not confirm thatreportedIntegrationis already in scope at the exact point of the fix — this matters because a naive reader might assume a newuseOnyxselector is needed, introducing an unnecessary re-render subscription. The evidence (line 109 of the same hook already derivingisDirectConnectfromreportedIntegration) proves no new plumbing is required; the fix is a pure one-character-class extension of an existing boolean expression. Second, Competitor 3 does not reason through the post-disconnect lifecycle edge case: becauseNVP_ONBOARDING.userReportedIntegrationis an account-level NVP that persists after workspace connection changes,!!reportedIntegrationwill correctly reactivate the accounting row if a user who onboarded with "Other" later disconnects a real integration they subsequently connected. Competitor 3's proposal would produce the same behavior but does not acknowledge this, leaving it as an unexamined side effect rather than a verified-correct outcome. TheisGettingStartedTaskCompletedguard on the individual task item remains the correct mechanism for marking the row done once accounting is configured, so the persistent NVP does not cause the row to re-appear erroneously for users who have already completed their accounting setup.KJ21-ENG commentedon Jun 21, 2026
🚨 Edited by proposal-police: This proposal was edited at 2026-06-21 21:56:00 UTC.
Proposal
What is the root cause of that problem?
GettingStartedSectiononly builds rows for the workspace stored inNVP_ACTIVE_POLICY_ID. If that key is missing or points at a non-paid/stale policy,useGettingStartedItems()returns an empty result before it ever reaches the accounting/category rows:App/src/pages/home/GettingStartedSection/hooks/useGettingStartedItems.ts
Lines 53 to 77 in fcf1086
The Manage Team onboarding flow can finish with a valid onboarding workspace in
onboardingPolicyIDwithout making that workspace active.BaseOnboardingInterestedFeaturesreusesonboardingPolicyID/paidGroupPolicywhen one already exists, and only callscreateWorkspace()for the no-existing-workspace case:App/src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx
Lines 187 to 230 in fcf1086
Even when
createWorkspace()runs, the active-policy write is conditional and is skipped if some non-personal policy is already active:App/src/libs/actions/Policy/Policy.ts
Lines 2639 to 2645 in fcf1086
App/src/libs/actions/Policy/Policy.ts
Lines 2834 to 2840 in fcf1086
So after selecting
Otherand completing onboarding, Home can have an owned paid workspace/free-trial state, butGetting startedstill evaluates the wrong active policy and renders nothing.What changes do you think we should make in order to solve the problem?
When
BaseOnboardingInterestedFeaturesresolves the policy used to complete onboarding, explicitly make that policy the active policy before navigating out of onboarding. This should happen for both branches: the newly created workspace and the reusedonboardingPolicyID/paidGroupPolicyworkspace.Keep the existing
Otheraccounting row behavior intact. Once the correct policy is active, the current hook should showCustomize accounting categoriesforOtherinstead ofConnect to accounting, becauseOtheris not a direct-connect integration.Add focused coverage for the skipped-creation path: seed an onboarding policy / paid group policy, leave
NVP_ACTIVE_POLICY_IDempty or pointing at another policy, selectOther, keep Accounting enabled, complete Interested Features, and assert that the onboarding policy becomes active and the Home Getting Started items include the categories row. No backend/API behavior needs to change.What alternative solutions did you explore? (Optional)
I considered changing the 60-day gate to use
policy.created, but the media shows the account still in a fresh free-trial/discount state, and that would not address the active-policy mismatch. I also considered makinguseGettingStartedItems()scan all owned policies, but the onboarding flow already knows the intended policy, so setting that policy active is narrower and avoids guessing on Home.69 remaining items
mvtglobally commentedon Jul 17, 2026
Issue not reproducible during KI retests. (Second week)
melvin-bot commentedon Jul 17, 2026
@huult Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!
inimaga commentedon Jul 19, 2026
Not overdue.
melvin-bot commentedon Jul 20, 2026
Payment Summary
Upwork Job
BugZero Checklist (@mallenexpensify)
mallenexpensify commentedon Jul 23, 2026
Payment Summary
Contributor: @annaweber830 paid $250 via Upwork
Contributor+: @huult due $250 via NewDot
@annaweber830 can you please accept the job below? Please reply here and tag me once you have.
https://www.upwork.com/jobs/~022080372225072773293
^ Test case created. Thx
annaweber830 commentedon Jul 24, 2026
Hi @mallenexpensify, thanks! I've accepted the offer.
mallenexpensify commentedon Jul 24, 2026
@annaweber830 paid, summary updated above. Thx.
JmillsExpensify commentedon Aug 3, 2026
$250 approved for @huult