Reset Tab Page Layout (#19453)

## Context
- Add "Reset to default" for page layout tabs and widgets — backend
mutation resets overrides, reactivates deactivated children, and deletes
custom children
- Fix override write bug where mutating an overridable property (e.g.
widget title) on a standard-app entity incorrectly overwrote the base
column instead of writing to the overrides JSONB —
PageLayoutUpdateService now uses resolveFlatEntityOverridableProperties
for accurate diffing and routes properties through
sanitizeOverridableEntityInput
- Deprecate isOverridden - We need more time to think about this
feature. Currently this adds too much complexity for a very small
benefit



https://github.com/user-attachments/assets/a84546c8-1e15-4d9e-a489-0825cf8b8ed2
This commit is contained in:
Weiko
2026-04-08 18:43:57 +02:00
committed by GitHub
parent 2267c56f15
commit 238018dc7b
94 changed files with 1000 additions and 619 deletions
@@ -0,0 +1,95 @@
import { useApolloClient } from '@apollo/client/react';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { FindOnePageLayoutDocument } from '~/generated-metadata/graphql';
import { useExitLayoutCustomizationMode } from '@/layout-customization/hooks/useExitLayoutCustomizationMode';
import { useInvalidateMetadataStore } from '@/metadata-store/hooks/useInvalidateMetadataStore';
import { useSetIsPageLayoutInEditMode } from '@/page-layout/hooks/useSetIsPageLayoutInEditMode';
import { PageLayoutComponentInstanceContext } from '@/page-layout/states/contexts/PageLayoutComponentInstanceContext';
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';
import { useAtomComponentStateCallbackState } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateCallbackState';
export const useRefreshPageLayoutAfterReset = (
pageLayoutIdFromProps: string,
) => {
const pageLayoutId = useAvailableComponentInstanceIdOrThrow(
PageLayoutComponentInstanceContext,
pageLayoutIdFromProps,
);
const client = useApolloClient();
const store = useStore();
const { setIsPageLayoutInEditMode } =
useSetIsPageLayoutInEditMode(pageLayoutId);
const { exitLayoutCustomizationMode } = useExitLayoutCustomizationMode();
const { invalidateMetadataStore } = useInvalidateMetadataStore();
const pageLayoutPersistedState = useAtomComponentStateCallbackState(
pageLayoutPersistedComponentState,
pageLayoutId,
);
const pageLayoutDraftState = useAtomComponentStateCallbackState(
pageLayoutDraftComponentState,
pageLayoutId,
);
const pageLayoutCurrentLayoutsState = useAtomComponentStateCallbackState(
pageLayoutCurrentLayoutsComponentState,
pageLayoutId,
);
const refreshPageLayoutAfterReset = useCallback(
async (collectAffectedViewIds: (layout: PageLayout) => Set<string>) => {
const { data } = await client.query({
query: FindOnePageLayoutDocument,
variables: { id: pageLayoutId },
fetchPolicy: 'network-only',
});
let affectedViewIds = new Set<string>();
if (isDefined(data?.getPageLayout)) {
const freshLayout = transformPageLayout(data.getPageLayout);
affectedViewIds = collectAffectedViewIds(freshLayout);
store.set(pageLayoutPersistedState, freshLayout);
store.set(pageLayoutDraftState, toDraftPageLayout(freshLayout));
store.set(
pageLayoutCurrentLayoutsState,
convertPageLayoutToTabLayouts(freshLayout),
);
}
setIsPageLayoutInEditMode(false);
exitLayoutCustomizationMode();
evictViewMetadataForViewIds(store, affectedViewIds);
invalidateMetadataStore();
},
[
client,
pageLayoutId,
store,
pageLayoutPersistedState,
pageLayoutDraftState,
pageLayoutCurrentLayoutsState,
setIsPageLayoutInEditMode,
exitLayoutCustomizationMode,
invalidateMetadataStore,
],
);
return { refreshPageLayoutAfterReset };
};