From 352d7dda55ea73a5828fbea6273847cf71412a8a Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Wed, 5 Aug 2026 17:41:57 +0200 Subject: [PATCH] fix(front): apply advanced filter value and operand edits after a fresh page load (#23819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem On a freshly loaded page (e.g. opening a saved view with an advanced filter), editing an advanced filter rule silently fails: - Toggling a record in a relation value dropdown (e.g. Account Owner \`Is Me\` → adding a workspace member) does nothing: the checkbox does not stick and the filter is never updated. - Changing the operand (e.g. \`Is\` → \`Is not\`) is also a no-op. The edits only work in the session where the rule was just created, which is why this slips through manual testing of new filters. Found while QAing #23718: the new runtime-computed relation chip made the stale value visible enough to notice the edit was never applied. ## Root cause The object-filter-dropdown component states for an advanced filter row live under the row's instance id (\`advanced-filter-\`, provided by \`AdvancedFilterRecordFilterRow\`). They are hydrated by \`useSetRecordFilterUsedInAdvancedFilterDropdownRow\` when a rule is created — but never on a later page load. \`AdvancedFilterValueInput\` did write \`objectFilterDropdownCurrentRecordFilter\` & co on dropdown open, but under a different instance id (\`advanced-filter-view-filter-value-input-\`) that no dropdown content ever reads — dead writes. So after a reload, \`selectedOperandInDropdown\` is undefined in the instance the dropdown reads, and \`ObjectFilterDropdownRecordSelect.handleMultipleRecordSelectChange\` (gated on it) silently drops the selection. Same story for \`useApplyObjectFilterDropdownOperand\`, which sees no current record filter and never upserts. ## Fix - \`useSetRecordFilterUsedInAdvancedFilterDropdownRow\` now also hydrates \`subFieldNameUsedInDropdown\` and \`relationTargetFieldMetadataIdUsedInDropdown\`, mirroring \`useSetEditableFilterChipDropdownStates\` (the regular filter chip flow, which does not have this bug). - \`AdvancedFilterValueInput\` calls it on value-dropdown open instead of the phantom-instance writes, and its search-input/subFieldName states now target the row instance actually read by the dropdown content. - \`AdvancedFilterRecordFilterOperandSelectContent\` hydrates the same states on operand-dropdown open. ## Test Verified locally against a seeded workspace, on a saved view \`Account Owner Is Me\` reloaded in a fresh session: - Adding a member in the value dropdown now applies immediately: chip updates to \`Me, Aaron Munoz\`, results re-query, Update view appears. - Unchecking \`Me\` leaves \`Aaron Munoz\` with the record-name chip and the filter applied. - Changing the operand to \`Is not\` applies (count flipped from owned-by-me to the complement). - Regular (non-advanced) filter chips unchanged. Ran \`lint:diff-with-main\`, \`typecheck\` and the advanced-filter jest suites. Review in cubic --- ...FilterRecordFilterOperandSelectContent.tsx | 9 +++++ .../components/AdvancedFilterValueInput.tsx | 38 ++++++------------- ...rdFilterUsedInAdvancedFilterDropdownRow.ts | 16 ++++++++ 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent.tsx index b75f4dd108..8b97abd4c5 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterRecordFilterOperandSelectContent.tsx @@ -1,4 +1,5 @@ import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset'; +import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow'; import { AdvancedFilterContext } from '@/object-record/advanced-filter/states/context/AdvancedFilterContext'; import { useApplyObjectFilterDropdownOperand } from '@/object-record/object-filter-dropdown/hooks/useApplyObjectFilterDropdownOperand'; @@ -42,12 +43,19 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({ const { applyObjectFilterDropdownOperand } = useApplyObjectFilterDropdownOperand(); + const { setRecordFilterUsedInAdvancedFilterDropdownRow } = + useSetRecordFilterUsedInAdvancedFilterDropdownRow(); + const handleOperandChange = (operand: ViewFilterOperand) => { closeDropdown(dropdownId); applyObjectFilterDropdownOperand(operand); }; + const handleDropdownOpen = () => { + setRecordFilterUsedInAdvancedFilterDropdownRow(filter); + }; + const selectedItemId = useAtomComponentStateValue( selectedItemIdComponentState, dropdownId, @@ -111,6 +119,7 @@ export const AdvancedFilterRecordFilterOperandSelectContent = ({ } dropdownOffset={DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET} dropdownPlacement="bottom-start" + onOpen={handleDropdownOpen} /> ); }; diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx index 90efd0e171..ea91f1feb8 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/components/AdvancedFilterValueInput.tsx @@ -2,11 +2,10 @@ import { AdvancedFilterDropdownFilterInput } from '@/object-record/advanced-filt import { AdvancedFilterDropdownTextInput } from '@/object-record/advanced-filter/components/AdvancedFilterDropdownTextInput'; import { AdvancedFilterValueInputDropdownButtonClickableSelect } from '@/object-record/advanced-filter/components/AdvancedFilterValueInputDropdownButtonClickableSelect'; import { DEFAULT_ADVANCED_FILTER_DROPDOWN_OFFSET } from '@/object-record/advanced-filter/constants/DefaultAdvancedFilterDropdownOffset'; +import { useSetRecordFilterUsedInAdvancedFilterDropdownRow } from '@/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow'; +import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId'; import { shouldShowFilterTextInput } from '@/object-record/advanced-filter/utils/shouldShowFilterTextInput'; -import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; -import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState'; import { objectFilterDropdownSearchInputComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownSearchInputComponentState'; -import { relationTargetFieldMetadataIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/relationTargetFieldMetadataIdUsedInDropdownComponentState'; import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState'; import { configurableViewFilterOperands } from '@/object-record/object-filter-dropdown/utils/configurableViewFilterOperands'; import { currentRecordFiltersComponentState } from '@/object-record/record-filter/states/currentRecordFiltersComponentState'; @@ -31,13 +30,16 @@ export const AdvancedFilterValueInput = ({ }: AdvancedFilterValueInputProps) => { const dropdownId = `advanced-filter-view-filter-value-input-${recordFilterId}`; + const objectFilterDropdownInstanceId = + getAdvancedFilterObjectFilterDropdownComponentInstanceId(recordFilterId); + const currentRecordFilters = useAtomComponentStateValue( currentRecordFiltersComponentState, ); const subFieldNameUsedInDropdown = useAtomComponentStateValue( subFieldNameUsedInDropdownComponentState, - dropdownId, + objectFilterDropdownInstanceId, ); const recordFilter = currentRecordFilters.find( @@ -48,24 +50,11 @@ export const AdvancedFilterValueInput = ({ const setObjectFilterDropdownSearchInput = useSetAtomComponentState( objectFilterDropdownSearchInputComponentState, - dropdownId, + objectFilterDropdownInstanceId, ); - const setFieldMetadataItemIdUsedInDropdown = useSetAtomComponentState( - fieldMetadataItemIdUsedInDropdownComponentState, - dropdownId, - ); - - const setRelationTargetFieldMetadataIdUsedInDropdown = - useSetAtomComponentState( - relationTargetFieldMetadataIdUsedInDropdownComponentState, - dropdownId, - ); - - const setObjectFilterDropdownCurrentRecordFilter = useSetAtomComponentState( - objectFilterDropdownCurrentRecordFilterComponentState, - dropdownId, - ); + const { setRecordFilterUsedInAdvancedFilterDropdownRow } = + useSetRecordFilterUsedInAdvancedFilterDropdownRow(); const operandHasNoInput = recordFilter && !configurableViewFilterOperands.has(recordFilter.operand); @@ -79,11 +68,7 @@ export const AdvancedFilterValueInput = ({ }; const handleFilterValueDropdownOpen = () => { - setObjectFilterDropdownCurrentRecordFilter(recordFilter); - setFieldMetadataItemIdUsedInDropdown(recordFilter.fieldMetadataId); - setRelationTargetFieldMetadataIdUsedInDropdown( - recordFilter.relationTargetFieldMetadataId ?? null, - ); + setRecordFilterUsedInAdvancedFilterDropdownRow(recordFilter); }; const filterType = recordFilter.type; @@ -95,7 +80,8 @@ export const AdvancedFilterValueInput = ({ const showFilterTextInputInsteadOfDropdown = shouldShowFilterTextInput({ recordFilter, - subFieldNameUsedInDropdown, + subFieldNameUsedInDropdown: + subFieldNameUsedInDropdown ?? recordFilter.subFieldName, }); return ( diff --git a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow.ts b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow.ts index cc1bdd8688..3803cc456a 100644 --- a/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow.ts +++ b/packages/twenty-front/src/modules/object-record/advanced-filter/hooks/useSetRecordFilterUsedInAdvancedFilterDropdownRow.ts @@ -1,7 +1,9 @@ import { getAdvancedFilterObjectFilterDropdownComponentInstanceId } from '@/object-record/advanced-filter/utils/getAdvancedFilterObjectFilterDropdownComponentInstanceId'; import { fieldMetadataItemIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/fieldMetadataItemIdUsedInDropdownComponentState'; import { objectFilterDropdownCurrentRecordFilterComponentState } from '@/object-record/object-filter-dropdown/states/objectFilterDropdownCurrentRecordFilterComponentState'; +import { relationTargetFieldMetadataIdUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/relationTargetFieldMetadataIdUsedInDropdownComponentState'; import { selectedOperandInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/selectedOperandInDropdownComponentState'; +import { subFieldNameUsedInDropdownComponentState } from '@/object-record/object-filter-dropdown/states/subFieldNameUsedInDropdownComponentState'; import { type RecordFilter } from '@/object-record/record-filter/types/RecordFilter'; import { useCallback } from 'react'; import { useStore } from 'jotai'; @@ -35,6 +37,20 @@ export const useSetRecordFilterUsedInAdvancedFilterDropdownRow = () => { }), recordFilter, ); + + store.set( + subFieldNameUsedInDropdownComponentState.atomFamily({ + instanceId: advancedFilterRowObjectFilterDropdownComponentInstanceId, + }), + recordFilter.subFieldName, + ); + + store.set( + relationTargetFieldMetadataIdUsedInDropdownComponentState.atomFamily({ + instanceId: advancedFilterRowObjectFilterDropdownComponentInstanceId, + }), + recordFilter.relationTargetFieldMetadataId ?? null, + ); }, [store], );