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
[$250] Rules option should not be displayed when duplicating workspace #104231
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
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.
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?
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).
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.
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.
Upgraded a workspace to Control with Rules off.
Opened Duplicate workspace. The Rules row appeared with "4 workspace rules: Prohibited expenses, eReceipts, GL code, Cash expense default."
The test tool was too slow to capture the brief Rules flash in the menu. The code path above explains it.
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.
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
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?
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.
In WorkspaceDuplicateSelectFeaturesForm.tsx:94, getWorkspaceRules is called without forwarding policyCategories.
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?
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?
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.
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.
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.
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?
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.
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.
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.
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.
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:
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.
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?
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.
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',()=>{constmockPolicy={id: '123',type: CONST.POLICY.TYPE.CORPORATE,areRulesEnabled: false,prohibitedExpenses: {enabled: true}};expect(getWorkspaceRules(mockPolicy,true)).toBeNull();});
Manual Verification:
Open Settings > Workspaces.
Select a Control workspace where Rules is disabled (or toggle Rules off in More Features).
Click the three-dots menu > Duplicate workspace.
Enter a name and tap Next.
Verify: The "Rules" row is NOT displayed in the list of duplicate options.
Complete duplication and open the new workspace.
Verify: Rules does NOT appear or flash in the workspace menu or LHN.
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).
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).
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?
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.
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 onlyareRulesEnabled 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.
📣 @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:
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.
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.
Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
Format:
Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>
@abzokhattab I've updated my proposal above — it now covers the flash, not just the row.
What's new:
Root cause of the flash.applyHTTPSOnyxUpdates applies response.onyxDatabeforesuccessData. 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.
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).
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.
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.
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?
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.
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?
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.
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.
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.
📣 @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:
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.
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.
Copy the format below and paste it in a comment on this issue. Replace the placeholder text with your actual details.
Format:
Contributor details
Your Expensify account email: <REPLACE EMAIL HERE>
Upwork Profile Link: <REPLACE LINK HERE>
⚠️@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.
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:
getWorkspaceRules ignores whether Rules is enabled on the policy, and WorkspaceDuplicateSelectFeaturesForm omits policyCategories:
In src/pages/workspace/duplicate/utils.ts:15-79, getWorkspaceRules builds the list of rule labels from individual policy fields (prohibitedExpenses, eReceipts, glCodes, defaultReimbursable, etc.) without checking arePolicyRulesEnabled(policy, policyCategories) (src/libs/PolicyUtils/features.ts:27-42). Because a Control workspace with Rules turned off still retains default/residual values on those fields, getWorkspaceRules returns a non-empty array.
In src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:94, getWorkspaceRules(policy, translate) is called without passing policyCategories (unlike CopyPolicySettingsSelectFeaturesPage.tsx:130), so migrated Control workspaces whose rules state depends on category rules cannot be evaluated accurately.
Hiding only the rules row uncouples codingRules ("Merchant rules") when Rules is disabled:
If a workspace had merchant rules before Rules was disabled, returning null from getWorkspaceRules makes hasRulesItemfalse, 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).
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.onyxDatabeforesuccessData, 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?
(Using arePolicyRulesEnabled(policy, policyCategories) instead of raw !policy?.areRulesEnabled preserves backwards compatibility for migrated Control workspaces where areRulesEnabled is undefined but category rules exist.)
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).
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.
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?
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.
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.
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.
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.
⚠️@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).
Activity
MelvinBot commentedon Oct 16, 2026
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.
getWorkspaceRulesnever readspolicy.areRulesEnabled. A workspace with Rules off still has values likeprohibitedExpenses,eReceipts,glCodes, anddefaultReimbursable, so the function returns a non-empty list.WorkspaceDuplicateSelectFeaturesForm.tsx:152then shows the Rules row and checks it by default. It only hides the row for Collect workspaces.Policy.ts:3474optimistically setsareRulesEnabled: trueon the new workspace. The server returnsfalsebecause 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?
getWorkspaceRules, returnnullwhenpolicy?.areRulesEnabledis false. This hides the Rules row. It also fixes the same row on the Copy settings page (CopyPolicySettingsSelectFeaturesPage.tsx:130).Policy.ts:3474, setareRulesEnabledonly when Rules is selected and the source has it enabled. This keeps the optimistic data in line with the server.getWorkspaceRulesreturningnullfor a workspace with Rules disabled.What alternative solutions did you explore? (Optional)
You could add the
areRulesEnabledcheck 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.
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
melvin-bot commentedon Oct 16, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @abzokhattab (
External)[-]Rules option should not be displayed when duplicating workspace[/-][+][$175] Rules option should not be displayed when duplicating workspace[/+]melvin-bot commentedon Oct 16, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022111095486930671989
camesenin commentedon Oct 16, 2026
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?
duplicate/utils.ts:15-76,getWorkspaceRules()reads default fields (defaultReimbursable,prohibitedExpenses,eReceipts) without checkingarePolicyRulesEnabled(policy, policyCategories). Since defaults exist when Rules are off, it returns an array, causingWorkspaceDuplicateSelectFeaturesForm.tsx:152andCopyPolicySettingsSelectFeaturesPage.tsx:130to display Rules.WorkspaceDuplicateSelectFeaturesForm.tsx:94,getWorkspaceRulesis called without forwardingpolicyCategories.Policy.ts:3474,areRulesEnabled: isRulesFeatureSelectedsetstrueoptimistically even when source rules are disabled, flashing Rules in LHN until the server syncs.MelvinBot Analysis
Melvin identifies the missing check but used
!policy?.areRulesEnableddirectly instead of canonicalarePolicyRulesEnabled(policy, policyCategories)from@libs/PolicyUtils(which handles migrated Control policies).What changes do you think we should make in order to solve the problem?
utils.ts, guard early:WorkspaceDuplicateSelectFeaturesForm.tsx:94:const rules = getWorkspaceRules(policy, translate, policyCategories);Policy.ts:3474:areRulesEnabled: isRulesFeatureSelected ? arePolicyRulesEnabled(sourcePolicy, policyCategories) : false,melvin-bot commentedon Oct 16, 2026
Details
taieddie commentedon Oct 16, 2026
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?
src/pages/workspace/duplicate/utils.ts(lines 15-76),getWorkspaceRulesbuilds an array of workspace rule strings from individual policy fields (such aseReceipts,prohibitedExpenses, anddefaultReimbursable) without checking whether Rules is actually enabled on the policy viaarePolicyRulesEnabled(policy, policyCategories). Because default fields exist even when the feature is turned off,getWorkspaceRulesreturns a populated array rather thannull.WorkspaceDuplicateSelectFeaturesForm.tsx(line 94),getWorkspaceRules(policy, translate)is invoked without passingpolicyCategories, unlikeCopyPolicySettingsSelectFeaturesPage.tsx(line 130). Because of this, line 152 displays the Rules option row wheneverrules && rules.length > 0 && !isCollect.WorkspaceDuplicateSelectFeaturesForm.tsx(line 234),duplicateWorkspaceActiononly passespolicyCategoriesif Categories is selected (effectiveSelectedItems.includes('categories') ? policyCategories : undefined). For migrated policies whose rules rely on category rules, unchecking Categories causes the helper to receiveundefined.src/libs/actions/Policy/Policy.ts(line 3474),duplicateWorkspace(buildOptimisticDuplicatePolicy) unconditionally setsareRulesEnabled: isRulesFeatureSelectedin 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?
In
src/pages/workspace/duplicate/utils.ts:arePolicyRulesEnabledfrom@libs/PolicyUtils.getWorkspaceRules(line 15), construct anactiveCategories: PolicyCategoriesrecord viaObject.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 wherecategory.pendingFields?.[field] === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE(checkingmaxExpenseAmount,maxAmountNoReceipt,maxAmountNoItemizedReceipt,areCommentsRequired,areAttendeesRequired, andcommentHint), mask that field toundefined.if (!arePolicyRulesEnabled(policy, activeCategories)) { return null; }.PolicyCategoriesrecord where pending-deletion rule fields are never treated as active, preventing false positives on migrated Control policies across bothWorkspaceDuplicateSelectFeaturesForm.tsxandCopyPolicySettingsSelectFeaturesPage.tsx.In
src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:policyCategoriesintogetWorkspaceRules:const rules = getWorkspaceRules(policy, translate, policyCategories);.policyCategoriesis already subscribed to viauseOnyxon line 57.policyCategoriesunconditionally toduplicateWorkspaceActioninstead of gating it oneffectiveSelectedItems.includes('categories'). Line 3565 inPolicy.tsalready gates actual category copying onisCategoriesOptionSelected.In
src/libs/actions/Policy/Policy.ts:buildOptimisticDuplicatePolicy(line 3474), constructactiveCategorieswith the same pending-deletion masking, and updateareRulesEnabledto usePolicyUtils.arePolicyRulesEnabled:areRulesEnabled: isRulesFeatureSelected ? PolicyUtils.arePolicyRulesEnabled(sourcePolicy, activeCategories) : false.In
tests/unit/WorkspaceDuplicateSelectFeaturesFormTest.tsx:createSourcePolicy(line 85), addareRulesEnabled: hasWorkspaceRulesto the mock policy fixture so the unit test accurately reflects the rules-enabled status of the source policy.getWorkspaceRulesreturnsnullwhenareRulesEnabledis false.What alternative solutions did you explore? (Optional)
MelvinBot's proposal suffers from a fatal flaw: it suggests checking
!policy?.areRulesEnableddirectly ingetWorkspaceRulesandPolicy.ts. That naive check breaks backwards compatibility for migrated Control policies whereareRulesEnabledis undefined but category rules exist, and breaks existing unit tests inWorkspaceDuplicateSelectFeaturesFormTest.tsx:137whose mock policy fixture leavesareRulesEnabledunset. Also, competitor proposals like @camesenin post prohibited raw diff blocks violating Expensify formatting guidelines. They break the rules. Leveraging the canonicalPolicyUtils.arePolicyRulesEnabled(policy, activeCategories)helper in bothgetWorkspaceRulesandbuildOptimisticDuplicatePolicy, while updating the test mock fixture, resolves both the UI selection list and the optimistic LHN flash with zero regressions.melvin-bot commentedon Oct 16, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
janiya2 commentedon Oct 16, 2026
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:
Unchecked Rules Generation in
getWorkspaceRules(src/pages/workspace/duplicate/utils.ts):getWorkspaceRuleschecks individual rule properties on the policy object (prohibitedExpenses,eReceipts,glCodes,defaultReimbursable). However, it completely omits checking whetherpolicy?.areRulesEnabledis actuallytrue. Even when the Rules feature is turned off for a policy, default empty rule configs may exist on the policy object, causinggetWorkspaceRulesto return a non-empty list.As a result,
WorkspaceDuplicateSelectFeaturesForm.tsx(line 152) andCopyPolicySettingsSelectFeaturesPage.tsx(line 130) display the "Rules" row and check it by default.Optimistic State Desync in
duplicateWorkspace(Policy.ts):In
src/libs/actions/Policy/Policy.ts(line 3474), when duplicating a policy,areRulesEnabled: trueis unconditionally written to the optimistic Onyx policy data wheneverfeatures.rulesis 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?
getWorkspaceRulesby checkingareRulesEnabled:In
src/pages/workspace/duplicate/utils.ts:Note: Returning
nullautomatically hides the Rules row in bothWorkspaceDuplicateSelectFeaturesForm.tsxandCopyPolicySettingsSelectFeaturesPage.tsx.Policy.ts:In
src/libs/actions/Policy/Policy.ts: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.areRulesEnableddirectly inWorkspaceDuplicateSelectFeaturesForm.tsx. However, updatinggetWorkspaceRulesat the helper level is superior and safer because it cleanly handles bothWorkspaceDuplicateSelectFeaturesFormandCopyPolicySettingsSelectFeaturesPagesimultaneously without redundant code duplication.Tests
Automated Tests:
Add unit test in
tests/unit/WorkspaceDuplicateTest.ts:Manual Verification:
melvin-bot commentedon Oct 16, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
57 remaining items
huhuli083-creator commentedon Oct 27, 2026
Proposal
What is the root cause of that problem?
Duplicate Workspace UI unconditionally includes the Rules feature card:
In
src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:104-110,allItemsbuilds the list of selectable features to duplicate by iterating overObject.keys(enabledFeatures)and filtering onlyfeature !== CONST.POLICY.MORE_FEATURES.ARE_COMPANY_CARDS_ENABLED. BecausegetEnabledDuplicateFeatures(src/pages/workspace/duplicate/utils.ts:154-169) mapsareRulesEnabled: policy?.areRulesEnabled, any source workspace withareRulesEnabled: truedisplays the "Rules - Require receipts, flag high-spend, and more" option in the Duplicate Workspace feature selection list (WorkspaceDuplicateSelectFeaturesForm.tsx:66-70).duplicateWorkspaceonly copies a partial subset of policy fields and never duplicates workspace rules:In
src/libs/actions/Policy/Policy.ts:4822-4906,duplicateWorkspaceconstructspolicyDataviabuildPolicyDataand then selectively merges fields onto${ONYXKEYS.COLLECTION.POLICY}${newPolicyID}(and into theDuplicateWorkspaceAPI parametersDuplicateWorkspaceParams). Specifically, whenparts.rulesis truthy (Policy.ts:4895), it only copies three fields (areRulesEnabled,customRules, andrequireTag), 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,commentHintinsrc/libs/actions/Policy/OptimisticPolicyCategoriesAndMccGroups.ts:89-99, which resets category fields to empty defaults).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_ENABLEDcheckbox inWorkspaceDuplicateSelectFeaturesFormmisleads 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?
Exclude
ARE_RULES_ENABLEDfrom the Duplicate Workspace feature options and payload:src/pages/workspace/duplicate/utils.ts:areRulesEnabledfromgetEnabledDuplicateFeatures(lines 154-169) soARE_RULES_ENABLEDis never counted ingetTotalEnabledDuplicateFeatures(lines 171-177, which controls whether clicking "Duplicate workspace" opensWorkspaceDuplicateSelectFeaturesFormvs.confirmDuplicateWorkspace).[CONST.POLICY.MORE_FEATURES.ARE_RULES_ENABLED]: 'rules'frommappingFeatureToPartKey(lines 25-38) and removerules?: booleanfromDuplicateWorkspaceParts(lines 11-23).src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:[CONST.POLICY.MORE_FEATURES.ARE_RULES_ENABLED]entry fromfeatureItems(lines 66-70).src/libs/actions/Policy/Policy.ts:duplicateWorkspace(lines 4895-4899), stop settingareRulesEnabled: Boolean(parts?.rules),customRules, andrequireTagon the optimistic duplicated policy so the newly duplicated workspace starts with rules disabled (areRulesEnabled: false), matching a fresh workspace.Unit Tests:
tests/unit/DuplicateWorkspaceUtilsTest.ts(or add test coverage) to verify thatgetEnabledDuplicateFeaturesandgetTotalEnabledDuplicateFeaturesdo not includeareRulesEnabledeven whenpolicy.areRulesEnabledistrue, and thatduplicateWorkspacedoes not enable rules on the duplicated policy.What alternative solutions did you explore? (Optional)
areRulesEnabledingetEnabledDuplicateFeaturesand only filteringARE_RULES_ENABLEDout ofallItemsinWorkspaceDuplicateSelectFeaturesForm.tsx:If we only hid
ARE_RULES_ENABLEDinWorkspaceDuplicateSelectFeaturesForm.tsxwithout removingareRulesEnabledfromgetEnabledDuplicateFeatures, a workspace where onlyareRulesEnabledis turned on would havetotalEnabledFeatures === 1(WorkspaceProfilePage.tsx), causing the app to navigate to an emptyWorkspaceDuplicateSelectFeaturesFormscreen with zero items to select. Removing it fromgetEnabledDuplicateFeaturesavoids this edge case.melvin-bot commentedon Oct 27, 2026
📣 @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:
Format:
abbasifaizan70 commentedon Oct 27, 2026
@abzokhattab I've updated my proposal above — it now covers the flash, not just the row.
What's new:
Root cause of the flash.
applyHTTPSOnyxUpdatesappliesresponse.onyxDatabeforesuccessData. TheDuplicatePolicyresponse 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'ssuccessDatanever re-assertsareRulesEnabled— so the wrong value stays in Onyx untilOpenPolicyProfilePagereturns the storedfalse. 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.Why it's Control-only.
arePolicyRulesEnabledonly answers definitively while the flag is an explicit boolean; when it'sundefinedit returnsfalsefor Collect but falls back tohasAnyCategoryRulesfor 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 isundefinedon migrated Control workspaces).A gap the row fix opens on its own. Hiding the Rules row makes
hasRulesItemfalse, 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.Automated tests — five listed in the proposal, three of them fail on
mainand 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.
andregrunert-ctrl commentedon Oct 27, 2026
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?
WorkspaceDuplicateSelectFeaturesForm.tsx, the Rules row is rendered based ongetWorkspaceRules, which inspects individual rule properties without strictly verifyingpolicy?.areRulesEnabled. In Control workspaces with Rules off, legacy rule objects still exist, causing the row to appear.Policy.ts(duplicateWorkspace), optimistic updates initializeareRulesEnabled: trueor 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?
getWorkspaceRules/WorkspaceDuplicateSelectFeaturesForm.tsx, ensure the Rules row is only added whenPolicyUtils.isPolicyFeatureEnabled(policy, CONST.POLICY.MORE_FEATURES.ARE_RULES_ENABLED)(orpolicy?.areRulesEnabled) evaluates totrue.Policy.ts(duplicateWorkspace), ensure the optimistic policy payload explicitly respects the resolved feature flags and does not defaultareRulesEnabledtotruewhen the source workspace has it disabled or when Rules is omitted from selection.CopyPolicySettingsSelectFeaturesPage.tsxto prevent the same row exposure in policy copying flows.What alternative solutions did you explore? (Optional)
melvin-bot commentedon Oct 27, 2026
📣 @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:
Format:
andregrunert-ctrl commentedon Oct 27, 2026
melvin-bot commentedon Oct 27, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
github-actions commentedon Oct 27, 2026
Kabeer099 commentedon Oct 28, 2026
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
DuplicatePolicyOnyx lifecycle:getWorkspaceRulesignores whether Rules is enabled on the policy, andWorkspaceDuplicateSelectFeaturesFormomitspolicyCategories:src/pages/workspace/duplicate/utils.ts:15-79,getWorkspaceRulesbuilds the list of rule labels from individual policy fields (prohibitedExpenses,eReceipts,glCodes,defaultReimbursable, etc.) without checkingarePolicyRulesEnabled(policy, policyCategories)(src/libs/PolicyUtils/features.ts:27-42). Because a Control workspace with Rules turned off still retains default/residual values on those fields,getWorkspaceRulesreturns a non-empty array.src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:94,getWorkspaceRules(policy, translate)is called without passingpolicyCategories(unlikeCopyPolicySettingsSelectFeaturesPage.tsx:130), so migrated Control workspaces whose rules state depends on category rules cannot be evaluated accurately.Hiding only the
rulesrow uncouplescodingRules("Merchant rules") when Rules is disabled:src/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:161-167, 201-203,codingRules("Merchant rules") is gated only bycodingRulesCount > 0 && !isCollect, and its coupling to the Rules checkbox relies onconst hasRulesItem = items.some((item) => item.value === 'rules').nullfromgetWorkspaceRulesmakeshasRulesItemfalse, leaving Merchant rules visible and selected on its own even though Rules is disabled (and inCopyPolicySettingsSelectFeaturesPage, copyingcodingRulesforcibly enables Rules on the target workspace).Why Rules flashes briefly in the duplicated workspace's LHN (both optimistic state and
DuplicatePolicyserver response):src/libs/actions/Policy/Policy.ts:3514, 3519,buildOptimisticDuplicatePolicyspreads...sourcePolicyand setsareRulesEnabled: isRulesFeatureSelectedwithout checkingarePolicyRulesEnabled(sourcePolicy, policyCategories).parts.expensesisfalse(after hiding the Rules row), theDuplicatePolicyAPI response'sonyxDatamerges the new policy withareRulesEnabled: falseand then merges a trailing{areRulesEnabled: true}update onto${ONYXKEYS.COLLECTION.POLICY}${targetPolicyID}.applyHTTPSOnyxUpdatesappliesresponse.onyxDatabeforesuccessData, andsuccessDatainsrc/libs/actions/Policy/Policy.ts:3706-3724only clearspendingAction/pendingFieldswithout re-assertingareRulesEnabled, Onyx holdsareRulesEnabled: truefor ~300–500ms untilOpenPolicyProfilePageoverwrites it withfalse—causing the brief LHN flash.What changes do you think we should make in order to solve the problem?
Gate
getWorkspaceRuleswitharePolicyRulesEnabled(policy, policyCategories)insrc/pages/workspace/duplicate/utils.ts:(Using
arePolicyRulesEnabled(policy, policyCategories)instead of raw!policy?.areRulesEnabledpreserves backwards compatibility for migrated Control workspaces whereareRulesEnabledisundefinedbut category rules exist.)Pass
policyCategoriesand gatecodingRulesonareRulesEnabledinsrc/pages/workspace/duplicate/WorkspaceDuplicateSelectFeaturesForm.tsx:policyCategoriesat line 94:const rules = getWorkspaceRules(policy, translate, policyCategories);codingRulesitem (line 161) onarePolicyRulesEnabled(policy, policyCategories)(and apply the same check toCODING_RULESinisCopyPolicySettingsPartEnabledOnSource).Prevent the LHN flash in both
buildOptimisticDuplicatePolicyandsuccessDatainsrc/libs/actions/Policy/Policy.ts:const shouldEnableRules = Boolean(parts?.expenses && PolicyUtils.arePolicyRulesEnabled(policy, policyCategories));buildOptimisticDuplicatePolicy(line 3519), setareRulesEnabled: shouldEnableRules.buildDuplicatePolicyData'ssuccessData(line 3708), includeareRulesEnabled: shouldEnableRuleson${ONYXKEYS.COLLECTION.POLICY}${targetPolicyID}so thatsuccessDataimmediately overrides the stray{areRulesEnabled: true}entry inDuplicatePolicy'sresponse.onyxDatawithin the same Onyx batch.What alternative solutions did you explore? (Optional)
!policy?.areRulesEnableddirectly ingetWorkspaceRules: Rejected because migrated Control policies haveareRulesEnabled === undefinedwhen category rules are active (arePolicyRulesEnabledhandles this canonical case).buildOptimisticDuplicatePolicywithoutsuccessData: Insufficient because theDuplicatePolicyresponse'sonyxDatamerges{areRulesEnabled: true}even whenparts.expensesisfalse, sosuccessDatamust enforce the requested boolean.SaimManzoor49 commentedon Oct 28, 2026
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
DuplicatePolicydata 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?
PolicyUtils/duplicateFeatures.tsto map form choices to API parts, includingrules → expensesandworkflows → 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.getDuplicateWorkspaceFeatureStatederives 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.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.getWorkspaceRulesusesarePolicyRulesEnabled(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 servertrue/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.
melvin-bot commentedon Oct 28, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
bhomvratrai-glitch commentedon Oct 28, 2026
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!
github-actions commentedon Oct 28, 2026
Fix feature availability when duplicating workspaces