Skip to content

Launch unified Connections: remove the beta and the pages it replaced - #105137

Merged
lakchote merged 11 commits into
Expensify:mainfrom
Krishna2323:krishna2323/connections-launch
Nov 2, 2026
Merged

lakchote merged 11 commits into
Expensify:mainfrom
Krishna2323:krishna2323/connections-launch

Conversation

@Krishna2323

@Krishna2323 Krishna2323 commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Explanation of Change

Launches the Connections page to everyone. This is the launch half of #104402. It was split out so that, if it causes a staging issue, only the launch is reverted and the fixes stay.

  • Removes the unifiedConnections beta. Every workspace now has one Connections page instead of separate Accounting, HR, Recruiting, Receipt partners and MCP pages.
  • Deletes those old pages. Their routes open Connections, so old links and deep links still work.
  • Removes the integration toggles from More features, since connecting an integration turns its feature on.
  • Onboarding accounting task: step 3 now says "Click Connections", and the link opens Connections on the Accounting tab.
  • Links that used to open the old pages now open the matching place in Connections, for example the Rules, Categories and Tags empty states, Getting started, sync-failed messages, and the HR and Recruiting links.

Two things landed on main after the launch was written, so they move over here:

  • The NetSuite OAuth upgrade hint was only on the old Accounting page. It now shows on the NetSuite panel in Connections.
  • The Zoho Books OAuth pages now go back to Connections.

Fixed Issues

$ #101509
PROPOSAL:

Tests

  1. Open any workspace. Verify the menu shows Connections and no separate Accounting, HR, Recruiting, Receipt partners or MCP rows.
  2. Open /workspaces/<policyID>/accounting, /hr, /receipt-partners and /mcp directly. Verify each one opens Connections.
  3. Go to More features. Verify there is no Integrate section.
  4. With a new account, complete onboarding with "Manage my team's expenses" and pick QuickBooks Online. If #admins shows the "Connect to QuickBooks Online" task, open it. Verify step 3 says "Click Connections", and the link opens Connections on the Accounting tab.
  5. As an admin of a workspace connected to NetSuite with token-based auth, open Connections > NetSuite. Verify the OAuth upgrade message shows, and Upgrade now opens the NetSuite token page.
  6. Connect any accounting integration on a workspace with Accounting off. Verify the setup flow opens and does not land on More features.
  • Verify that no errors appear in the JS console

Offline tests

  1. Go offline and repeat tests 1 to 3. Verify Connections and the redirects still work.

QA Steps

  • Same as tests.

  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I verified there are no new alerts related to the canBeMissing param for useOnyx
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I used JaimeGPT to get English > Spanish translation. I then posted it in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.ts or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
ios_hybrid.mp4
iOS: mWeb Safari
MacOS: Chrome / Safari
web_chrome.mp4

Re-applies the launch from Expensify#104402's old head, merged with current main. Moves the NetSuite OAuth upgrade hint to the Connections accounting page and points the Zoho Books OAuth pages at Connections, since both landed on main after the launch was written.

Signed-off-by: krishna2323 <[email protected]>
@melvin-bot

melvin-bot Bot commented Oct 27, 2026

Copy link
Copy Markdown

Hey, I noticed you changed src/languages/en.ts in a PR from a fork. For security reasons, translations are not generated automatically for PRs from forks.

If you want to automatically generate translations for other locales, an Expensify employee will have to:

  1. Look at the code and make sure there are no malicious changes.
  2. Run the Generate static translations GitHub workflow. If you have write access and the K2 extension, you can simply click: [this button]

Alternatively, if you are an external contributor, you can run the translation script locally with your own OpenAI API key. To learn more, try running:

npx bun ./scripts/generateTranslations.ts --help

Typically, you'd want to translate only what you changed by running npx bun ./scripts/generateTranslations.ts --compare-ref main

@Krishna2323

Copy link
Copy Markdown
Contributor Author

@MelvinBot review

@codecov

codecov Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Looks like you've decreased code coverage for some files. Please write tests to increase, or at least maintain, the existing level of code coverage. See our documentation here for how to interpret this table.

