Fix deactivated tabs not visible in new tab action (#19811)

## Context
On custom objects, clicking "+ New Tab" on a record page layout never
exposed deactivated tabs for reactivation, even though isActive: false
tabs were correctly returned by the API. Standard objects worked fine.

## Fix
isReactivatableTab gated reactivation on tab.applicationId ===
objectMetadata.applicationId. For custom objects these two ids are
intentionally different.
This check was unnecessary after all, we simply want to check if a tab
is inactive (only non-custom entities can be de-activated) 👍

<img width="694" height="551" alt="Screenshot 2026-04-17 at 18 14 28"
src="https://github.com/user-attachments/assets/42485cb2-8be5-4a55-a311-479ed3226908"
/>
This commit is contained in:
Weiko 2026-04-17 19:10:14 +02:00 committed by GitHub
parent 6095798434
commit a18840f3cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 10 additions and 73 deletions

View file

@ -1,7 +1,6 @@
import { STANDARD_PAGE_LAYOUT_TAB_TITLE_TRANSLATIONS } from '@/page-layout/constants/StandardPageLayoutTabTitleTranslations';
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
import { useIsCurrentObjectCustom } from '@/page-layout/hooks/useIsCurrentObjectCustom';
import { useRecordPageLayoutObjectApplicationId } from '@/page-layout/hooks/useRecordPageLayoutObjectApplicationId';
import { useUpdatePageLayoutTab } from '@/page-layout/hooks/useUpdatePageLayoutTab';
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
import { isReactivatableTab } from '@/page-layout/utils/isReactivatableTab';
@ -39,7 +38,6 @@ export const PageLayoutTabListNewTabDropdownContent = ({
const shouldTranslateTabTitles = !isCustom;
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
const { objectApplicationId } = useRecordPageLayoutObjectApplicationId();
const { updatePageLayoutTab } = useUpdatePageLayoutTab();
const setActiveTabId = useSetAtomComponentState(activeTabIdComponentState);
@ -49,13 +47,8 @@ export const PageLayoutTabListNewTabDropdownContent = ({
const { navigatePageLayoutSidePanel } = useNavigatePageLayoutSidePanel();
const inactiveTabs = useMemo(
() =>
sortTabsByPosition(
currentPageLayout.tabs.filter((tab) =>
isReactivatableTab({ tab, objectApplicationId }),
),
),
[currentPageLayout.tabs, objectApplicationId],
() => sortTabsByPosition(currentPageLayout.tabs.filter(isReactivatableTab)),
[currentPageLayout.tabs],
);
const handleCreateEmptyTab = useCallback(() => {

View file

@ -1,7 +1,6 @@
import { useCreatePageLayoutTab } from '@/page-layout/hooks/useCreatePageLayoutTab';
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode';
import { useRecordPageLayoutObjectApplicationId } from '@/page-layout/hooks/useRecordPageLayoutObjectApplicationId';
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
import { type PageLayoutAddTabStrategy } from '@/page-layout/types/PageLayoutAddTabStrategy';
import { isReactivatableTab } from '@/page-layout/utils/isReactivatableTab';
@ -25,7 +24,6 @@ export const usePageLayoutAddTabStrategy = ({
}): PageLayoutAddTabStrategy | undefined => {
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
const isPageLayoutInEditMode = useIsPageLayoutInEditMode();
const { objectApplicationId } = useRecordPageLayoutObjectApplicationId();
const isRecordPageGlobalEditionEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_GLOBAL_EDITION_ENABLED,
@ -77,9 +75,7 @@ export const usePageLayoutAddTabStrategy = ({
return undefined;
}
const hasInactiveTabs = currentPageLayout.tabs.some((tab) =>
isReactivatableTab({ tab, objectApplicationId }),
);
const hasInactiveTabs = currentPageLayout.tabs.some(isReactivatableTab);
const mode =
currentPageLayout.type === PageLayoutType.RECORD_PAGE && hasInactiveTabs

View file

@ -1,25 +0,0 @@
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { isDefined } from 'twenty-shared/utils';
export const useRecordPageLayoutObjectApplicationId = (): {
objectApplicationId: string | undefined;
} => {
const { currentPageLayout } = useCurrentPageLayoutOrThrow();
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
const objectMetadataId = currentPageLayout.objectMetadataId;
if (!isDefined(objectMetadataId)) {
return { objectApplicationId: undefined };
}
const objectMetadataItem = objectMetadataItems.find(
(item) => item.id === objectMetadataId,
);
return {
objectApplicationId: objectMetadataItem?.applicationId,
};
};

View file

@ -17,35 +17,15 @@ const makeTab = (overrides: Partial<PageLayoutTab> = {}): PageLayoutTab =>
}) as unknown as PageLayoutTab;
describe('isReactivatableTab', () => {
it('should return true when tab is inactive and applicationId matches', () => {
const tab = makeTab({ isActive: false, applicationId: 'app-1' });
it('should return true when tab is inactive', () => {
const tab = makeTab({ isActive: false });
expect(isReactivatableTab({ tab, objectApplicationId: 'app-1' })).toBe(
true,
);
expect(isReactivatableTab(tab)).toBe(true);
});
it('should return false when tab is active', () => {
const tab = makeTab({ isActive: true, applicationId: 'app-1' });
const tab = makeTab({ isActive: true });
expect(isReactivatableTab({ tab, objectApplicationId: 'app-1' })).toBe(
false,
);
});
it('should return false when applicationId does not match', () => {
const tab = makeTab({ isActive: false, applicationId: 'app-1' });
expect(isReactivatableTab({ tab, objectApplicationId: 'app-2' })).toBe(
false,
);
});
it('should return false when objectApplicationId is undefined', () => {
const tab = makeTab({ isActive: false, applicationId: 'app-1' });
expect(isReactivatableTab({ tab, objectApplicationId: undefined })).toBe(
false,
);
expect(isReactivatableTab(tab)).toBe(false);
});
});

View file

@ -1,11 +1,4 @@
import { type PageLayoutTab } from '@/page-layout/types/PageLayoutTab';
export const isReactivatableTab = ({
tab,
objectApplicationId,
}: {
tab: PageLayoutTab;
objectApplicationId: string | undefined;
}): boolean => {
return !tab.isActive && tab.applicationId === objectApplicationId;
};
export const isReactivatableTab = (tab: PageLayoutTab): boolean =>
!tab.isActive;