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
[$175] Report fields - Disabled list type report field appears on report after deletion #103935
Version Number: 9.5.8-3 Reproducible in staging?: Yes Reproducible in production?: Yes If this was caught during regression testing, add the test name, ID and link from BrowserStack:#102142 Email or phone of affected tester (no customers):applausetester+bm8900011@applause.expensifail.com Issue reported by: Applause Internal Team Bug source: Exploratory - Significant User Experience Deterioration Device used: Web, android App Component: Workspace Settings
Action Performed:
Go to staging.new.expensify.com
Create a workspace
Go to Workspace Settings > Report fields and create a list type report field
Go to workspace chat and create an expense report
Go to Workspace Settings > Report fields, open the list type report field, and disable all list options
Go to workspace chat, open the report, and verify the report field does not appear
Go to Workspace Settings > Report fields and delete the list type report field
Navigate back to workspace chat and open the report
Expected Result:
A disabled list field that was hidden before deletion should stay hidden after deletion.
Actual Result:
Disabled list type report field appears on the report after deletion
Only the workspace's copy of a report field records which list options are disabled. The report keeps its own snapshot of the field, taken when the report was created, and that snapshot still shows every option as enabled.
getAvailableReportFields copies disabledOptions from the workspace field onto the report's copy, but only while the workspace field exists.
After you delete the field, policyReportField is undefined, so the report's stale disabledOptions are used.
Add a unit test: given a deleted list field with no value, the field is hidden.
A deleted field that still has a value stays visible, so the report keeps data a user entered.
What alternative solutions did you explore? (Optional)
Filter in getAvailableReportFields. This is broader. It also changes the report title logic that reads this list.
Sync disabledOptions onto each report's copy. This would be exact, but the backend would have to write it when options change or the field is deleted. That work is outside the App repo.
Supporting evidence
Workspace actions update only policy.fieldList, never a report's fieldList. This applies to both deleteReportFields (src/libs/actions/Policy/ReportField.ts, around lines 313-394) and updateReportFieldListValueEnabled (around lines 476-525).
While the delete is pending, the workspace field still exists, so the field stays hidden. It reappears once the success data sets the workspace field to null.
The comment in getAvailableReportFields says keeping deleted fields on the report is intended: "These will persist even if a field is deleted from the policy."
Confidence is medium. The code path is clear, but I did not reproduce the bug or check whether the report's copy has an empty value in this repro. If the backend fills in a default value, the "no value" check would not hide the field.
Next Steps for Contributor+ team: To accept:@MelvinBot implement [this](https://github.com/Expensify/App/issues/103935) to create a draft PR. To refine:@MelvinBot <your feedback> To reject: Explain why you are rejecting Melvin's proposal.
changed the title [-]Report fields - Disabled list type report field appears on report after deletion[/-][+][$175] Report fields - Disabled list type report field appears on report after deletion[/+]on Oct 13, 2026
Please re-state the problem that we are trying to solve in this issue.
When a workspace admin disables all list options of a list-type report field and then deletes that report field from Workspace Settings, the report field (which was correctly hidden on an expense report while all its options were disabled) unexpectedly reappears on the report after the field is deleted from the workspace.
What is the root cause of that problem?
When options of a list report field are disabled in Workspace Settings (updateReportFieldListValueEnabled), only policy.fieldList[fieldKey].disabledOptions is updated in Onyx. The report's snapshot in report.fieldList retains its initial state where options were not recorded as disabled.
While the field still exists on the workspace policy:
getAvailableReportFields merges disabledOptions: policyReportField.disabledOptions from the policy onto the report's copy of the field:
Because all options are disabled in policyReportField.disabledOptions, hasEnabledListValue(reportField) returns false.
Consequently, shouldHideSingleReportField evaluates !hasEnableOption to true, and the field is properly hidden on the report.
However, once the report field is deleted from the workspace:
policyReportField becomes undefined.
In getAvailableReportFields, the spread ...(policyReportField ? ... : {}) does not run.
As a result, reportField.disabledOptions falls back to the report's local copy, where disabledOptions is undefined (or empty).
In hasEnabledListValue, !reportField.disabledOptions.at(index) evaluates !undefined to true, erroneously concluding that all list options are enabled!
Therefore, shouldHideSingleReportField returns false (do not hide), and the deleted list field unexpectedly unhides and reappears on the report with an inline delete button.
What changes do you think we should make in order to solve the problem?
The root issue should be resolved centrally in src/libs/ReportUtils.ts within getAvailableReportFields.
All downstream consumers (MoneyRequestViewReportFields.tsx, MoneyReportView.tsx, and hasFieldViolations in ReportUtils.ts) obtain their fields through getReportFieldMaps, which calls getAvailableReportFields.
Primary Recommended Approach (Single-Point Central Fix in getAvailableReportFields):
In src/libs/ReportUtils.ts, inside getAvailableReportFields:
When a field is present in report.fieldList (field) but no longer exists on the policy (!policyReportField), if it is a list type field that has no selected value (!field.value), it should not be returned for display on unsettled reports:
constfields=mergedFieldIds.map((id)=>{constfield=report?.fieldList?.[getReportFieldKey(id)]??report?.fieldList?.[id];constpolicyReportField=policyReportFields.find(({fieldID})=>fieldID===id);if(field){// If a list-type report field has been deleted from the policy and has no selected value,// it cannot be selected and should not reappear on the report.if(!policyReportField&&field.type===CONST.REPORT_FIELD_TYPES.LIST&&!field.value){returnnull;}return{
...field,
...(policyReportField
? {disabledOptions: policyReportField.disabledOptions,values: policyReportField.values,}
: {}),};}if(policyReportField){returnpolicyReportField;}returnnull;});
Why this is the cleanest solution:
If a list field was deleted from the policy, no valid options can ever be selected for it on the workspace.
If the report already has an entered value (field.value), it will still be returned and preserved historically as intended.
If the field was deleted without any value entered (or was already disabled and hidden), it cleanly remains hidden.
It does NOT require modifying function signatures or prop drilling policyFieldList across MoneyRequestViewReportFields.tsx, MoneyReportView.tsx, and ReportUtils.ts.
Unit Tests:
Add unit tests in tests/unit/ReportUtilsTest.ts:
Verify that getAvailableReportFields excludes an unsettled report's list field when the field is deleted from policyReportFields and has no value (value: '').
Verify that getAvailableReportFields retains a deleted list field if it has a populated historical value (value: 'Option 1').
What alternative solutions did you explore? (Optional)
Passing policyFieldList to shouldHideSingleReportField:
Modifying shouldHideSingleReportField(reportField, policyFieldList) and checking !policyFieldList?.[fieldKey] && !reportField.value. Drawback: This modifies the function signature and requires updating all 3 caller sites (MoneyRequestViewReportFields.tsx:150, MoneyReportView.tsx:132, and ReportUtils.ts:10718), introducing unnecessary code duplication across components. Handling it at the source in getAvailableReportFields is far cleaner and adheres strictly to DRY principles.
Syncing disabledOptions to all report snapshots upon policy updates: Drawback: Modifying all active report snapshots on the client or backend whenever an admin toggles a list option in workspace settings would be expensive, non-performant, and out-of-scope for the App repository.
Upwork Proposal / Cover Letter (For Upwork Submission)
Hi,
I have thoroughly investigated Expensify issue #103935 ("Report fields - Disabled list type report field appears on report after deletion") and identified the exact root cause in src/libs/ReportUtils.ts.
Summary of Root Cause & Fix:
When options of a list report field are disabled in Workspace Settings, only policy.fieldList[fieldKey].disabledOptions is updated in Onyx, while the report's snapshot retains an un-updated state.
Once the field is deleted from the workspace, policyReportField becomes undefined. In getAvailableReportFields, falling back to the report copy results in disabledOptions being undefined. Consequently, hasEnabledListValue evaluates !undefined to true, causing shouldHideSingleReportField to return false and unexpectedly unhide the deleted list field.
I have prepared a clean, single-point architectural fix centrally within getAvailableReportFields in ReportUtils.ts, ensuring that deleted list fields without a selected value (!field.value) are excluded from unsettled reports, without requiring multi-file prop-drilling or breaking caller signatures.
My full technical proposal and unit test plan are detailed on the GitHub issue: #103935
Please re-state the problem that we are trying to solve in this issue.
When a list-type report field has all of its options disabled on a workspace, it is correctly hidden on unsettled expense reports. However, once the report field is deleted from the workspace settings, the disabled field unexpectedly reappears on the unsettled report with an empty value.
What is the root cause of that problem?
The root cause lies in how src/libs/ReportUtils.ts:getAvailableReportFields reconciles policy field definitions with the report's snapshot:
Option Disabling Updates Policy Only:
When an admin disables all list options in Workspace Settings > Report Fields, Onyx updates policy.fieldList[fieldKey].disabledOptions to [true, true, ...]. Existing report snapshots (report.fieldList) are not mutated, so report.fieldList[fieldKey].disabledOptions remains undefined.
Snapshot Overlay in getAvailableReportFields (lines 5190–5203):
Because all options in policyReportField.disabledOptions are true, hasEnabledListValue returns false. shouldHideSingleReportField evaluates !hasEnableOption to true and hides the field.
Regression Upon Deletion:
When the field is deleted from the workspace, policyReportFields no longer contains the field (policyReportField === undefined).
In getAvailableReportFields, the spread ...(policyReportField ? ... : {}) evaluates to {}.
Consequently, field.disabledOptions reverts to the report snapshot's local value (undefined).
In hasEnabledListValue, !reportField.disabledOptions.at(index) evaluates !undefined to true, mistakenly considering every option enabled.
shouldHideSingleReportField now returns false (do not hide), causing the deleted, unselectable list field to resurrect on the unsettled report.
What changes do you think we should make in order to solve the problem?
In src/libs/ReportUtils.ts:
Cleanly Filter Unpopulated Deleted Fields in getAvailableReportFields:
For unsettled reports, a report field that was completely deleted from the workspace policy (!policyReportField) and has no entered value (!field.value?.trim()) cannot have options selected or edited, and should not be displayed:
constfields=mergedFieldIds.map((id)=>{constfield=report?.fieldList?.[getReportFieldKey(id)]??report?.fieldList?.[id];constpolicyReportField=policyReportFields.find(({fieldID})=>fieldID===id);if(field){// If a field has been deleted from the policy and has no entered value on an unsettled report,// it cannot be filled and should remain hidden.if(!policyReportField&&!field.value){returnnull;}return{
...field,
...(policyReportField
? {disabledOptions: policyReportField.disabledOptions,values: policyReportField.values,}
: {}),};}if(policyReportField){returnpolicyReportField;}returnnull;});
Preserve Historical Values on Settled / Populated Reports:
If a field was previously populated by the user before deletion (field.value is present), !field.value is false, so the field is preserved in the list for auditability and historical record.
If the report is settled (reportIsSettled === true), lines 5181–5183 already return reportFields directly.
Only unpopulated, deleted fields on unsettled reports are omitted, matching expected design behavior.
What alternative solutions did you explore? (Optional)
Prop-drilling policyFieldList into shouldHideSingleReportField: Requires changing signatures across MoneyRequestViewReportFields.tsx, MoneyReportView.tsx, and ReportUtils.ts. Resolving it centrally at the source in getAvailableReportFields adheres to DRY principles and eliminates redundant prop forwarding.
Mutating all active reports on the backend upon policy deletion: Expensive and out of scope for frontend state reconciliation.
Test Plan
Automated Unit Tests (tests/unit/ReportUtilsTest.ts):
Create an unsettled report with a list field in report.fieldList that is missing from policyReportFields.
When field.value is empty, assert that getAvailableReportFields returns an array excluding the deleted field.
When field.value is 'Option 1', assert that getAvailableReportFields retains the deleted field with its value intact.
When reportIsSettled is true, assert that all snapshot fields continue to be returned.
Manual QA Steps:
Navigate to Workspace Settings > Report Fields and create a list type report field with options.
Open workspace chat and create a new expense report.
In Workspace Settings > Report Fields, disable all options for that list field.
Return to the expense report and verify the list field is hidden.
In Workspace Settings > Report Fields, delete the list field.
Return to the expense report.
Verify: The deleted list field remains hidden and does not reappear on the report.
Create another expense report and select a value for a list report field. Delete the field from workspace settings.
Verify: The entered value is still displayed on the expense report historically.
Please re-state the problem that we are trying to solve in this issue.
When a list type report field has all of its options disabled in Workspace Settings, it is correctly hidden on unsettled expense reports. However, if the workspace admin subsequently deletes that list report field from the workspace, the field unexpectedly reappears on the report with a delete (trash) button.
What is the root cause of that problem?
This is a regression introduced by PR #102142 ("Restore: Show up to three report fields per row and edit them inline"), which added inline delete buttons for report fields that were deleted from the workspace so that historical entered values wouldn't be locked without an editor.
When options of a list report field are disabled in Workspace Settings, the update modifies policy.fieldList[fieldKey].disabledOptions in Onyx. The report snapshot (report.fieldList) is not updated across all reports.
While the field exists on the policy:
In src/libs/ReportUtils.ts (getAvailableReportFields), disabledOptions is merged onto the report's copy:
Because all options are disabled in policyReportField.disabledOptions, hasEnabledListValue returns false.
In shouldHideSingleReportField: !hasEnableOption evaluates to true, and the field is correctly hidden.
Once the field is deleted from the workspace:
policyReportField becomes undefined.
In getAvailableReportFields, reportField.disabledOptions is taken from report.fieldList, where disabledOptions is undefined (or empty).
In hasEnabledListValue, !reportField.disabledOptions.at(index) evaluates !undefined to true, erroneously assuming all options are enabled.
Consequently, shouldHideSingleReportField returns false (do not hide).
In MoneyRequestViewReportFields.tsx, isFieldDeletable: field.fieldID !== CONST.REPORT_FIELD_TITLE_FIELD_ID && !isFieldOnPolicy && isEditable evaluates to true, resurrecting the deleted list field with an inline delete button.
What changes do you think we should make in order to solve the problem?
The fix should be handled centrally in src/libs/ReportUtils.ts inside getAvailableReportFields.
When a field is present in report.fieldList (field) but no longer exists on the policy (!policyReportField), if it is a list type field that has no selected value (!field.value), it has no valid selectable options and must not be resurrected on unsettled reports:
constfields=mergedFieldIds.map((id)=>{constfield=report?.fieldList?.[getReportFieldKey(id)]??report?.fieldList?.[id];constpolicyReportField=policyReportFields.find(({fieldID})=>fieldID===id);if(field){// If a list-type report field was deleted from the policy and has no selected value,// it cannot be selected and should not reappear on unsettled reports.if(!policyReportField&&field.type===CONST.REPORT_FIELD_TYPES.LIST&&!field.value){returnnull;}return{
...field,
...(policyReportField
? {disabledOptions: policyReportField.disabledOptions,values: policyReportField.values,}
: {}),};}if(policyReportField){returnpolicyReportField;}returnnull;});
Why this is the cleanest solution:
If an admin deletes a list field from the workspace, no new values can ever be selected for it.
If the field has no entered value (e.g. was disabled and hidden prior to deletion), it cleanly remains hidden.
It does not require prop-drilling or modifying caller signatures across MoneyRequestViewReportFields.tsx, MoneyReportView.tsx, or hasFieldViolations.
Unit Tests:
Add unit test coverage in tests/unit/ReportUtilsTest.ts:
Verify that getAvailableReportFields excludes an unsettled report's list field when the field is deleted from policyReportFields and has no value (value: '').
Verify that getAvailableReportFields retains a deleted list field if it has a populated historical value (value: 'Option 1').
What alternative solutions did you explore? (Optional)
Passing policyFieldList to shouldHideSingleReportField:
Modifying shouldHideSingleReportField(reportField, policyFieldList) and checking !policyFieldList?.[fieldKey] && !reportField.value. Drawback: Modifies the function signature across multiple call sites (MoneyRequestViewReportFields.tsx, MoneyReportView.tsx, and ReportUtils.ts), violating DRY. Handling it at the source in getAvailableReportFields is far cleaner.
Syncing disabledOptions across all report snapshots on policy updates: Drawback: Updating all active reports in Onyx when options are toggled would introduce heavy performance overhead and network operations.
Root cause:getAvailableReportFields in src/libs/ReportUtils.ts reconciles the report's snapshot of a report field against the workspace's live definition, copying disabledOptions from the workspace field onto the report's copy only while the workspace field still exists. After the field is deleted from Workspace Settings, policyReportField is undefined, so the reconciliation falls back to the report's stale snapshot where all options read as enabled, and the field reappears on the report.
Fix approach:
In getAvailableReportFields, when the workspace-side field definition is missing (deleted) but a report-side snapshot exists, preserve the previously applied disabled/hidden state instead of resetting to all-enabled - e.g. carry over disabledOptions from the last known snapshot or exclude the field when it was hidden before deletion.
Alternatively, clear the report field value entry when its workspace definition is deleted so the field has nothing to render.
Keep behavior unchanged for fields that still exist on the workspace.
Verification: (1) Follow the exact repro: create list field, disable all options, confirm hidden on report, delete the field, confirm it stays hidden on the report. (2) Regression: a non-disabled list field that is deleted behaves the same as before. (3) Existing report-fields unit tests still pass.
When report fields are configured in Workspace Settings, option states (such as disabledOptions) are stored on the policy's field definition in Onyx (policy.fieldList), while individual reports retain a local snapshot (report.fieldList) from when the report was created.
While the list field exists on the policy:
Inside getAvailableReportFields in src/libs/ReportUtils.ts, the policy's live disabledOptions array is merged onto the report field snapshot. When all options are disabled, hasEnabledListValue checks disabledOptions and returns false, so shouldHideSingleReportField correctly hides the field on expense reports.
Once the list field is deleted from the workspace: policyReportField becomes undefined in getAvailableReportFields. The function falls back to the report's snapshot in report.fieldList, where disabledOptions is empty or undefined.
In hasEnabledListValue, checking indices against an empty or undefined disabledOptions array evaluates as enabled (!undefined === true). Consequently, shouldHideSingleReportField returns false, causing the deleted field to unexpectedly reappear on the report with an inline delete button.
What changes do you think we should make in order to solve the problem?
In src/libs/ReportUtils.ts within getAvailableReportFields:
When constructing the list of available fields for an unsettled report, check if a field originates from the report's snapshot but no longer exists on the policy (!policyReportField). If the field is a list type (CONST.REPORT_FIELD_TYPES.LIST) and has no entered value (!field.value), omit it from the returned available fields.
This ensures:
If a list field was deleted from the workspace and has no entered value (such as a field that was previously disabled and hidden), it cleanly remains hidden on unsettled reports.
Settled reports remain unaffected as they continue to return their persisted reportFields.
We will also add unit tests in tests/unit/ReportUtilsTest.ts:
Verify that getAvailableReportFields omits a list-type field that was deleted from the policy when it has no value.
Verify that getAvailableReportFields retains a deleted list-type field if a value was previously saved.
What alternative solutions did you explore? (Optional)
Modifying shouldHideSingleReportField across call sites to inspect whether the field exists on the policy. This was rejected because it requires prop-drilling policy fields through multiple components (MoneyRequestViewReportFields, MoneyReportView, and violation utilities), whereas handling it centrally in getAvailableReportFields solves the problem cleanly at the data source.
Broadcasting updates to all report snapshots whenever workspace field options are toggled. This was rejected due to unnecessary Onyx write overhead and network complexity.
@mkhutornyi I was able to reproduce this and identified why MelvinBot's 4-file proposal is unnecessarily invasive:
Melvin's premise is mistaken: Melvin avoided fixing getAvailableReportFields claiming it affects report title logic. In reality, report title logic explicitly filters for isReportFieldOfTypeTitle (type: 'text'), which is never a list field. Title logic is completely unaffected.
The real root cause: When options are disabled, only policy.fieldList is updated in Onyx. Once deleted from the policy, policyReportField becomes undefined in getAvailableReportFields. The fallback to the report's snapshot leaves disabledOptions undefined, which hasEnabledListValue treats as enabled, unhiding the field.
I have a clean 1-file fix directly in getAvailableReportFields (ReportUtils.ts) that excludes deleted list fields without a value on unsettled reports. It solves the issue centrally at the source without prop-drilling policyFieldList across 4 files or changing function signatures.
Repro Demo:
reproduceDemoReport.webm
Fix Demo:
fixDemoReport.webm
Let me know if you'd like me to submit the full proposal!
@mkhutornyi Thanks a lot @emkhalid for testing this and sharing the demo videos!
It's great to see confirmation that handling this in getAvailableReportFields (as outlined in the proposal above) resolves the issue cleanly in a single file without affecting report titles.
Looking forward to your review @mkhutornyi, and happy to get started on the PR and unit tests whenever you're ready!
Activity
applause-bot commentedon Oct 13, 2026
While Applause doesn't embed attachments for this bug, here are the links for quick view:
https://platform.applause.com/services/links/v1/external/da8709f0f0418e466630b17d5f5b02587650f8a38c0dc396e1a04700b38c8a42
MelvinBot commentedon Oct 13, 2026
Proposal
What is the root cause of that problem?
Only the workspace's copy of a report field records which list options are disabled. The report keeps its own snapshot of the field, taken when the report was created, and that snapshot still shows every option as enabled.
getAvailableReportFieldscopiesdisabledOptionsfrom the workspace field onto the report's copy, but only while the workspace field exists.policyReportFieldis undefined, so the report's staledisabledOptionsare used.shouldHideSingleReportFieldnow finds enabled options, so the field shows again.This bug is older than Restore: Show up to three report fields per row and edit them inline, which did not touch this logic. That PR did add an inline delete button for fields that are no longer on the workspace, which makes the field that reappears easier to notice.
What changes do you think we should make in order to solve the problem?
In
shouldHideSingleReportField, also hide a list field that is no longer on the workspace and has no value:policyFieldListargument.truewhen the field type islist, the field is not inpolicyFieldList, and the field has no value.policy?.fieldListfrom all three callers so they stay consistent:MoneyRequestViewReportFields.tsx:150, which picks the fields to renderMoneyReportView.tsx:132, which decides whether to show the field block at allReportUtils.ts:10718, which checks field violations (the red error dot)A deleted field that still has a value stays visible, so the report keeps data a user entered.
What alternative solutions did you explore? (Optional)
getAvailableReportFields. This is broader. It also changes the report title logic that reads this list.disabledOptionsonto each report's copy. This would be exact, but the backend would have to write it when options change or the field is deleted. That work is outside the App repo.Supporting evidence
policy.fieldList, never a report'sfieldList. This applies to bothdeleteReportFields(src/libs/actions/Policy/ReportField.ts, around lines 313-394) andupdateReportFieldListValueEnabled(around lines 476-525).null.getAvailableReportFieldssays keeping deleted fields on the report is intended: "These will persist even if a field is deleted from the policy."valuein this repro. If the backend fills in a default value, the "no value" check would not hide the field.Next Steps for Contributor+ team:
To accept:
@MelvinBot implement [this](https://github.com/Expensify/App/issues/103935)to create a draft PR.To refine:
@MelvinBot <your feedback>To reject: Explain why you are rejecting Melvin's proposal.
view run
melvin-bot commentedon Oct 13, 2026
Triggered auto assignment to Contributor-plus team member for initial proposal review - @mkhutornyi (
External)[-]Report fields - Disabled list type report field appears on report after deletion[/-][+][$175] Report fields - Disabled list type report field appears on report after deletion[/+]melvin-bot commentedon Oct 13, 2026
Job added to Upwork: https://www.upwork.com/jobs/~022110094793846314623
janiya2 commentedon Oct 13, 2026
Contributor details
Your Expensify account email: janithad2@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/janithad
Upwork Job URL: https://www.upwork.com/jobs/~022110094793846314623
Upwork Job ID: 2110094793846314623
Proposal
Please re-state the problem that we are trying to solve in this issue.
When a workspace admin disables all list options of a list-type report field and then deletes that report field from Workspace Settings, the report field (which was correctly hidden on an expense report while all its options were disabled) unexpectedly reappears on the report after the field is deleted from the workspace.
What is the root cause of that problem?
updateReportFieldListValueEnabled), onlypolicy.fieldList[fieldKey].disabledOptionsis updated in Onyx. The report's snapshot inreport.fieldListretains its initial state where options were not recorded as disabled.getAvailableReportFieldsmergesdisabledOptions: policyReportField.disabledOptionsfrom the policy onto the report's copy of the field:hasEnabledListValue:policyReportField.disabledOptions,hasEnabledListValue(reportField)returnsfalse.shouldHideSingleReportFieldevaluates!hasEnableOptiontotrue, and the field is properly hidden on the report.policyReportFieldbecomesundefined.getAvailableReportFields, the spread...(policyReportField ? ... : {})does not run.reportField.disabledOptionsfalls back to the report's local copy, wheredisabledOptionsisundefined(or empty).hasEnabledListValue,!reportField.disabledOptions.at(index)evaluates!undefinedtotrue, erroneously concluding that all list options are enabled!shouldHideSingleReportFieldreturnsfalse(do not hide), and the deleted list field unexpectedly unhides and reappears on the report with an inline delete button.What changes do you think we should make in order to solve the problem?
The root issue should be resolved centrally in
src/libs/ReportUtils.tswithingetAvailableReportFields.All downstream consumers (
MoneyRequestViewReportFields.tsx,MoneyReportView.tsx, andhasFieldViolationsinReportUtils.ts) obtain their fields throughgetReportFieldMaps, which callsgetAvailableReportFields.Primary Recommended Approach (Single-Point Central Fix in
getAvailableReportFields):In
src/libs/ReportUtils.ts, insidegetAvailableReportFields:When a field is present in
report.fieldList(field) but no longer exists on the policy (!policyReportField), if it is alisttype field that has no selected value (!field.value), it should not be returned for display on unsettled reports:Why this is the cleanest solution:
field.value), it will still be returned and preserved historically as intended.policyFieldListacrossMoneyRequestViewReportFields.tsx,MoneyReportView.tsx, andReportUtils.ts.Unit Tests:
Add unit tests in
tests/unit/ReportUtilsTest.ts:getAvailableReportFieldsexcludes an unsettled report's list field when the field is deleted frompolicyReportFieldsand has no value (value: '').getAvailableReportFieldsretains a deleted list field if it has a populated historical value (value: 'Option 1').What alternative solutions did you explore? (Optional)
Passing
policyFieldListtoshouldHideSingleReportField:Modifying
shouldHideSingleReportField(reportField, policyFieldList)and checking!policyFieldList?.[fieldKey] && !reportField.value.Drawback: This modifies the function signature and requires updating all 3 caller sites (
MoneyRequestViewReportFields.tsx:150,MoneyReportView.tsx:132, andReportUtils.ts:10718), introducing unnecessary code duplication across components. Handling it at the source ingetAvailableReportFieldsis far cleaner and adheres strictly to DRY principles.Syncing
disabledOptionsto all report snapshots upon policy updates:Drawback: Modifying all active report snapshots on the client or backend whenever an admin toggles a list option in workspace settings would be expensive, non-performant, and out-of-scope for the App repository.
Upwork Proposal / Cover Letter (For Upwork Submission)
Hi,
I have thoroughly investigated Expensify issue #103935 ("Report fields - Disabled list type report field appears on report after deletion") and identified the exact root cause in
src/libs/ReportUtils.ts.Summary of Root Cause & Fix:
policy.fieldList[fieldKey].disabledOptionsis updated in Onyx, while the report's snapshot retains an un-updated state.policyReportFieldbecomesundefined. IngetAvailableReportFields, falling back to the report copy results indisabledOptionsbeingundefined. Consequently,hasEnabledListValueevaluates!undefinedtotrue, causingshouldHideSingleReportFieldto returnfalseand unexpectedly unhide the deleted list field.getAvailableReportFieldsinReportUtils.ts, ensuring that deleted list fields without a selected value (!field.value) are excluded from unsettled reports, without requiring multi-file prop-drilling or breaking caller signatures.My full technical proposal and unit test plan are detailed on the GitHub issue:
#103935
GitHub Username: janiya2
Expensify Email: janithad2@gmail.com
Upwork Profile: https://www.upwork.com/freelancers/janithad
Ready to implement the PR and automated unit tests immediately upon assignment.
Best regards,
Janitha Dilshan
melvin-bot commentedon Oct 13, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
jloa-dev commentedon Oct 13, 2026
Proposal
Please re-state the problem that we are trying to solve in this issue.
When a list-type report field has all of its options disabled on a workspace, it is correctly hidden on unsettled expense reports. However, once the report field is deleted from the workspace settings, the disabled field unexpectedly reappears on the unsettled report with an empty value.
What is the root cause of that problem?
The root cause lies in how
src/libs/ReportUtils.ts:getAvailableReportFieldsreconciles policy field definitions with the report's snapshot:Option Disabling Updates Policy Only:
When an admin disables all list options in Workspace Settings > Report Fields, Onyx updates
policy.fieldList[fieldKey].disabledOptionsto[true, true, ...]. Existing report snapshots (report.fieldList) are not mutated, soreport.fieldList[fieldKey].disabledOptionsremainsundefined.Snapshot Overlay in
getAvailableReportFields(lines 5190–5203):policyReportFieldis present, sodisabledOptionsis overlaid ontofield.hasEnabledListValue(reportField)(lines 14424–14426):policyReportField.disabledOptionsaretrue,hasEnabledListValuereturnsfalse.shouldHideSingleReportFieldevaluates!hasEnableOptiontotrueand hides the field.Regression Upon Deletion:
policyReportFieldsno longer contains the field (policyReportField === undefined).getAvailableReportFields, the spread...(policyReportField ? ... : {})evaluates to{}.field.disabledOptionsreverts to the report snapshot's local value (undefined).hasEnabledListValue,!reportField.disabledOptions.at(index)evaluates!undefinedtotrue, mistakenly considering every option enabled.shouldHideSingleReportFieldnow returnsfalse(do not hide), causing the deleted, unselectable list field to resurrect on the unsettled report.What changes do you think we should make in order to solve the problem?
In
src/libs/ReportUtils.ts:Cleanly Filter Unpopulated Deleted Fields in
getAvailableReportFields:For unsettled reports, a report field that was completely deleted from the workspace policy (
!policyReportField) and has no entered value (!field.value?.trim()) cannot have options selected or edited, and should not be displayed:Preserve Historical Values on Settled / Populated Reports:
field.valueis present),!field.valueisfalse, so the field is preserved in the list for auditability and historical record.reportIsSettled === true), lines 5181–5183 already returnreportFieldsdirectly.What alternative solutions did you explore? (Optional)
policyFieldListintoshouldHideSingleReportField: Requires changing signatures acrossMoneyRequestViewReportFields.tsx,MoneyReportView.tsx, andReportUtils.ts. Resolving it centrally at the source ingetAvailableReportFieldsadheres to DRY principles and eliminates redundant prop forwarding.Test Plan
Automated Unit Tests (
tests/unit/ReportUtilsTest.ts):report.fieldListthat is missing frompolicyReportFields.field.valueis empty, assert thatgetAvailableReportFieldsreturns an array excluding the deleted field.field.valueis'Option 1', assert thatgetAvailableReportFieldsretains the deleted field with its value intact.reportIsSettledistrue, assert that all snapshot fields continue to be returned.Manual QA Steps:
Contributor details
Your Expensify account email: jloa.dev@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0163955a2ba22190aa
melvin-bot commentedon Oct 13, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
camesenin commentedon Oct 13, 2026
Contributor details
Your Expensify account email: camesenin@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~0170d6b02e9440e604
Proposal
Please re-state the problem that we are trying to solve in this issue.
When a list type report field has all of its options disabled in Workspace Settings, it is correctly hidden on unsettled expense reports. However, if the workspace admin subsequently deletes that list report field from the workspace, the field unexpectedly reappears on the report with a delete (trash) button.
What is the root cause of that problem?
This is a regression introduced by PR #102142 ("Restore: Show up to three report fields per row and edit them inline"), which added inline delete buttons for report fields that were deleted from the workspace so that historical entered values wouldn't be locked without an editor.
policy.fieldList[fieldKey].disabledOptionsin Onyx. The report snapshot (report.fieldList) is not updated across all reports.src/libs/ReportUtils.ts(getAvailableReportFields),disabledOptionsis merged onto the report's copy:hasEnabledListValue(reportField):policyReportField.disabledOptions,hasEnabledListValuereturnsfalse.shouldHideSingleReportField:!hasEnableOptionevaluates totrue, and the field is correctly hidden.policyReportFieldbecomesundefined.getAvailableReportFields,reportField.disabledOptionsis taken fromreport.fieldList, wheredisabledOptionsisundefined(or empty).hasEnabledListValue,!reportField.disabledOptions.at(index)evaluates!undefinedtotrue, erroneously assuming all options are enabled.shouldHideSingleReportFieldreturnsfalse(do not hide).MoneyRequestViewReportFields.tsx,isFieldDeletable: field.fieldID !== CONST.REPORT_FIELD_TITLE_FIELD_ID && !isFieldOnPolicy && isEditableevaluates totrue, resurrecting the deleted list field with an inline delete button.What changes do you think we should make in order to solve the problem?
The fix should be handled centrally in
src/libs/ReportUtils.tsinsidegetAvailableReportFields.When a field is present in
report.fieldList(field) but no longer exists on the policy (!policyReportField), if it is a list type field that has no selected value (!field.value), it has no valid selectable options and must not be resurrected on unsettled reports:Why this is the cleanest solution:
field.value), it is retained and rendered with the trash button so the historical value is not lost, strictly fulfilling PR Restore: Show up to three report fields per row and edit them inline #102142's intended behavior.MoneyRequestViewReportFields.tsx,MoneyReportView.tsx, orhasFieldViolations.Unit Tests:
Add unit test coverage in
tests/unit/ReportUtilsTest.ts:getAvailableReportFieldsexcludes an unsettled report's list field when the field is deleted frompolicyReportFieldsand has no value (value: '').getAvailableReportFieldsretains a deleted list field if it has a populated historical value (value: 'Option 1').What alternative solutions did you explore? (Optional)
policyFieldListtoshouldHideSingleReportField:Modifying
shouldHideSingleReportField(reportField, policyFieldList)and checking!policyFieldList?.[fieldKey] && !reportField.value.Drawback: Modifies the function signature across multiple call sites (
MoneyRequestViewReportFields.tsx,MoneyReportView.tsx, andReportUtils.ts), violating DRY. Handling it at the source ingetAvailableReportFieldsis far cleaner.disabledOptionsacross all report snapshots on policy updates:Drawback: Updating all active reports in Onyx when options are toggled would introduce heavy performance overhead and network operations.
melvin-bot commentedon Oct 13, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
khizzcr7 commentedon Oct 14, 2026
Hi, I'd like to work on this.
Root cause:
getAvailableReportFieldsinsrc/libs/ReportUtils.tsreconciles the report's snapshot of a report field against the workspace's live definition, copyingdisabledOptionsfrom the workspace field onto the report's copy only while the workspace field still exists. After the field is deleted from Workspace Settings,policyReportFieldis undefined, so the reconciliation falls back to the report's stale snapshot where all options read as enabled, and the field reappears on the report.Fix approach:
getAvailableReportFields, when the workspace-side field definition is missing (deleted) but a report-side snapshot exists, preserve the previously applied disabled/hidden state instead of resetting to all-enabled - e.g. carry overdisabledOptionsfrom the last known snapshot or exclude the field when it was hidden before deletion.Verification: (1) Follow the exact repro: create list field, disable all options, confirm hidden on report, delete the field, confirm it stays hidden on the report. (2) Regression: a non-disabled list field that is deleted behaves the same as before. (3) Existing report-fields unit tests still pass.
khizzcr7 commentedon Oct 14, 2026
Contributor details
Your Expensify account email: khizar.akram.ka@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~01c5c6bc0e7d162f7b
melvin-bot commentedon Oct 14, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
GhulamMustufa commentedon Oct 14, 2026
Contributor details
Your Expensify account email: ghulammustafa.mf@gmail.com
Upwork Profile Link: https://www.upwork.com/freelancers/~01f39a67fa921946bd
Proposal
What is the root cause of that problem?
When report fields are configured in Workspace Settings, option states (such as
disabledOptions) are stored on the policy's field definition in Onyx (policy.fieldList), while individual reports retain a local snapshot (report.fieldList) from when the report was created.While the list field exists on the policy:
Inside
getAvailableReportFieldsinsrc/libs/ReportUtils.ts, the policy's livedisabledOptionsarray is merged onto the report field snapshot. When all options are disabled,hasEnabledListValuechecksdisabledOptionsand returnsfalse, soshouldHideSingleReportFieldcorrectly hides the field on expense reports.Once the list field is deleted from the workspace:
policyReportFieldbecomesundefinedingetAvailableReportFields. The function falls back to the report's snapshot inreport.fieldList, wheredisabledOptionsis empty or undefined.In
hasEnabledListValue, checking indices against an empty or undefineddisabledOptionsarray evaluates as enabled (!undefined === true). Consequently,shouldHideSingleReportFieldreturnsfalse, causing the deleted field to unexpectedly reappear on the report with an inline delete button.What changes do you think we should make in order to solve the problem?
In
src/libs/ReportUtils.tswithingetAvailableReportFields:When constructing the list of available fields for an unsettled report, check if a field originates from the report's snapshot but no longer exists on the policy (
!policyReportField). If the field is a list type (CONST.REPORT_FIELD_TYPES.LIST) and has no entered value (!field.value), omit it from the returned available fields.This ensures:
reportFields.We will also add unit tests in
tests/unit/ReportUtilsTest.ts:getAvailableReportFieldsomits a list-type field that was deleted from the policy when it has no value.getAvailableReportFieldsretains a deleted list-type field if a value was previously saved.What alternative solutions did you explore? (Optional)
shouldHideSingleReportFieldacross call sites to inspect whether the field exists on the policy. This was rejected because it requires prop-drilling policy fields through multiple components (MoneyRequestViewReportFields,MoneyReportView, and violation utilities), whereas handling it centrally ingetAvailableReportFieldssolves the problem cleanly at the data source.melvin-bot commentedon Oct 14, 2026
✅ Contributor details stored successfully. Thank you for contributing to Expensify!
emkhalid commentedon Oct 14, 2026
@mkhutornyi I was able to reproduce this and identified why MelvinBot's 4-file proposal is unnecessarily invasive:
getAvailableReportFieldsclaiming it affects report title logic. In reality, report title logic explicitly filters forisReportFieldOfTypeTitle(type: 'text'), which is never alistfield. Title logic is completely unaffected.policy.fieldListis updated in Onyx. Once deleted from the policy,policyReportFieldbecomesundefinedingetAvailableReportFields. The fallback to the report's snapshot leavesdisabledOptionsundefined, whichhasEnabledListValuetreats as enabled, unhiding the field.I have a clean 1-file fix directly in
getAvailableReportFields(ReportUtils.ts) that excludes deleted list fields without a value on unsettled reports. It solves the issue centrally at the source without prop-drillingpolicyFieldListacross 4 files or changing function signatures.Repro Demo:
reproduceDemoReport.webm
Fix Demo:
fixDemoReport.webm
Let me know if you'd like me to submit the full proposal!
GhulamMustufa commentedon Oct 15, 2026
@mkhutornyi Thanks a lot @emkhalid for testing this and sharing the demo videos!
It's great to see confirmation that handling this in
getAvailableReportFields(as outlined in the proposal above) resolves the issue cleanly in a single file without affecting report titles.Looking forward to your review @mkhutornyi, and happy to get started on the PR and unit tests whenever you're ready!
melvin-bot commentedon Oct 16, 2026
@mkhutornyi Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!