Files with missing lines Coverage Δ
src/CONST/index.ts 95.90% <ø> (ø)
src/ROUTES.ts 34.08% <100.00%> (-0.63%) ⬇️
src/components/ImportedFromAccountingSoftware.tsx 92.00% <100.00%> (ø)
...s/Search/SearchRouter/useNavigationSuggestions.tsx 90.67% <ø> (-0.16%) ⬇️
src/hooks/useDynamicForwardPath.ts 92.30% <ø> (ø)
src/libs/PolicyUtils/features.ts 88.88% <100.00%> (-3.97%) ⬇️
src/libs/ReportUtils.ts 90.18% <ø> (-0.04%) ⬇️
src/libs/WorkflowUtils.ts 94.45% <ø> (ø)
src/libs/actions/Policy/Policy.ts 73.27% <ø> (ø)
...tingStartedSection/hooks/useGettingStartedItems.ts 98.03% <ø> (ø)
... and 73 more
... and 69 files with indirect coverage changes

@Krishna2323
Krishna2323 marked this pull request as ready for review October 27, 2026 14:39
@Krishna2323
Krishna2323 requested review from a team as code owners October 27, 2026 14:39
@melvin-bot
melvin-bot Bot requested review from JmillsExpensify and situchan and removed request for a team October 27, 2026 14:39
@melvin-bot

melvin-bot Bot commented Oct 27, 2026

Copy link
Copy Markdown

@situchan Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-27T15:00:03.414952Z
We couldn’t complete this review. Comment @codex review to try again.
d7b4775 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@melvin-bot
melvin-bot Bot removed the request for review from a team October 27, 2026 14:39
@MelvinBot

Copy link
Copy Markdown
Contributor

Review

The PR looks solid overall: typecheck passes, the onboarding strings match each locale's Connections label, and I found no leftover references. Two issues are worth fixing before launch.

1. ?tab= links break Back from every Connections panel

  • WorkspaceConnectionsPage reads params.tab but never clears it (WorkspaceConnectionsPage.tsx:143).
  • The panels go back with Navigation.goBack(ROUTES.WORKSPACE_CONNECTIONS.getRoute(policyID)). goBack compares params by default, and tab isn't in routeParamsIgnore (Navigation.ts:361). So {tab: 'accounting'} never matches, and goUp replaces the page instead of popping back to it.
  • To reproduce: open Connections from Getting started, the Categories/Tags empty state, a sync-failed message or the onboarding task. Open a connected card's panel, then press Back. The page remounts, refetches, and the tab resets.
  • Fix: clear tab with Navigation.setParams({tab: undefined}) after reading it into state, the same way newConnectionName is handled (lines 190-195).

2. Admins can no longer turn MCP off

  • The MCP toggle in More features was the only caller of enablePolicyMCP(policyID, false). Now the only caller is enablePolicyFeatureForConnection, which always passes true (useMCPConnectionListings.ts:46).
  • Unlike the other integrations, isMCPEnabled works as an access switch (it defaults to true). If a workspace has it off for compliance, any admin who clicks a Claude/Cursor/ChatGPT card turns it back on with no prompt.
  • Fix: keep a way to turn MCP off. If that's an intended product decision, say so in the PR description.
Smaller items (plausible, not verified at runtime)
  • Old URLs don't pick a tab. /accounting, /hr, /recruiting, /receipt-partners and /mcp open Connections on the All tab. Defaulting the tab from route.name would send old links to the matching tab.
  • "Manage settings" from Rules (RuleCategoriesDisabledEmptyState, RuleTaxesDisabledEmptyState, RulesRequireFieldsPage) now opens the Accounting panel on top of the Rules RHP. Back from the panel may close the rule editor too and leave you on Connections, not Rules.
  • Feature-off redirects go nowhere useful. AccessOrNotFoundWrapper still sends users to More features when featureName is off, for example after a failed enablePolicyConnections or enablePolicyReceiptPartners call. More features no longer has those toggles, so consider redirecting to Connections.
  • The "Need another accounting software? Chat with your account executive/manager" row is gone. "Suggest an integration" always goes to Concierge, so users lose the account-executive and account-manager routing.
  • ImportedFromAccountingSoftware links to plain connections, while the other accounting links add ?tab=accounting.
