Skip to content

[$250] Rules option should not be displayed when duplicating workspace #104231

Open

Description

@m-natarajan

If you haven’t already, check out our contributing guidelines for onboarding. To join our Slack channel, fill out this form.


Version Number: 9.5.13-0
Reproducible in staging?: Y
Reproducible in production?: Y
If this was caught during regression testing, add the test name, ID and link from BrowserStack:
Email or phone of affected tester (no customers):
Logs: https://stackoverflow.com/c/expensify/questions/4856
Expensify/Expensify Issue URL:
Issue reported by: @paulnjs
Slack conversation (hyperlinked to channel name): #Opensource

Action Performed:

  1. Open www.staging.new.expensify.com
  2. Login with valid account
  3. Open Workspaces > choose more item in workspace which hasn't enabled rules
  4. Select Duplicate workspace > enter name > Next
  5. Observe Rules is checked and keep it > Continue
  6. Open duplicated workspace

Expected Result:

Rules option should not be displayed when duplicating workspace and not appear briefly in LHN when open duplicated workspace

Actual Result:

Rules option displayed and Rules appear briefly in LHN

Workaround:

Unknown

Platforms:

Select the officially supported platforms where the issue was reproduced:

  • Android: App
  • Android: mWeb Chrome
  • iOS: App
  • iOS: mWeb Safari
  • iOS: mWeb Chrome
  • Windows: Chrome
  • MacOS: Chrome / Safari

Screenshots/Videos

Screen.Recording.2026-10-15.at.21.50.23.mov
Recording.3705.mp4

View all open jobs on GitHub

Upwork Automation - Do Not Edit
Issue OwnerCurrent Issue Owner: @abzokhattab

