From 56d6e13b5dedb595d68672c7b0ebf4d37b0708fc Mon Sep 17 00:00:00 2001 From: Weiko Date: Wed, 15 Apr 2026 18:08:24 +0200 Subject: [PATCH] Fix fields widget flash during reset (#19726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary - Remove the evictViewMetadataForViewIds step from the page-layout reset flow. It synchronously cleared viewFields/viewFieldGroups rows from the metadata store, leaving a window where useFieldsWidgetGroups saw a view with no fields and fell back to buildDefaultFieldsWidgetGroups, briefly rendering a synthetic "General" + "Other" layout before the real reset defaults arrived. - invalidateMetadataStore() alone is sufficient: it marks the collections stale and triggers MinimalMetadataLoadEffect to refetch, which replaces current atomically. The UI now transitions old-layout → new-default with no synthetic flash. - Simplified refreshPageLayoutAfterReset to no longer take a collectAffectedViewIds callback, and updated both tab/widget reset call sites plus ObjectLayout.tsx accordingly. - Deleted the now-unused evictViewMetadataForViewIds and collectViewIdsFromWidgets utils. --- .../hooks/useRefreshPageLayoutAfterReset.ts | 84 ++++++++----------- .../hooks/useResetPageLayoutTabToDefault.ts | 7 +- .../hooks/useResetPageLayoutToDefault.ts | 17 +--- .../useResetPageLayoutWidgetToDefault.ts | 9 +- .../utils/collectViewIdsFromWidgets.ts | 20 ----- .../utils/evictViewMetadataForViewIds.ts | 31 ------- .../components/tabs/ObjectLayout.tsx | 1 - 7 files changed, 40 insertions(+), 129 deletions(-) delete mode 100644 packages/twenty-front/src/modules/page-layout/utils/collectViewIdsFromWidgets.ts delete mode 100644 packages/twenty-front/src/modules/page-layout/utils/evictViewMetadataForViewIds.ts diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useRefreshPageLayoutAfterReset.ts b/packages/twenty-front/src/modules/page-layout/hooks/useRefreshPageLayoutAfterReset.ts index ec5aca1523..4e59352b12 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useRefreshPageLayoutAfterReset.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useRefreshPageLayoutAfterReset.ts @@ -15,9 +15,7 @@ import { hasInitializedFieldsWidgetGroupsDraftComponentState } from '@/page-layo import { pageLayoutCurrentLayoutsComponentState } from '@/page-layout/states/pageLayoutCurrentLayoutsComponentState'; import { pageLayoutDraftComponentState } from '@/page-layout/states/pageLayoutDraftComponentState'; import { pageLayoutPersistedComponentState } from '@/page-layout/states/pageLayoutPersistedComponentState'; -import { type PageLayout } from '@/page-layout/types/PageLayout'; import { convertPageLayoutToTabLayouts } from '@/page-layout/utils/convertPageLayoutToTabLayouts'; -import { evictViewMetadataForViewIds } from '@/page-layout/utils/evictViewMetadataForViewIds'; import { toDraftPageLayout } from '@/page-layout/utils/toDraftPageLayout'; import { transformPageLayout } from '@/page-layout/utils/transformPageLayout'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; @@ -76,55 +74,47 @@ export const useRefreshPageLayoutAfterReset = ( pageLayoutId, ); - const refreshPageLayoutAfterReset = useCallback( - async (collectAffectedViewIds: (layout: PageLayout) => Set) => { - const { data } = await client.query({ - query: FindOnePageLayoutDocument, - variables: { id: pageLayoutId }, - fetchPolicy: 'network-only', - }); + const refreshPageLayoutAfterReset = useCallback(async () => { + const { data } = await client.query({ + query: FindOnePageLayoutDocument, + variables: { id: pageLayoutId }, + fetchPolicy: 'network-only', + }); - let affectedViewIds = new Set(); + if (isDefined(data?.getPageLayout)) { + const freshLayout = transformPageLayout(data.getPageLayout); - if (isDefined(data?.getPageLayout)) { - const freshLayout = transformPageLayout(data.getPageLayout); + store.set(pageLayoutPersistedState, freshLayout); + store.set(pageLayoutDraftState, toDraftPageLayout(freshLayout)); + store.set( + pageLayoutCurrentLayoutsState, + convertPageLayoutToTabLayouts(freshLayout), + ); + } - affectedViewIds = collectAffectedViewIds(freshLayout); + store.set(fieldsWidgetGroupsDraftState, {}); + store.set(fieldsWidgetUngroupedFieldsDraftState, {}); + store.set(fieldsWidgetEditorModeDraftState, {}); + store.set(hasInitializedFieldsWidgetGroupsDraftState, {}); - store.set(pageLayoutPersistedState, freshLayout); - store.set(pageLayoutDraftState, toDraftPageLayout(freshLayout)); - store.set( - pageLayoutCurrentLayoutsState, - convertPageLayoutToTabLayouts(freshLayout), - ); - } - - store.set(fieldsWidgetGroupsDraftState, {}); - store.set(fieldsWidgetUngroupedFieldsDraftState, {}); - store.set(fieldsWidgetEditorModeDraftState, {}); - store.set(hasInitializedFieldsWidgetGroupsDraftState, {}); - - setIsPageLayoutInEditMode(false); - exitLayoutCustomizationMode(); - evictViewMetadataForViewIds(store, affectedViewIds); - invalidateMetadataStore(); - }, - [ - client, - pageLayoutId, - store, - pageLayoutPersistedState, - pageLayoutDraftState, - pageLayoutCurrentLayoutsState, - fieldsWidgetGroupsDraftState, - fieldsWidgetUngroupedFieldsDraftState, - fieldsWidgetEditorModeDraftState, - hasInitializedFieldsWidgetGroupsDraftState, - setIsPageLayoutInEditMode, - exitLayoutCustomizationMode, - invalidateMetadataStore, - ], - ); + setIsPageLayoutInEditMode(false); + exitLayoutCustomizationMode(); + invalidateMetadataStore(); + }, [ + client, + pageLayoutId, + store, + pageLayoutPersistedState, + pageLayoutDraftState, + pageLayoutCurrentLayoutsState, + fieldsWidgetGroupsDraftState, + fieldsWidgetUngroupedFieldsDraftState, + fieldsWidgetEditorModeDraftState, + hasInitializedFieldsWidgetGroupsDraftState, + setIsPageLayoutInEditMode, + exitLayoutCustomizationMode, + invalidateMetadataStore, + ]); return { refreshPageLayoutAfterReset }; }; diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutTabToDefault.ts b/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutTabToDefault.ts index 4bce6bc2bd..bd2b989b44 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutTabToDefault.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutTabToDefault.ts @@ -7,7 +7,6 @@ import { CrudOperationType } from 'twenty-shared/types'; import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; import { RESET_PAGE_LAYOUT_TAB_TO_DEFAULT } from '@/page-layout/graphql/mutations/resetPageLayoutTabToDefault'; import { useRefreshPageLayoutAfterReset } from '@/page-layout/hooks/useRefreshPageLayoutAfterReset'; -import { collectViewIdsFromWidgets } from '@/page-layout/utils/collectViewIdsFromWidgets'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; export const useResetPageLayoutTabToDefault = ( @@ -24,11 +23,7 @@ export const useResetPageLayoutTabToDefault = ( async (tabId: string) => { try { await resetMutation({ variables: { id: tabId } }); - await refreshPageLayoutAfterReset((layout) => - collectViewIdsFromWidgets( - layout.tabs.find((tab) => tab.id === tabId)?.widgets ?? [], - ), - ); + await refreshPageLayoutAfterReset(); } catch (error) { if (CombinedGraphQLErrors.is(error)) { handleMetadataError(error, { diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutToDefault.ts b/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutToDefault.ts index 8f4965698d..e7a6d59435 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutToDefault.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutToDefault.ts @@ -10,9 +10,6 @@ import { useInvalidateMetadataStore } from '@/metadata-store/hooks/useInvalidate import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; import { RESET_PAGE_LAYOUT_TO_DEFAULT } from '@/page-layout/graphql/mutations/resetPageLayoutToDefault'; import { pageLayoutIsInitializedComponentState } from '@/page-layout/states/pageLayoutIsInitializedComponentState'; -import { type PageLayout } from '@/page-layout/types/PageLayout'; -import { collectViewIdsFromWidgets } from '@/page-layout/utils/collectViewIdsFromWidgets'; -import { evictViewMetadataForViewIds } from '@/page-layout/utils/evictViewMetadataForViewIds'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; export const useResetPageLayoutToDefault = () => { @@ -23,22 +20,10 @@ export const useResetPageLayoutToDefault = () => { const store = useStore(); const resetPageLayoutToDefault = useCallback( - async ({ - pageLayoutId, - pageLayout, - }: { - pageLayoutId: string; - pageLayout: PageLayout; - }) => { - const preResetViewIds = collectViewIdsFromWidgets( - pageLayout.tabs.flatMap((tab) => tab.widgets), - ); - + async ({ pageLayoutId }: { pageLayoutId: string }) => { try { await resetMutation({ variables: { id: pageLayoutId } }); - evictViewMetadataForViewIds(store, preResetViewIds); - if (isDefined(pageLayoutId)) { store.set( pageLayoutIsInitializedComponentState.atomFamily({ diff --git a/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutWidgetToDefault.ts b/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutWidgetToDefault.ts index e6f64ec175..9ef2864e1b 100644 --- a/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutWidgetToDefault.ts +++ b/packages/twenty-front/src/modules/page-layout/hooks/useResetPageLayoutWidgetToDefault.ts @@ -7,7 +7,6 @@ import { ResetPageLayoutWidgetToDefaultDocument } from '~/generated-metadata/gra import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler'; import { useRefreshPageLayoutAfterReset } from '@/page-layout/hooks/useRefreshPageLayoutAfterReset'; -import { collectViewIdsFromWidgets } from '@/page-layout/utils/collectViewIdsFromWidgets'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; export const useResetPageLayoutWidgetToDefault = ( @@ -24,13 +23,7 @@ export const useResetPageLayoutWidgetToDefault = ( async (widgetId: string) => { try { await resetMutation({ variables: { id: widgetId } }); - await refreshPageLayoutAfterReset((layout) => - collectViewIdsFromWidgets( - layout.tabs - .flatMap((tab) => tab.widgets) - .filter((widget) => widget.id === widgetId), - ), - ); + await refreshPageLayoutAfterReset(); } catch (error) { if (CombinedGraphQLErrors.is(error)) { handleMetadataError(error, { diff --git a/packages/twenty-front/src/modules/page-layout/utils/collectViewIdsFromWidgets.ts b/packages/twenty-front/src/modules/page-layout/utils/collectViewIdsFromWidgets.ts deleted file mode 100644 index 3cdfcd876e..0000000000 --- a/packages/twenty-front/src/modules/page-layout/utils/collectViewIdsFromWidgets.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { isDefined } from 'twenty-shared/utils'; - -import { type PageLayoutWidget } from '@/page-layout/types/PageLayoutWidget'; -import { getWidgetConfigurationViewId } from '@/page-layout/utils/getWidgetConfigurationViewId'; - -export const collectViewIdsFromWidgets = ( - widgets: PageLayoutWidget[], -): Set => { - const viewIds = new Set(); - - for (const widget of widgets) { - const viewId = getWidgetConfigurationViewId(widget.configuration); - - if (isDefined(viewId)) { - viewIds.add(viewId); - } - } - - return viewIds; -}; diff --git a/packages/twenty-front/src/modules/page-layout/utils/evictViewMetadataForViewIds.ts b/packages/twenty-front/src/modules/page-layout/utils/evictViewMetadataForViewIds.ts deleted file mode 100644 index 42cbfa3df7..0000000000 --- a/packages/twenty-front/src/modules/page-layout/utils/evictViewMetadataForViewIds.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { useStore } from 'jotai'; -import { isDefined } from 'twenty-shared/utils'; - -import { - type MetadataEntityKey, - metadataStoreState, -} from '@/metadata-store/states/metadataStoreState'; - -const VIEW_RELATED_METADATA_KEYS: MetadataEntityKey[] = [ - 'viewFields', - 'viewFieldGroups', -]; - -export const evictViewMetadataForViewIds = ( - store: ReturnType, - viewIds: Set, -) => { - if (viewIds.size === 0) { - return; - } - - for (const key of VIEW_RELATED_METADATA_KEYS) { - store.set(metadataStoreState.atomFamily(key), (prev) => ({ - ...prev, - current: (prev.current as { viewId?: string }[]).filter( - (item) => !isDefined(item.viewId) || !viewIds.has(item.viewId), - ), - currentCollectionHash: undefined, - })); - } -}; diff --git a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectLayout.tsx b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectLayout.tsx index b697633d14..9f9b0bcce6 100644 --- a/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectLayout.tsx +++ b/packages/twenty-front/src/modules/settings/data-model/object-details/components/tabs/ObjectLayout.tsx @@ -77,7 +77,6 @@ export const ObjectLayout = ({ objectMetadataItem }: ObjectLayoutProps) => { await resetPageLayoutToDefault({ pageLayoutId: pageLayout.id, - pageLayout, }); };