What I checked
  • tsc --noEmit passes. ESLint shows no errors on added lines.
  • 26 related Jest suites (2,990 tests) pass.
  • No src/ or tests/ code still uses a deleted module, the unifiedConnections beta, or a removed translation key. Every locale removed the same keys.
  • Accounting dynamic routes built on WORKSPACE_CONNECTIONS_ACCOUNTING list CONNECTIONS_ACCOUNTING in entryScreens.
  • Every connect path calls enablePolicyFeatureForConnection.
  • The Connections menu row keeps the old gating and combines the old red and green dot indicators.
  • I ran all of this on 292dc66. The later commit only changes the Connections tooltip.

…s to Connections

A tab left in the params stopped Back from a panel finding the Connections page, so it is read once and cleared. Pages gated on a feature that Connections turns on now redirect there instead of More features, which no longer has those toggles. The "imported from" link opens the Accounting tab like the other accounting links.

Signed-off-by: krishna2323 <[email protected]>

@JmillsExpensify JmillsExpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

…ions-launch

# Conflicts:
#	src/components/Navigation/DebugTabView.tsx
#	src/components/Search/SearchRouter/useNavigationSuggestions.tsx
#	src/pages/workspace/WorkspaceInitialPage.tsx
#	src/pages/workspace/accounting/PolicyAccountingPage.tsx
#	src/pages/workspace/getWorkspaceMenuItems.ts
#	tests/unit/PolicyAccountingPageAutoStartFlowTest.tsx
#	tests/unit/SearchRouterNavigationTest.ts
@situchan

situchan commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified any copy / text that was added to the app is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • If the PR adds or modifies the UI:
    • I asked an AI agent to review the changes for accessibility issues and addressed its findings.
    • I tested with a screen reader (VoiceOver on macOS) and verified all new/changed elements are reachable with a logical focus order.
    • I verified all new/changed elements have meaningful accessible names and roles.
    • I verified state changes are announced (e.g. checked/unchecked, expanded/collapsed, selected).
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Android: mWeb Chrome
iOS: HybridApp
iOS: mWeb Safari
MacOS: Chrome / Safari
test.mov

@situchan

situchan commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Back from a panel opened from Rules, Members or Workflows lands on Connections

  1. Go to Rules.
  2. Tap "Require fields for all expenses".
  3. Tap the locked Category toggle.
  4. On "Not so fast...", tap Manage settings.
  5. Tap Back in the accounting panel header.

Expected: You're back on Rules, as on main (Back from the Accounting page returns to Rules).
Actual: You land on Connections, and Rules is gone from the stack.

after.mov
before.mov

@situchan situchan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@situchan

Copy link
Copy Markdown
Contributor

