Compare commits

...

3 Commits

Author SHA1 Message Date
Charles Bochet 9e02573c41 fix: make LayoutRenderingContext optional in FrontComponentRenderer
The settings custom tab doesn't operate within a page layout, so forcing
a LayoutRenderingProvider wrapper with an arbitrary layout type is wrong.
Instead, make the context optional in useFrontComponentExecutionContext
so consumers without a page layout simply get recordId: null.

Made-with: Cursor
2026-04-15 19:04:50 +02:00
Charles Bochet 386e2af3c5 Merge branch 'main' into fix/settings-custom-tab-layout-context 2026-04-15 18:50:40 +02:00
Lazare-42 3b530d0e79 fix: wrap settings custom tab FrontComponentRenderer with LayoutRenderingProvider
FrontComponentRenderer uses useFrontComponentExecutionContext which calls
useLayoutRenderingContext(). Every other render site (side panel, command
menu, record pages) provides this context, but SettingsApplicationCustomTab
was missing it, causing a crash when opening the Custom tab in app settings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-14 12:53:15 +02:00
3 changed files with 29 additions and 6 deletions
@@ -87,7 +87,7 @@ jest.mock('@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState', () => ({
}));
jest.mock('@/ui/layout/contexts/LayoutRenderingContext', () => ({
useLayoutRenderingContext: () => ({
useOptionalLayoutRenderingContext: () => ({
targetRecordIdentifier: mockTargetRecordIdentifier,
}),
}));
@@ -14,7 +14,7 @@ import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { useLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
import { useOptionalLayoutRenderingContext } from '@/ui/layout/contexts/LayoutRenderingContext';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
@@ -123,12 +123,12 @@ export const useFrontComponentExecutionContext = ({
}
};
const { targetRecordIdentifier } = useLayoutRenderingContext();
const layoutRenderingContext = useOptionalLayoutRenderingContext();
const executionContext: FrontComponentExecutionContext = {
frontComponentId,
userId: currentUser?.id ?? null,
recordId: targetRecordIdentifier?.id ?? null,
recordId: layoutRenderingContext?.targetRecordIdentifier?.id ?? null,
};
const unmountFrontComponent: FrontComponentHostCommunicationApi['unmountFrontComponent'] =
@@ -1,3 +1,5 @@
import React, { useContext } from 'react';
import { type PageLayoutType } from '~/generated-metadata/graphql';
import { createRequiredContext } from '~/utils/createRequiredContext';
import { type TargetRecordIdentifier } from './TargetRecordIdentifier';
@@ -13,5 +15,26 @@ export type LayoutRenderingContextType = {
isInSidePanel: boolean;
};
export const [LayoutRenderingProvider, useLayoutRenderingContext] =
createRequiredContext<LayoutRenderingContextType>('LayoutRenderingContext');
const LayoutRenderingContext = React.createContext<
LayoutRenderingContextType | undefined
>(undefined);
export const LayoutRenderingProvider = LayoutRenderingContext.Provider;
export const useLayoutRenderingContext = (): LayoutRenderingContextType => {
const context = useContext(LayoutRenderingContext);
if (context === undefined) {
throw new Error(
'LayoutRenderingContext not found. Please wrap your component tree with <LayoutRenderingProvider>.',
);
}
return context;
};
export const useOptionalLayoutRenderingContext = ():
| LayoutRenderingContextType
| undefined => {
return useContext(LayoutRenderingContext);
};