[FRONT COMPONENTS] Introduce conditionalAvailabilityExpression to command menu items (#18319)

## PR Description

- Uses `expr-eval` to enable front components (SDK plugins) to define
conditional availability as declarative expressions.
- Moves shared types and constants to `twenty-shared`
- Introduces a `conditionalAvailabilityExpression` field on
`CommandMenuItemEntity`, allowing command menu items to store an
`expr-eval` compatible expression string that is evaluated against a
CommandMenuContext to determine if the item should be shown.
- Creates an esbuild transform plugin
`conditional-availability-transform-plugin` in `twenty-sdk` that
converts TypeScript conditional availability expressions into
`expr-eval` compatible syntax at build time, so SDK developers can write
natural TS expressions that get transformed to evaluable strings.
- Removes deprecated `forceRegisteredActionsByKey` state and its usage.
- Creates `useCommandMenuContext` hook that builds the full
`CommandMenuContext` object from React state, which is then passed to
`useCommandMenuItemFrontComponentActions` for evaluating conditional
availability expressions.
This commit is contained in:
Raphaël Bosi
2026-03-04 16:33:58 +01:00
committed by GitHub
parent f09a9cc25a
commit b11f77df2a
349 changed files with 1648 additions and 454 deletions
@@ -6,7 +6,6 @@ import { useObjectNavItemColor } from '@/navigation-menu-item/hooks/useObjectNav
import { getStandardObjectIconColor } from '@/navigation-menu-item/utils/getStandardObjectIconColor';
import { type ProcessedNavigationMenuItem } from '@/navigation-menu-item/utils/sortNavigationMenuItems';
import { lastVisitedViewPerObjectMetadataItemState } from '@/navigation/states/lastVisitedViewPerObjectMetadataItemState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { NavigationDrawerItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
import { NavigationDrawerItemsCollapsableContainer } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItemsCollapsableContainer';
@@ -18,7 +17,7 @@ import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomState
import { coreViewsFromObjectMetadataItemFamilySelector } from '@/views/states/selectors/coreViewsFromObjectMetadataItemFamilySelector';
import { ViewKey } from '@/views/types/ViewKey';
import { useLocation } from 'react-router-dom';
import { AppPath } from 'twenty-shared/types';
import { AppPath, CoreObjectNameSingular } from 'twenty-shared/types';
import { getAppPath, isDefined } from 'twenty-shared/utils';
import { Avatar, useIcons } from 'twenty-ui/display';
import { AnimatedExpandableContainer } from 'twenty-ui/layout';
@@ -5,7 +5,7 @@ import { useWorkspaceNavigationMenuItems } from '@/navigation-menu-item/hooks/us
import { NavigationDrawerSectionForObjectMetadataItems } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItems';
import { NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader } from '@/object-metadata/components/NavigationDrawerSectionForObjectMetadataItemsSkeletonLoader';
import { useFilteredObjectMetadataItems } from '@/object-metadata/hooks/useFilteredObjectMetadataItems';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { useIsPrefetchLoading } from '@/prefetch/hooks/useIsPrefetchLoading';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useLingui } from '@lingui/react/macro';
@@ -1,5 +1,5 @@
import { NavigationDrawerItemForObjectMetadataItem } from '@/object-metadata/components/NavigationDrawerItemForObjectMetadataItem';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { getObjectPermissionsForObject } from '@/object-metadata/utils/getObjectPermissionsForObject';
import { useObjectPermissions } from '@/object-record/hooks/useObjectPermissions';
@@ -1,34 +0,0 @@
export enum CoreObjectNameSingular {
Activity = 'activity',
ActivityTarget = 'activityTarget',
ApiKey = 'apiKey',
Attachment = 'attachment',
Blocklist = 'blocklist',
CalendarChannel = 'calendarChannel',
CalendarEvent = 'calendarEvent',
Comment = 'comment',
Company = 'company',
ConnectedAccount = 'connectedAccount',
Dashboard = 'dashboard',
TimelineActivity = 'timelineActivity',
Favorite = 'favorite',
FavoriteFolder = 'favoriteFolder',
Message = 'message',
MessageChannel = 'messageChannel',
MessageParticipant = 'messageParticipant',
MessageFolder = 'messageFolder',
MessageThread = 'messageThread',
Note = 'note',
NoteTarget = 'noteTarget',
Opportunity = 'opportunity',
Person = 'person',
Task = 'task',
TaskTarget = 'taskTarget',
Webhook = 'webhook',
WorkspaceMember = 'workspaceMember',
MessageThreadSubscriber = 'messageThreadSubscriber',
Workflow = 'workflow',
MessageChannelMessageAssociation = 'messageChannelMessageAssociation',
WorkflowVersion = 'workflowVersion',
WorkflowRun = 'workflowRun',
}
@@ -1,4 +1,4 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { CoreObjectNameSingular } from 'twenty-shared/types';
export const getAvatarType = (objectNameSingular: string) => {
if (objectNameSingular === CoreObjectNameSingular.WorkspaceMember) {
@@ -1,5 +1,5 @@
import { type Company } from '@/companies/types/Company';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { getCompanyDomainName } from '@/object-metadata/utils/getCompanyDomainName';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
@@ -1,4 +1,4 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
@@ -1,4 +1,4 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
@@ -1,4 +1,4 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
@@ -1,4 +1,4 @@
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { CoreObjectNameSingular } from 'twenty-shared/types';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
export const isObjectMetadataAvailableForRelation = (