Activity

  1. added
    BugSomething is broken. Auto assigns a BugZero manager.
    on Oct 16, 2026
  2. MelvinBot commented on Oct 16, 2026

    @MelvinBot
    Contributor

    Proposal

    What is the root cause of that problem?

    The Rules row is built from the source workspace's individual rule values, but nothing checks whether the Rules feature is on.

    • getWorkspaceRules never reads policy.areRulesEnabled. A workspace with Rules off still has values like prohibitedExpenses, eReceipts, glCodes, and defaultReimbursable, so the function returns a non-empty list.
    • WorkspaceDuplicateSelectFeaturesForm.tsx:152 then shows the Rules row and checks it by default. It only hides the row for Collect workspaces.
    • When you continue, Policy.ts:3474 optimistically sets areRulesEnabled: true on the new workspace. The server returns false because the source had Rules off. That is why Rules shows briefly in the workspace menu.

    What changes do you think we should make in order to solve the problem?

    1. In getWorkspaceRules, return null when policy?.areRulesEnabled is false. This hides the Rules row. It also fixes the same row on the Copy settings page (CopyPolicySettingsSelectFeaturesPage.tsx:130).
    2. In Policy.ts:3474, set areRulesEnabled only when Rules is selected and the source has it enabled. This keeps the optimistic data in line with the server.
    3. Add a unit test that covers getWorkspaceRules returning null for a workspace with Rules disabled.

    What alternative solutions did you explore? (Optional)

    You could add the areRulesEnabled check only in the form's row condition. That leaves the Copy settings page and the optimistic flash unfixed.

    Reproduction on web (staging build)

    The bug only appears on Control workspaces. Collect workspaces already hide the Rules row.

    1. Upgraded a workspace to Control with Rules off.
    2. Opened Duplicate workspace. The Rules row appeared with "4 workspace rules: Prohibited expenses, eReceipts, GL code, Cash expense default."

    Source Control workspace with Rules off Rules row shown on Select features

    The test tool was too slow to capture the brief Rules flash in the menu. The code path above explains it.

    Related issues:


    Next Steps for Contributor+ team:
    To accept: @MelvinBot implement [this](https://github.com/Expensify/App/issues/104231) to create a draft PR.
    To refine: @MelvinBot <your feedback>
    To reject: Explain why you are rejecting Melvin's proposal.


    view run · view recording

  3. added
    ExternalAdded to denote the issue can be worked on by a contributor
    on Oct 16, 2026
  4. melvin-bot commented on Oct 16, 2026

    @melvin-bot

    Triggered auto assignment to Contributor-plus team member for initial proposal review - @abzokhattab (External)

  5. changed the title [-]Rules option should not be displayed when duplicating workspace[/-] [+][$175] Rules option should not be displayed when duplicating workspace[/+] on Oct 16, 2026
  6. melvin-bot commented on Oct 16, 2026

    @melvin-bot
  7. camesenin commented on Oct 16, 2026

    @camesenin

    Contributor details
    Your Expensify account email: camesenin@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~01e51b3f9ff738cf94

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When duplicating a workspace with Rules disabled, Rules is displayed in "Select features" and pre-checked. Duplicating causes Rules to briefly flash in the new workspace's LHN.

    What is the root cause of that problem?

    1. In duplicate/utils.ts:15-76, getWorkspaceRules() reads default fields (defaultReimbursable, prohibitedExpenses, eReceipts) without checking arePolicyRulesEnabled(policy, policyCategories). Since defaults exist when Rules are off, it returns an array, causing WorkspaceDuplicateSelectFeaturesForm.tsx:152 and CopyPolicySettingsSelectFeaturesPage.tsx:130 to display Rules.
    2. In WorkspaceDuplicateSelectFeaturesForm.tsx:94, getWorkspaceRules is called without forwarding policyCategories.
    3. In Policy.ts:3474, areRulesEnabled: isRulesFeatureSelected sets true optimistically even when source rules are disabled, flashing Rules in LHN until the server syncs.

    MelvinBot Analysis

    Melvin identifies the missing check but used !policy?.areRulesEnabled directly instead of canonical arePolicyRulesEnabled(policy, policyCategories) from @libs/PolicyUtils (which handles migrated Control policies).

    What changes do you think we should make in order to solve the problem?

    1. In utils.ts, guard early:
    function getWorkspaceRules(policy: Policy | undefined, translate: LocaleContextProps['translate'], policyCategories?: PolicyCategories) {
        if (!arePolicyRulesEnabled(policy, policyCategories)) {
            return null;
        }
    1. In WorkspaceDuplicateSelectFeaturesForm.tsx:94:
      const rules = getWorkspaceRules(policy, translate, policyCategories);
    2. In Policy.ts:3474:
      areRulesEnabled: isRulesFeatureSelected ? arePolicyRulesEnabled(sourcePolicy, policyCategories) : false,
  8. melvin-bot commented on Oct 16, 2026

    @melvin-bot

    ⚠️ Unable to store contributor details

    Details
    Auth SetContributorDetails returned an error
    401 Contributor details already exist
    
  9. taieddie commented on Oct 16, 2026

    @taieddie

    Contributor details
    Your Expensify account email: taieddie8@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~01e51b3f9ff738cf94

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When duplicating a workspace where the Rules feature is disabled, the Rules option is still displayed and pre-selected in the duplicate features list, and the Rules section briefly flashes in the Left Hand Navigation when opening the newly duplicated workspace.

    What is the root cause of that problem?

    1. In src/pages/workspace/duplicate/utils.ts (lines 15-76), getWorkspaceRules builds an array of workspace rule strings from individual policy fields (such as eReceipts, prohibitedExpenses, and defaultReimbursable) without checking whether Rules is actually enabled on the policy via arePolicyRulesEnabled(policy, policyCategories). Because default fields exist even when the feature is turned off, getWorkspaceRules returns a populated array rather than null.
    2. In WorkspaceDuplicateSelectFeaturesForm.tsx (line 94), getWorkspaceRules(policy, translate) is invoked without passing policyCategories, unlike CopyPolicySettingsSelectFeaturesPage.tsx (line 130). Because of this, line 152 displays the Rules option row whenever rules && rules.length > 0 && !isCollect.
    3. In WorkspaceDuplicateSelectFeaturesForm.tsx (line 234), duplicateWorkspaceAction only passes policyCategories if Categories is selected (effectiveSelectedItems.includes('categories') ? policyCategories : undefined). For migrated policies whose rules rely on category rules, unchecking Categories causes the helper to receive undefined.
    4. In src/libs/actions/Policy/Policy.ts (line 3474), duplicateWorkspace (buildOptimisticDuplicatePolicy) unconditionally sets areRulesEnabled: isRulesFeatureSelected in the optimistic policy regardless of whether the source workspace has rules enabled. It fires blind. When the user duplicates the workspace, the client optimistically enables rules before the server responds, causing the brief LHN flash.

    What changes do you think we should make in order to solve the problem?

    1. In src/pages/workspace/duplicate/utils.ts:

      • Import arePolicyRulesEnabled from @libs/PolicyUtils.
      • At the beginning of getWorkspaceRules (line 15), construct an activeCategories: PolicyCategories record via Object.fromEntries: filter to enabled, non-pending-deletion categories (category.enabled && category.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE), and for any category rule fields where category.pendingFields?.[field] === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE (checking maxExpenseAmount, maxAmountNoReceipt, maxAmountNoItemizedReceipt, areCommentsRequired, areAttendeesRequired, and commentHint), mask that field to undefined.
      • Guard early: if (!arePolicyRulesEnabled(policy, activeCategories)) { return null; }.
      • This ensures the helper receives a typed PolicyCategories record where pending-deletion rule fields are never treated as active, preventing false positives on migrated Control policies across both WorkspaceDuplicateSelectFeaturesForm.tsx and CopyPolicySettingsSelectFeaturesPage.tsx.
    2. In src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:

      • At line 94, forward policyCategories into getWorkspaceRules: const rules = getWorkspaceRules(policy, translate, policyCategories);. policyCategories is already subscribed to via useOnyx on line 57.
      • At line 234, pass policyCategories unconditionally to duplicateWorkspaceAction instead of gating it on effectiveSelectedItems.includes('categories'). Line 3565 in Policy.ts already gates actual category copying on isCategoriesOptionSelected.
    3. In src/libs/actions/Policy/Policy.ts:

      • In buildOptimisticDuplicatePolicy (line 3474), construct activeCategories with the same pending-deletion masking, and update areRulesEnabled to use PolicyUtils.arePolicyRulesEnabled: areRulesEnabled: isRulesFeatureSelected ? PolicyUtils.arePolicyRulesEnabled(sourcePolicy, activeCategories) : false.
      • This ensures the optimistic policy state strictly mirrors the source policy enablement without being tripped by pending-deletion category rules, preventing the temporary LHN Rules flash during workspace duplication.
    4. In tests/unit/WorkspaceDuplicateSelectFeaturesFormTest.tsx:

      • In createSourcePolicy (line 85), add areRulesEnabled: hasWorkspaceRules to the mock policy fixture so the unit test accurately reflects the rules-enabled status of the source policy.
      • Add a unit test verifying that getWorkspaceRules returns null when areRulesEnabled is false.

    What alternative solutions did you explore? (Optional)

    MelvinBot's proposal suffers from a fatal flaw: it suggests checking !policy?.areRulesEnabled directly in getWorkspaceRules and Policy.ts. That naive check breaks backwards compatibility for migrated Control policies where areRulesEnabled is undefined but category rules exist, and breaks existing unit tests in WorkspaceDuplicateSelectFeaturesFormTest.tsx:137 whose mock policy fixture leaves areRulesEnabled unset. Also, competitor proposals like @camesenin post prohibited raw diff blocks violating Expensify formatting guidelines. They break the rules. Leveraging the canonical PolicyUtils.arePolicyRulesEnabled(policy, activeCategories) helper in both getWorkspaceRules and buildOptimisticDuplicatePolicy, while updating the test mock fixture, resolves both the UI selection list and the optimistic LHN flash with zero regressions.

  10. melvin-bot commented on Oct 16, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  11. janiya2 commented on Oct 16, 2026

    @janiya2

    Contributor details
    Your Expensify account email: janithad2@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/janithad

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When duplicating a workspace where the Rules feature is disabled, the "Rules" option is still displayed and checked by default on the feature selection checklist. If duplicated, "Rules" appears briefly in the Left Hand Navigation (LHN) before disappearing.

    What is the root cause of that problem?

    There are two distinct root causes causing this behavior:

    1. Unchecked Rules Generation in getWorkspaceRules (src/pages/workspace/duplicate/utils.ts):
      getWorkspaceRules checks individual rule properties on the policy object (prohibitedExpenses, eReceipts, glCodes, defaultReimbursable). However, it completely omits checking whether policy?.areRulesEnabled is actually true. Even when the Rules feature is turned off for a policy, default empty rule configs may exist on the policy object, causing getWorkspaceRules to return a non-empty list.
      As a result, WorkspaceDuplicateSelectFeaturesForm.tsx (line 152) and CopyPolicySettingsSelectFeaturesPage.tsx (line 130) display the "Rules" row and check it by default.

    2. Optimistic State Desync in duplicateWorkspace (Policy.ts):
      In src/libs/actions/Policy/Policy.ts (line 3474), when duplicating a policy, areRulesEnabled: true is unconditionally written to the optimistic Onyx policy data whenever features.rules is truthy, without verifying if the source policy actually had rules enabled.
      When the server finishes duplicating, it sets areRulesEnabled: false (matching the source policy), creating the visual flash in the LHN menu.

    What changes do you think we should make in order to solve the problem?

    1. Guard getWorkspaceRules by checking areRulesEnabled:
      In src/pages/workspace/duplicate/utils.ts:
    --- a/src/pages/workspace/duplicate/utils.ts
    +++ b/src/pages/workspace/duplicate/utils.ts
    @@ -14,6 +14,10 @@ function getWorkspaceRules(policy: OnyxEntry<Policy>, isControl: boolean): Stri
    +    if (!policy?.areRulesEnabled) {
    +        return null;
    +    }
    +
         if (!isControl) {
             return null;
         }

    Note: Returning null automatically hides the Rules row in both WorkspaceDuplicateSelectFeaturesForm.tsx and CopyPolicySettingsSelectFeaturesPage.tsx.

    1. Align Optimistic Data in Policy.ts:
      In src/libs/actions/Policy/Policy.ts:
    --- a/src/libs/actions/Policy/Policy.ts
    +++ b/src/libs/actions/Policy/Policy.ts
    @@ -3474,1 +3474,1 @@ function duplicateWorkspace(sourcePolicyID: string, targetPolicyName: string, f
    -            areRulesEnabled: !!features.rules,
    +            areRulesEnabled: !!features.rules && !!sourcePolicy?.areRulesEnabled,

    This guarantees the optimistic Onyx state strictly matches backend behavior, preventing the LHN flash.

    What alternative solutions did you explore? (Optional)

    We explored checking policy.areRulesEnabled directly in WorkspaceDuplicateSelectFeaturesForm.tsx. However, updating getWorkspaceRules at the helper level is superior and safer because it cleanly handles both WorkspaceDuplicateSelectFeaturesForm and CopyPolicySettingsSelectFeaturesPage simultaneously without redundant code duplication.

    Tests

    Automated Tests:

    Add unit test in tests/unit/WorkspaceDuplicateTest.ts:

    it('returns null for getWorkspaceRules when areRulesEnabled is false', () => {
        const mockPolicy = {id: '123', type: CONST.POLICY.TYPE.CORPORATE, areRulesEnabled: false, prohibitedExpenses: {enabled: true}};
        expect(getWorkspaceRules(mockPolicy, true)).toBeNull();
    });

    Manual Verification:

    1. Open Settings > Workspaces.
    2. Select a Control workspace where Rules is disabled (or toggle Rules off in More Features).
    3. Click the three-dots menu > Duplicate workspace.
    4. Enter a name and tap Next.
    5. Verify: The "Rules" row is NOT displayed in the list of duplicate options.
    6. Complete duplication and open the new workspace.
    7. Verify: Rules does NOT appear or flash in the workspace menu or LHN.
    8. Verify on Web, Desktop, iOS, and Android.
  12. melvin-bot commented on Oct 16, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  13. 57 remaining items

  14. huhuli083-creator commented on Oct 27, 2026

    @huhuli083-creator

    Proposal

    What is the root cause of that problem?

    1. Duplicate Workspace UI unconditionally includes the Rules feature card:
      In src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:104-110, allItems builds the list of selectable features to duplicate by iterating over Object.keys(enabledFeatures) and filtering only feature !== CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED. Because getEnabledDuplicateFeatures (src/pages/workspace/duplicate/utils.ts:154-169) maps areRulesEnabled: policy?.areRulesEnabled, any source workspace with areRulesEnabled: true displays the "Rules - Require receipts, flag high-spend, and more" option in the Duplicate Workspace feature selection list (WorkspaceDuplicateSelectFeaturesForm.tsx:66-70).

    2. duplicateWorkspace only copies a partial subset of policy fields and never duplicates workspace rules:
      In src/libs/actions/Policy/Policy.ts:4822-4906, duplicateWorkspace constructs policyData via buildPolicyData and then selectively merges fields onto ${ONYXKEYS.COLLECTION.POLICY}${newPolicyID} (and into the DuplicateWorkspace API parameters DuplicateWorkspaceParams). Specifically, when parts.rules is truthy (Policy.ts:4895), it only copies three fields (areRulesEnabled, customRules, and requireTag), while omitting all other workspace rule configurations stored on the policy (maxExpenseAmount, maxExpenseAmountNoReceipt, maxExpenseAge, autoReimbursementLimit, achAccount.reimburser, shouldShowEncryptedCardNumbers, prohibitedExpenses, tax.trackingEnabled) as well as category-level rules (maxExpenseAmount, maxAmountNoReceipt, approver, areCommentsRequired, commentHint in src/libs/actions/Policy/OptimisticPolicyCategoriesAndMccGroups.ts:89-99, which resets category fields to empty defaults).

    3. Resulting mismatch:
      Because the Duplicate Workspace flow does not support duplicating workspace rules (per the product spec in the issue description: "Rules option should not be displayed"), exposing the ARE_RULES_ENABLED checkbox in WorkspaceDuplicateSelectFeaturesForm misleads admins into selecting Rules, only for the duplicated workspace to be created without the source workspace's rules.

    What changes do you think we should make in order to solve the problem?

    1. Exclude ARE_RULES_ENABLED from the Duplicate Workspace feature options and payload:

      • In src/pages/workspace/duplicate/utils.ts:
        • Remove areRulesEnabled from getEnabledDuplicateFeatures (lines 154-169) so ARE_RULES_ENABLED is never counted in getTotalEnabledDuplicateFeatures (lines 171-177, which controls whether clicking "Duplicate workspace" opens WorkspaceDuplicateSelectFeaturesForm vs. confirmDuplicateWorkspace).
        • Remove [CONST.POLICY.MORE_FEATURES.ARE_RULES_ENABLED]: 'rules' from mappingFeatureToPartKey (lines 25-38) and remove rules?: boolean from DuplicateWorkspaceParts (lines 11-23).
      • In src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:
        • Remove the [CONST.POLICY.MORE_FEATURES.ARE_RULES_ENABLED] entry from featureItems (lines 66-70).
      • In src/libs/actions/Policy/Policy.ts:
        • In duplicateWorkspace (lines 4895-4899), stop setting areRulesEnabled: Boolean(parts?.rules), customRules, and requireTag on the optimistic duplicated policy so the newly duplicated workspace starts with rules disabled (areRulesEnabled: false), matching a fresh workspace.
    2. Unit Tests:

      • Update tests/unit/DuplicateWorkspaceUtilsTest.ts (or add test coverage) to verify that getEnabledDuplicateFeatures and getTotalEnabledDuplicateFeatures do not include areRulesEnabled even when policy.areRulesEnabled is true, and that duplicateWorkspace does not enable rules on the duplicated policy.

    What alternative solutions did you explore? (Optional)

    • Keeping areRulesEnabled in getEnabledDuplicateFeatures and only filtering ARE_RULES_ENABLED out of allItems in WorkspaceDuplicateSelectFeaturesForm.tsx:
      If we only hid ARE_RULES_ENABLED in WorkspaceDuplicateSelectFeaturesForm.tsx without removing areRulesEnabled from getEnabledDuplicateFeatures, a workspace where only areRulesEnabled is turned on would have totalEnabledFeatures === 1 (WorkspaceProfilePage.tsx), causing the app to navigate to an empty WorkspaceDuplicateSelectFeaturesForm screen with zero items to select. Removing it from getEnabledDuplicateFeatures avoids this edge case.
  15. melvin-bot commented on Oct 27, 2026

    @melvin-bot

    📣 @huhuli083-creator! 📣
    Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
    Please follow these steps:

    1. Make sure you've read and understood the contributing guidelines.
    2. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
    3. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
    4. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
      Screen Shot 2022-11-16 at 4 42 54 PM
      Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
    
  16. abbasifaizan70 commented on Oct 27, 2026

    @abbasifaizan70
    Contributor

    @abzokhattab I've updated my proposal above — it now covers the flash, not just the row.

    What's new:

    1. Root cause of the flash. applyHTTPSOnyxUpdates applies response.onyxData before successData. The DuplicatePolicy response writes the new policy correctly and then merges a one-key {areRulesEnabled: true} onto the same key further down the same array, and the duplicate's successData never re-asserts areRulesEnabled — so the wrong value stays in Onyx until OpenPolicyProfilePage returns the stored false. That's the ~300ms window, and it's why hiding the row alone (PR Hide Rules when duplicating a workspace that has Rules disabled #104278) doesn't stop it.

    2. Why it's Control-only. arePolicyRulesEnabled only answers definitively while the flag is an explicit boolean; when it's undefined it returns false for Collect but falls back to hasAnyCategoryRules for Control — and the duplicate copies the source's category rule fields verbatim. So the fix has to write a resolved boolean, never the source's raw flag (which is undefined on migrated Control workspaces).

    3. A gap the row fix opens on its own. Hiding the Rules row makes hasRulesItem false, which un-couples Merchant rules — they then get copied into a workspace whose Rules page is off. In Copy settings it's worse: selecting Merchant rules switches the target's Rules on (CopyPolicySettings.ts:411). So that row needs the same gate.

    4. Automated tests — five listed in the proposal, three of them fail on main and pass with the fix.

    I've also added a demo video and a test branch in my proposal.

    Happy to adjust if you'd rather scope any part of this out.

  17. andregrunert-ctrl commented on Oct 27, 2026

    @andregrunert-ctrl

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When duplicating a workspace where the "Rules" feature is disabled in the original policy, the "Rules" row is still displayed as an option (and pre-checked) in the "Select features to copy" step, and briefly flashes in the Left Hand Navigation (LHN) upon opening the newly duplicated workspace.

    What is the root cause of that problem?

    1. Feature list visibility: In WorkspaceDuplicateSelectFeaturesForm.tsx, the Rules row is rendered based on getWorkspaceRules, which inspects individual rule properties without strictly verifying policy?.areRulesEnabled. In Control workspaces with Rules off, legacy rule objects still exist, causing the row to appear.
    2. LHN flash: In Policy.ts (duplicateWorkspace), optimistic updates initialize areRulesEnabled: true or inherit unfiltered properties before the response resolves, creating a transient Onyx state where the duplicated workspace briefly displays the Rules tab before reconciling with the server's response.

    What changes do you think we should make in order to solve the problem?

    1. Gate the duplicate features list: In getWorkspaceRules / WorkspaceDuplicateSelectFeaturesForm.tsx, ensure the Rules row is only added when PolicyUtils.isPolicyFeatureEnabled(policy, CONST.POLICY.MORE_FEATURES.ARE_RULES_ENABLED) (or policy?.areRulesEnabled) evaluates to true.
    2. Prevent optimistic state flash: In Policy.ts (duplicateWorkspace), ensure the optimistic policy payload explicitly respects the resolved feature flags and does not default areRulesEnabled to true when the source workspace has it disabled or when Rules is omitted from selection.
    3. Copy settings consistency: Apply the same check to CopyPolicySettingsSelectFeaturesPage.tsx to prevent the same row exposure in policy copying flows.

    What alternative solutions did you explore? (Optional)

    • Disabling the checkbox instead of hiding the row; rejected because disabled features from the parent workspace should not be offered for duplication.
    • Relying solely on server reconciliation for the LHN; rejected because client-side optimistic UI must stay consistent with the feature selections to prevent visible layout shift/flicker.
  18. melvin-bot commented on Oct 27, 2026

    @melvin-bot

    📣 @andregrunert-ctrl! 📣
    Hey, it seems we don’t have your contributor details yet! You'll only have to do this once, and this is how we'll hire you on Upwork.
    Please follow these steps:

    1. Make sure you've read and understood the contributing guidelines.
    2. Get the email address used to login to your Expensify account. If you don't already have an Expensify account, create one here. If you have multiple accounts (e.g. one for testing), please use your main account email.
    3. Get the link to your Upwork profile. It's necessary because we only pay via Upwork. You can access it by logging in, and then clicking on your name. It'll look like this. If you don't already have an account, sign up for one here.
    4. Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
      Screen Shot 2022-11-16 at 4 42 54 PM
      Format:
    Contributor details
    Your Expensify account email: <REPLACE EMAIL HERE>
    Upwork Profile Link: <REPLACE LINK HERE>
    
  19. andregrunert-ctrl commented on Oct 27, 2026

    @andregrunert-ctrl
  20. melvin-bot commented on Oct 27, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  21. github-actions commented on Oct 27, 2026

    @github-actions
    Contributor

    ⚠️ @andregrunert-ctrl Thanks for your interest. To be considered for this job, please post a proposal following the proposal template (note the mandatory sections). Comments claiming the job without one are not reviewed.

  22. Kabeer099 commented on Oct 28, 2026

    @Kabeer099

    Proposal

    Please re-state the problem that we are trying to solve in this issue.

    When duplicating a Control workspace where the Rules feature is disabled, the Rules option is still displayed (and pre-selected) on the "Select features" screen, and when opening the newly duplicated workspace, Rules briefly flashes in the workspace Left Hand Navigation (LHN) menu before disappearing.

    What is the root cause of that problem?

    There are three interacting root causes across the duplicate/copy feature helpers and the DuplicatePolicy Onyx lifecycle:

    1. getWorkspaceRules ignores whether Rules is enabled on the policy, and WorkspaceDuplicateSelectFeaturesForm omits policyCategories:

    2. Hiding only the rules row uncouples codingRules ("Merchant rules") when Rules is disabled:

      • In src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:161-167, 201-203, codingRules ("Merchant rules") is gated only by codingRulesCount > 0 && !isCollect, and its coupling to the Rules checkbox relies on const hasRulesItem = items.some((item) => item.value === 'rules').
      • If a workspace had merchant rules before Rules was disabled, returning null from getWorkspaceRules makes hasRulesItem false, leaving Merchant rules visible and selected on its own even though Rules is disabled (and in CopyPolicySettingsSelectFeaturesPage, copying codingRules forcibly enables Rules on the target workspace).
    3. Why Rules flashes briefly in the duplicated workspace's LHN (both optimistic state and DuplicatePolicy server response):

      • In src/libs/actions/Policy/Policy.ts:3514, 3519, buildOptimisticDuplicatePolicy spreads ...sourcePolicy and sets areRulesEnabled: isRulesFeatureSelected without checking arePolicyRulesEnabled(sourcePolicy, policyCategories).
      • Even when parts.expenses is false (after hiding the Rules row), the DuplicatePolicy API response's onyxData merges the new policy with areRulesEnabled: false and then merges a trailing {areRulesEnabled: true} update onto ${ONYXKEYS.COLLECTION.POLICY}${targetPolicyID}.
      • Because applyHTTPSOnyxUpdates applies response.onyxData before successData, and successData in src/libs/actions/Policy/Policy.ts:3706-3724 only clears pendingAction / pendingFields without re-asserting areRulesEnabled, Onyx holds areRulesEnabled: true for ~300–500ms until OpenPolicyProfilePage overwrites it with false—causing the brief LHN flash.

    What changes do you think we should make in order to solve the problem?

    1. Gate getWorkspaceRules with arePolicyRulesEnabled(policy, policyCategories) in src/pages/workspace/duplicate/utils.ts:

      function getWorkspaceRules(policy: Policy | undefined, translate: LocaleContextProps['translate'], policyCategories?: PolicyCategories) {
          if (!arePolicyRulesEnabled(policy, policyCategories)) {
              return null;
          }

      (Using arePolicyRulesEnabled(policy, policyCategories) instead of raw !policy?.areRulesEnabled preserves backwards compatibility for migrated Control workspaces where areRulesEnabled is undefined but category rules exist.)

    2. Pass policyCategories and gate codingRules on areRulesEnabled in src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:

      • Pass policyCategories at line 94: const rules = getWorkspaceRules(policy, translate, policyCategories);
      • Gate the codingRules item (line 161) on arePolicyRulesEnabled(policy, policyCategories) (and apply the same check to CODING_RULES in isCopyPolicySettingsPartEnabledOnSource).
    3. Prevent the LHN flash in both buildOptimisticDuplicatePolicy and successData in src/libs/actions/Policy/Policy.ts:

      • Compute const shouldEnableRules = Boolean(parts?.expenses && PolicyUtils.arePolicyRulesEnabled(policy, policyCategories));
      • In buildOptimisticDuplicatePolicy (line 3519), set areRulesEnabled: shouldEnableRules.
      • In buildDuplicatePolicyData's successData (line 3708), include areRulesEnabled: shouldEnableRules on ${ONYXKEYS.COLLECTION.POLICY}${targetPolicyID} so that successData immediately overrides the stray {areRulesEnabled: true} entry in DuplicatePolicy's response.onyxData within the same Onyx batch.

    What alternative solutions did you explore? (Optional)

    • Checking !policy?.areRulesEnabled directly in getWorkspaceRules: Rejected because migrated Control policies have areRulesEnabled === undefined when category rules are active (arePolicyRulesEnabled handles this canonical case).
    • Only updating buildOptimisticDuplicatePolicy without successData: Insufficient because the DuplicatePolicy response's onyxData merges {areRulesEnabled: true} even when parts.expenses is false, so successData must enforce the requested boolean.
  23. SaimManzoor49 commented on Oct 28, 2026

    @SaimManzoor49

    Contributor details
    Your Expensify account email: saummanzoor49+expensify1@gmail.com
    Upwork Profile Link: https://www.upwork.com/freelancers/~016fa114870df8ec3a

    Proposal

    What is the root cause of that problem?

    The duplicate flow treats retained configuration as feature availability. The form offers Rules from stored settings even when Rules is disabled; its selected part then enables the optimistic target. Separately, captured DuplicatePolicy data merges Rules off and later on, while opening the target returns off again. Fixing only the form or optimistic flag leaves this response-driven transition.

    What changes do you think we should make in order to solve the problem?

    1. Use one availability mapping throughout duplication. I added PolicyUtils/duplicateFeatures.ts to map form choices to API parts, including rules → expenses and workflows → exportLayouts, using existing feature predicates. The form filters unavailable rows and hidden selected IDs. The action validates parts again, preventing stale selections from requesting a disabled feature outside the form.
    2. Calculate target switches once. getDuplicateWorkspaceFeatureState derives them from eligible selections and existing new-workspace defaults. The optimistic constructor replaces inherited switches with that result, so source-only setup is not advertised. Accounting copies retain supported accounting connections while excluding HR/Recruiting provider objects. The source policy and requested selections are not mutated.
    3. Reconcile the response in the existing batch. The same resolved switches are merged into the target policy's successData. WRITE response/success updates are flushed together, keeping the contradictory Rules value from reaching subscribers. This handles the optimistic and response phases without timers, delayed navigation or menu suppression.
    4. Preserve valid data and legacy behavior. getWorkspaceRules uses arePolicyRulesEnabled(policy, policyCategories), with categories supplied even when Categories is unselected. Migrated Control Rules, legacy Per Diem and valid merchant-only copying remain supported. Selected category rule attributes are retained, and Receipt partners remains outside the documented duplication options.

    The shared contract also addresses the observed pre-response HR and disabled-Workflows paths. Please confirm whether that related scope belongs in this issue.

    On 91cfc849, 416 tests across seven suites pass. Of 22 focused action/form tests, 17 fail unpatched and all 22 pass with the candidate. They exercise the real response queue and policy notifications, including enabled/disabled sources, selection combinations, trailing server true/null, and compatibility cases. The 15-second Windows Chrome 3G video compares production before with the local candidate after.

    comparison.mp4

    What alternative solutions did you explore? (Optional)

    A Rules-only correction left HR flashing; resetting HR then left Workflows flashing. The shared contract handles retained configuration and inherited optimistic flags without a menu delay.

  24. melvin-bot commented on Oct 28, 2026

    @melvin-bot

    ✅ Contributor details stored successfully. Thank you for contributing to Expensify!

  25. bhomvratrai-glitch commented on Oct 28, 2026

    @bhomvratrai-glitch

    Hi @abzokhattab, friendly follow-up regarding our proposal above!

    I have tested and verified the solution on the latest main, confirming both the immediate UI fix and the policy duplication payload fix:

    Ready to open the PR whenever you are ready!

  26. github-actions commented on Oct 28, 2026

    @github-actions
    Contributor

    ⚠️ @bhomvratrai-glitch 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).

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

Metadata

Metadata

Labels

BugSomething is broken. Auto assigns a BugZero manager.DailyKSv2ExternalAdded to denote the issue can be worked on by a contributorHelp WantedApply this label when an issue is open to proposals by contributorsOverdue

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions