From 086ea644bb4a67824bc19cae2725126b3dd2c1be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?= <71827178+bosiraphael@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:15:52 +0200 Subject: [PATCH] Resolve frontComponent relation on command menu items via selector (#19493) ## Bug description Headless command menu items were interpreted as non headless command menu items and opened in the side panel. https://github.com/user-attachments/assets/44d8b4d3-9ee5-4f12-9ff8-b3ed51e5b3b6 ## Fix https://github.com/user-attachments/assets/9d6eb900-9817-4ceb-87aa-547ad046a5a1 - Command menu items received via SSE lack the nested `frontComponent` relation (only `frontComponentId` is present), causing `item.frontComponent?.isHeadless` to always be undefined. - Add `frontComponents` to the metadata store's stale entity loading - Rewrite `commandMenuItemsSelector` to join `commandMenuItems` with `frontComponents` by `frontComponentId` --- .../src/generated-metadata/graphql.ts | 4 ++-- .../common/states/commandMenuItemsSelector.ts | 24 +++++++++++++++++-- .../queries/findManyFrontComponents.ts | 10 ++++++++ .../hooks/useLoadStaleMetadataEntities.ts | 18 ++++++++++++++ 4 files changed, 52 insertions(+), 4 deletions(-) diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 25b461f742..8732c27c78 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -6782,7 +6782,7 @@ export type RenewApplicationTokenMutation = { __typename?: 'Mutation', renewAppl export type FindManyFrontComponentsQueryVariables = Exact<{ [key: string]: never; }>; -export type FindManyFrontComponentsQuery = { __typename?: 'Query', frontComponents: Array<{ __typename?: 'FrontComponent', id: string, name: string, applicationId: string }> }; +export type FindManyFrontComponentsQuery = { __typename?: 'Query', frontComponents: Array<{ __typename?: 'FrontComponent', id: string, name: string, applicationId: string, builtComponentChecksum: string, builtComponentPath: string, componentName: string, createdAt: string, description?: string | null, isHeadless: boolean, sourceComponentPath: string, universalIdentifier?: string | null, updatedAt: string, usesSdkClient: boolean }> }; export type FindOneFrontComponentQueryVariables = Exact<{ id: Scalars['UUID']; @@ -8374,7 +8374,7 @@ export const FindOnePageLayoutDocument = {"kind":"Document","definitions":[{"kin export const UploadFilesFieldFileDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UploadFilesFieldFile"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"file"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Upload"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fieldMetadataId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uploadFilesFieldFile"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"file"},"value":{"kind":"Variable","name":{"kind":"Name","value":"file"}}},{"kind":"Argument","name":{"kind":"Name","value":"fieldMetadataId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fieldMetadataId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"path"}},{"kind":"Field","name":{"kind":"Name","value":"size"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode; export const UploadWorkflowFileDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UploadWorkflowFile"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"file"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Upload"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uploadWorkflowFile"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"file"},"value":{"kind":"Variable","name":{"kind":"Name","value":"file"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"path"}},{"kind":"Field","name":{"kind":"Name","value":"size"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode; export const RenewApplicationTokenDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RenewApplicationToken"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"applicationRefreshToken"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"renewApplicationToken"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"applicationRefreshToken"},"value":{"kind":"Variable","name":{"kind":"Name","value":"applicationRefreshToken"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"applicationAccessToken"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"applicationRefreshToken"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}}]}}]}}]}}]} as unknown as DocumentNode; -export const FindManyFrontComponentsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyFrontComponents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"frontComponents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}}]}}]}}]} as unknown as DocumentNode; +export const FindManyFrontComponentsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindManyFrontComponents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"frontComponents"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"builtComponentChecksum"}},{"kind":"Field","name":{"kind":"Name","value":"builtComponentPath"}},{"kind":"Field","name":{"kind":"Name","value":"componentName"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"isHeadless"}},{"kind":"Field","name":{"kind":"Name","value":"sourceComponentPath"}},{"kind":"Field","name":{"kind":"Name","value":"universalIdentifier"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}},{"kind":"Field","name":{"kind":"Name","value":"usesSdkClient"}}]}}]}}]} as unknown as DocumentNode; export const FindOneFrontComponentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"FindOneFrontComponent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"frontComponent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"builtComponentChecksum"}},{"kind":"Field","name":{"kind":"Name","value":"isHeadless"}},{"kind":"Field","name":{"kind":"Name","value":"usesSdkClient"}},{"kind":"Field","name":{"kind":"Name","value":"applicationTokenPair"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"applicationAccessToken"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}}]}},{"kind":"Field","name":{"kind":"Name","value":"applicationRefreshToken"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"token"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}}]}}]}}]}}]}}]} as unknown as DocumentNode; export const CreateOneLogicFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneLogicFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateLogicFunctionFromSourceInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneLogicFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LogicFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LogicFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LogicFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"timeoutSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"sourceHandlerPath"}},{"kind":"Field","name":{"kind":"Name","value":"handlerName"}},{"kind":"Field","name":{"kind":"Name","value":"toolInputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"isTool"}},{"kind":"Field","name":{"kind":"Name","value":"cronTriggerSettings"}},{"kind":"Field","name":{"kind":"Name","value":"databaseEventTriggerSettings"}},{"kind":"Field","name":{"kind":"Name","value":"httpRouteTriggerSettings"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; export const DeleteOneLogicFunctionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteOneLogicFunction"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"LogicFunctionIdInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteOneLogicFunction"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"LogicFunctionFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"LogicFunctionFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"LogicFunction"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"runtime"}},{"kind":"Field","name":{"kind":"Name","value":"timeoutSeconds"}},{"kind":"Field","name":{"kind":"Name","value":"sourceHandlerPath"}},{"kind":"Field","name":{"kind":"Name","value":"handlerName"}},{"kind":"Field","name":{"kind":"Name","value":"toolInputSchema"}},{"kind":"Field","name":{"kind":"Name","value":"isTool"}},{"kind":"Field","name":{"kind":"Name","value":"cronTriggerSettings"}},{"kind":"Field","name":{"kind":"Name","value":"databaseEventTriggerSettings"}},{"kind":"Field","name":{"kind":"Name","value":"httpRouteTriggerSettings"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/command-menu-item/server-items/common/states/commandMenuItemsSelector.ts b/packages/twenty-front/src/modules/command-menu-item/server-items/common/states/commandMenuItemsSelector.ts index ce5ca08a40..9c0f395c06 100644 --- a/packages/twenty-front/src/modules/command-menu-item/server-items/common/states/commandMenuItemsSelector.ts +++ b/packages/twenty-front/src/modules/command-menu-item/server-items/common/states/commandMenuItemsSelector.ts @@ -1,5 +1,8 @@ import { metadataStoreState } from '@/metadata-store/states/metadataStoreState'; +import { type FlatCommandMenuItem } from '@/metadata-store/types/FlatCommandMenuItem'; +import { type FlatFrontComponent } from '@/metadata-store/types/FlatFrontComponent'; import { createAtomSelector } from '@/ui/utilities/state/jotai/utils/createAtomSelector'; +import { isDefined } from 'twenty-shared/utils'; import { type CommandMenuItemFieldsFragment } from '~/generated-metadata/graphql'; export const commandMenuItemsSelector = createAtomSelector< @@ -7,8 +10,25 @@ export const commandMenuItemsSelector = createAtomSelector< >({ key: 'commandMenuItemsSelector', get: ({ get }) => { - const storeItem = get(metadataStoreState, 'commandMenuItems'); + const commandMenuItems = get(metadataStoreState, 'commandMenuItems') + .current as FlatCommandMenuItem[]; + const flatFrontComponents = get(metadataStoreState, 'frontComponents') + .current as FlatFrontComponent[]; - return storeItem.current as CommandMenuItemFieldsFragment[]; + const frontComponentsById = new Map( + flatFrontComponents.map((frontComponent) => [ + frontComponent.id, + frontComponent, + ]), + ); + + return commandMenuItems.map((item) => ({ + ...item, + frontComponent: isDefined(item.frontComponentId) + ? (frontComponentsById.get(item.frontComponentId) ?? + item.frontComponent ?? + null) + : null, + })) as CommandMenuItemFieldsFragment[]; }, }); diff --git a/packages/twenty-front/src/modules/front-components/graphql/queries/findManyFrontComponents.ts b/packages/twenty-front/src/modules/front-components/graphql/queries/findManyFrontComponents.ts index d0f1a8b0d1..340ac46b22 100644 --- a/packages/twenty-front/src/modules/front-components/graphql/queries/findManyFrontComponents.ts +++ b/packages/twenty-front/src/modules/front-components/graphql/queries/findManyFrontComponents.ts @@ -6,6 +6,16 @@ export const FIND_MANY_FRONT_COMPONENTS = gql` id name applicationId + builtComponentChecksum + builtComponentPath + componentName + createdAt + description + isHeadless + sourceComponentPath + universalIdentifier + updatedAt + usesSdkClient } } `; diff --git a/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts index 29decdef41..a3158f0392 100644 --- a/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts +++ b/packages/twenty-front/src/modules/metadata-store/hooks/useLoadStaleMetadataEntities.ts @@ -15,6 +15,7 @@ import { FindManyCommandMenuItemsDocument, FindAllRecordPageLayoutsDocument, FindFieldsWidgetViewsDocument, + FindManyFrontComponentsDocument, FindManyLogicFunctionsDocument, FindManyNavigationMenuItemsDocument, GetChatThreadsDocument, @@ -208,6 +209,23 @@ export const useLoadStaleMetadataEntities = () => { ); } + if (staleEntityKeys.includes('frontComponents')) { + fetchPromises.push( + client + .query({ + query: FindManyFrontComponentsDocument, + fetchPolicy: 'network-only', + }) + .then((result) => { + if (!isDefined(result.data?.frontComponents)) { + return; + } + + replaceDraft('frontComponents', result.data.frontComponents); + }), + ); + } + if (staleEntityKeys.includes('agentChatThreads') && isAiEnabled) { fetchPromises.push( client