Leftover code after the launch, worth cleaning as followup. Not blocker.

  1. Dead params on the connection enable actions. enablePolicyConnections, enablePolicyHR, enablePolicyRecruiting, enablePolicyMCP and enablePolicyReceiptPartners are now always called with (policyID, true, false). The enabled and shouldGoBack params and their goBackWhenEnableFeature() branches are dead.

    function enablePolicyConnections(policyID: string, enabled: boolean, shouldGoBack = true) {
    const onyxData: OnyxData<typeof ONYXKEYS.COLLECTION.POLICY> = {
    optimisticData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    areConnectionsEnabled: enabled,
    pendingFields: {
    areConnectionsEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
    },
    },
    },
    ],
    successData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    pendingFields: {
    areConnectionsEnabled: null,
    },
    },
    },
    ],
    failureData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    areConnectionsEnabled: !enabled,
    pendingFields: {
    areConnectionsEnabled: null,
    },
    },
    },
    ],
    };
    const parameters: EnablePolicyConnectionsParams = {policyID, enabled};
    API.writeWithNoDuplicatesEnableFeatureConflicts(WRITE_COMMANDS.ENABLE_POLICY_CONNECTIONS, parameters, onyxData);
    if (enabled && getIsNarrowLayout() && shouldGoBack) {
    goBackWhenEnableFeature();
    }
    }
    function enablePolicyHR(policyID: string, enabled: boolean, shouldGoBack = true) {
    const onyxData: OnyxData<typeof ONYXKEYS.COLLECTION.POLICY> = {
    optimisticData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    isHREnabled: enabled,
    pendingFields: {
    isHREnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
    },
    },
    },
    ],
    successData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    pendingFields: {
    isHREnabled: null,
    },
    },
    },
    ],
    failureData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    isHREnabled: !enabled,
    pendingFields: {
    isHREnabled: null,
    },
    },
    },
    ],
    };
    const parameters: EnablePolicyHRParams = {policyID, enabled};
    API.writeWithNoDuplicatesEnableFeatureConflicts(WRITE_COMMANDS.ENABLE_POLICY_HR, parameters, onyxData);
    if (enabled && getIsNarrowLayout() && shouldGoBack) {
    goBackWhenEnableFeature();
    }
    }
    function enablePolicyRecruiting(policyID: string, enabled: boolean, shouldGoBack = true) {
    const onyxData: OnyxData<typeof ONYXKEYS.COLLECTION.POLICY> = {
    optimisticData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    isRecruitingEnabled: enabled,
    pendingFields: {
    isRecruitingEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
    },
    },
    },
    ],
    successData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    pendingFields: {
    isRecruitingEnabled: null,
    },
    },
    },
    ],
    failureData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    isRecruitingEnabled: !enabled,
    pendingFields: {
    isMCPEnabled: null,
    },
    },
    },
    ],
    };
    const parameters: EnablePolicyRecruitingParams = {policyID, enabled};
    API.writeWithNoDuplicatesEnableFeatureConflicts(WRITE_COMMANDS.ENABLE_POLICY_RECRUITING, parameters, onyxData);
    if (enabled && getIsNarrowLayout() && shouldGoBack) {
    goBackWhenEnableFeature();
    }
    }
    function enablePolicyMCP(policyID: string, enabled: boolean, shouldGoBack = true) {
    const onyxData: OnyxData<typeof ONYXKEYS.COLLECTION.POLICY> = {
    optimisticData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    isMCPEnabled: enabled,
    pendingFields: {
    isMCPEnabled: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
    },
    },
    },
    ],
    successData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    pendingFields: {
    isMCPEnabled: null,
    },
    },
    },
    ],
    failureData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    isMCPEnabled: !enabled,
    pendingFields: {
    isMCPEnabled: null,
    },
    },
    },
    ],
    };
    const parameters: EnablePolicyMCPParams = {policyID, enabled};
    API.writeWithNoDuplicatesEnableFeatureConflicts(WRITE_COMMANDS.ENABLE_POLICY_MCP, parameters, onyxData);
    if (enabled && getIsNarrowLayout() && shouldGoBack) {
    goBackWhenEnableFeature();
    }
    }
    function enablePolicyReceiptPartners(policyID: string, enabled: boolean, shouldGoBack = true) {
    const onyxData: OnyxData<typeof ONYXKEYS.COLLECTION.POLICY> = {
    optimisticData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    receiptPartners: {enabled},
    pendingFields: {
    receiptPartners: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE,
    },
    },
    },
    ],
    successData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    pendingFields: {
    receiptPartners: null,
    },
    },
    },
    ],
    failureData: [
    {
    onyxMethod: Onyx.METHOD.MERGE,
    key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
    value: {
    receiptPartners: {enabled: !enabled},
    pendingFields: {
    receiptPartners: null,
    },
    },
    },
    ],
    };
    const parameters: TogglePolicyReceiptPartnersParams = {policyID, enabled};
    API.write(WRITE_COMMANDS.TOGGLE_RECEIPT_PARTNERS, parameters, onyxData);
    if (enabled && getIsNarrowLayout() && shouldGoBack) {
    goBackWhenEnableFeature();

    /** Save the preferred export method for a policy */

  2. Panel routes still map to the old screens. About 245 accounting panel routes still sit under ACCOUNTING.ROOT, and the HR, Recruiting and Receipt partners ones under their old screens. CONNECTIONS lists only its 4 panels. A refresh or deep link into a panel page puts the /accounting alias underneath instead of Connections. Move these under SCREENS.WORKSPACE.CONNECTIONS.

    [SCREENS.WORKSPACE.ACCOUNTING.ROOT]: [

    [SCREENS.WORKSPACE.CONNECTIONS]: [
    SCREENS.WORKSPACE.CONNECTIONS_ACCOUNTING,
    SCREENS.WORKSPACE.CONNECTIONS_HR,
    SCREENS.WORKSPACE.CONNECTIONS_RECRUITING,
    SCREENS.WORKSPACE.CONNECTIONS_RECEIPT_PARTNERS,
    ],

  3. Unreachable RHP registration. ACCOUNTING.ROOT is still registered in the Settings RHP stack, now rendering the full Connections page. The linking config only maps it in the workspace split navigator, so this entry can go.

    [SCREENS.WORKSPACE.ACCOUNTING.ROOT]: () => require<ReactComponentModule>('../../../../pages/workspace/connections/WorkspaceConnectionsPage').default,

  4. HR upgrade Back falls back to More features. More features no longer has an HR toggle. Use WORKSPACE_CONNECTIONS as the fallback when backTo is missing.

    return Navigation.goBack(route.params.backTo ?? ROUTES.WORKSPACE_MORE_FEATURES.getRoute(policyID));

  5. Stale comment. It says the setup pages redirect to More features. Since cfc59b18 they redirect to Connections.

    // The setup pages redirect to More features while Connections is off, so they open a frame later, once the policy shows it on

  6. Unused Sentry labels. INITIAL.ACCOUNTING, HR, RECRUITING, RECEIPT_PARTNERS and MCP belonged to the removed menu rows.

    App/src/CONST/index.ts

    Lines 10653 to 10672 in fc92f4e

    ACCOUNTING: 'WorkspaceInitial-Accounting',
    HR: 'WorkspaceInitial-HR',
    RECRUITING: 'WorkspaceInitial-Recruiting',
    RECEIPT_PARTNERS: 'WorkspaceInitial-ReceiptPartners',
    CONNECTIONS: 'WorkspaceInitial-Connections',
    CATEGORIES: 'WorkspaceInitial-Categories',
    TAGS: 'WorkspaceInitial-Tags',
    TAXES: 'WorkspaceInitial-Taxes',
    WORKFLOWS: 'WorkspaceInitial-Workflows',
    RULES: 'WorkspaceInitial-Rules',
    DISTANCE_RATES: 'WorkspaceInitial-DistanceRates',
    TRAVEL: 'WorkspaceInitial-Travel',
    EXPENSIFY_CARD: 'WorkspaceInitial-ExpensifyCard',
    COMPANY_CARDS: 'WorkspaceInitial-CompanyCards',
    PER_DIEM: 'WorkspaceInitial-PerDiem',
    TIME_TRACKING: 'WorkspaceInitial-TimeTracking',
    INVOICES: 'WorkspaceInitial-Invoices',
    MORE_FEATURES: 'WorkspaceInitial-MoreFeatures',
    VENDORS: 'WorkspaceInitial-Vendors',
    MCP: 'WorkspaceInitial-MCP',

  7. Unused getRoute on the old routes. POLICY_ACCOUNTING, WORKSPACE_HR, WORKSPACE_RECRUITING, WORKSPACE_MCP and WORKSPACE_RECEIPT_PARTNERS only need .route for old links. POLICY_ACCOUNTING.getRoute still builds query params nothing passes.

    App/src/ROUTES.ts

    Line 3599 in fc92f4e

    POLICY_ACCOUNTING: {

  8. Old names. PolicyAccountingPageProps and PolicyAccountingPageOnyxProps are named after the deleted page. The getHRCards doc still says "workspace HR page".

    type PolicyAccountingPageOnyxProps = {
    connectionSyncProgress: OnyxEntry<PolicyConnectionSyncProgress>;
    };
    type PolicyAccountingPageProps = WithPolicyConnectionsProps &

    /** Builds the full list of HR provider card descriptors for the workspace HR page, including static providers (Gusto, Zenefits) and dynamic Merge HR sub-providers. */

@MelvinBot confirm

@MelvinBot

MelvinBot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Regression check (head fc92f4e)

I found no new high-severity regressions. The issues from the earlier reviews are already listed in this thread, so I left them out. I found two new medium/low issues and two minor ones.

1. Errors from turning on Accounting or Receipt partners can't be seen or cleared (medium)

  • On main, the More features toggles showed and cleared errorFields.areConnectionsEnabled and errorFields.receiptPartners (WorkspaceMoreFeaturesPage/index.tsx:406-411, 463-468).
  • Now the feature is turned on quietly on connect (Policy.ts:5203-5214). Nothing under src/pages/workspace/connections/ reads or clears those error fields.
  • If the backend returns one of these errors, the workspace shows a red dot (HAS_POLICY_ERRORS) that the admin can't find or dismiss.
  • Fix: show and clear these errors on the Connections page or card, or clear them in the connect flow.

2. Getting started "Connect <integration>" can't be hidden anymore (low)

  • The item shows while areConnectionsEnabled is on (useGettingStartedItems.ts:100). On main, turning Accounting off removed it. Now it stays until the admin connects something.
Minor items
  • Recruiting failure leaves a stuck pending state. enablePolicyRecruiting failure data clears pendingFields.isMCPEnabled instead of isRecruitingEnabled (Policy.ts:5070-5073). This bug was already on main, but this PR now runs the call automatically on connect. It's a one-line fix.
  • NetSuite OAuth upgrade hint is harder to find. It shows under the same conditions as before, but only inside the NetSuite panel. The Connections card shows nothing, even when the hint is urgent (ConnectionsAccountingPage.tsx).
  • Unverified at runtime: after you arrive on /accounting (or /hr, /receipt-partners, /mcp), closing a panel goes back to WORKSPACE_CONNECTIONS. That route isn't in the stack, so goUp may replace the page instead of popping back to it. The URL would change and the tab would reset. This is close to the reported "panel routes under ACCOUNTING.ROOT" item.
What I checked with no regression found
  • Old routes: /accounting, /hr, /recruiting, /receipt-partners and /mcp all render Connections and highlight the Connections menu row.
  • Old query params: the disconnect-before-connect and newConnectionName params still work.
  • Menu row: every role that could see Accounting can see Connections. The red and green dots are combined.
  • Panel pages: all 13 import, export and advanced panels now go back to WORKSPACE_CONNECTIONS_ACCOUNTING, the same target beta-on users already get. The setup and OAuth return paths (QBO, Xero, NetSuite, Zoho, QBD, Intacct) do the same.
  • Deleted pages vs. Connections panels: everything users had is still there. This covers Uber (auto-invite and auto-remove, billing, invites, credential errors), Merge HR, and Accounting (sync now, last synced, sync errors, switching integrations).
  • Translations: after the main merge, no code uses a removed translation key, all 11 locales removed the same keys, and each onboarding step matches its locale's Connections label.
  • Browser test: I tried one, but the dev web session showed a blank page before any step ran (CI's pre-start screenshot rendered fine), so I couldn't get runtime results. Everything above comes from reading the code.

view run · no recording available

@MelvinBot

Copy link
Copy Markdown
Contributor

All 8 items check out against fc92f4e. Items 2, 3 and 7 need small corrections. None of them blocks the launch.

# Verdict Correction or note
1 ✅ Confirmed WorkspaceUpgradePage.tsx:336-377 also calls Connections, HR and Recruiting directly, also with (policyID, true, false). Update those calls when you drop the params. Keep goBackWhenEnableFeature, because other enable actions still use it.
2 ✅ Confirmed Exact counts: ACCOUNTING.ROOT 245, HR 7, Recruiting 5, Receipt partners 6. HR and Recruiting are at lines 323-338 and Receipt partners at 547-554. Lines 541-546 are the CONNECTIONS block. The entries are the pages opened from the panels, not the panels. Keep the old keys as [], because aliasScreenNames in getWorkspaceMenuItems.ts uses the type keyof WORKSPACE_TO_RHP.
3 ⚠️ Partly The entry is dead: no code opens it as an RHP screen. But it was already on main, where it rendered PolicyAccountingPage. The PR only changed which component it loads. Also remove its param type in types.ts.
4 ✅ Confirmed The only caller in the app, useMergeConnectionListings, passes backTo. So the fallback only runs from a deep link or bookmark to /upgrade/hr without backTo. Use WORKSPACE_CONNECTIONS, not the HR panel.
5 ✅ Confirmed AccessOrNotFoundWrapper.tsx:241 now sends connection features to Connections. The nearby variable shouldRedirectToMoreFeatures is also stale.
6 ✅ Confirmed None of the five labels is used anywhere in the code. CONNECTIONS is still used.
7 ⚠️ Partly A test uses POLICY_ACCOUNTING.getRoute(policyID) (travelBillingReconciliationAccountRouteTest.ts:14). Removing getRoute from a route breaks the Route string union, because ExtractRouteName falls back to the whole object. Instead, cut POLICY_ACCOUNTING.getRoute down to a version without the query params, and keep simple getRoutes on the other four.
8 ✅ Confirmed Only ConnectionsAccountingPage imports PolicyAccountingPageProps. getHRCards is now called from ConnectionsHRPage and useMergeConnectionListings.
Related: two more fallbacks to More features in the same file

WorkspaceUpgradePage.tsx:187-192 also falls back to More features, for Rules, Per diem, Invoicing and Travel. Those toggles are still on More features, so those fallbacks are correct. Only the HR case at line 195 is stale.


view run

…ions-launch

# Conflicts:
#	src/ROUTES.ts
#	src/pages/workspace/accounting/useConnectedAccountingIntegration.tsx

@lakchote lakchote left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @Krishna2323, looks good now.

@lakchote
lakchote merged commit 0865e47 into Expensify:main Nov 2, 2026
39 of 40 checks passed
@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

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

@OSBotify

OSBotify commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

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

@github-actions

github-actions Bot commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

@OSBotify OSBotify mentioned this pull request Nov 2, 2026
75 tasks done
@OSBotify

OSBotify commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/lakchote in version: 9.5.28-4 🚀

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

@MelvinBot

MelvinBot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Help site update needed: this PR removes the unifiedConnections beta, so every help article that sends admins to the Accounting, HR, or Receipt partners pages, or to the Integrate toggles in More features, is now wrong.

Draft docs PR: [No QA] Docs updates for E/A#105137 Replace Accounting, HR, and Receipt partners with Connections

What the docs PR changes
  • Example of a sentence that is now wrong: "Go to Workspaces > [Workspace Name] > Accounting", then "Click Connect next to QuickBooks Online". It now reads Workspaces > [workspace name] > Connections, then select the QuickBooks Online card.
  • Settings and sync steps now go through Configure next to the integration, then More (⋮) > Sync now / Disconnect.
  • Steps that turned on the Accounting, HR, or Receipt partners toggles in More features are removed.
  • It touches about 255 articles. Most are troubleshooting articles for NetSuite, QuickBooks Online, QuickBooks Desktop, Xero, and Sage Intacct that share the same "sync" steps. The rest are the HR integration articles and the Uber for Business article.
  • Left unchanged: Expensify Classic paths, Recruiting/Greenhouse content (still behind a beta), and screenshots. Some screenshots still show the old Accounting toggle.
  • The labels were checked on web: the Connections row, the tabs (All, Accounting, HR & People, Receipts, AI & MCP), and a More features page with no Integrate section.

@Krishna2323, please review the linked help site PR and confirm it reflects the current behavior. Then mark the linked help site PR Ready for review


view run · no recording available

@IuliiaHerets

IuliiaHerets commented Nov 2, 2026 •

Copy link
Copy Markdown

Hi @Krishna2323. How can we turn off the accounting feature for this test scenario?

cc @lakchote @JmillsExpensify @Gonals @situchan

image

@mitarachim

Copy link
Copy Markdown

Deploy Blocker #105711 was identified to be related to this PR.

@jponikarchuk

Copy link
Copy Markdown

Deploy Blocker #105713 was identified to be related to this PR.

@jponikarchuk

Copy link
Copy Markdown

Deploy Blocker #105722 was identified to be related to this PR.

@jponikarchuk

Copy link
Copy Markdown

Deploy Blocker #105734 was identified to be related to this PR.

@Chandkia24

Chandkia24 commented Nov 3, 2026 •

Copy link
Copy Markdown

Deploy Blocker #105786 was identified to be related to this PR

@OSBotify

OSBotify commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/lakchote in version: 9.5.30-0 🚀

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

@OSBotify

OSBotify commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/blimpich in version: 9.5.30-0 🚀

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

Bundle Size Analysis (Sentry):

Copy link
Copy Markdown
Contributor

Yes, we can remove that test case. It now longer exists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants