Add standard command menu items (#18527)

## Add standard command menu items

### Summary

This PR introduces standard command menu items, migrating hardcoded
command menu actions to the backend command menu item architecture
powered by front components. It adds a new `twenty-standard-application`
package that defines, builds, and registers front components as standard
command menu items, gated behind the `IS_COMMAND_MENU_ITEM_ENABLED`
feature flag.

### Description

- **New `twenty-standard-application` package**: Contains front
component definitions with an esbuild-based build pipeline that
generates minified `.mjs` bundles and a manifest with checksums.
- **Server-side registration**: New constants register all items with
metadata (labels, icons, positions, availability types, conditional
expressions). A `StandardFrontComponentUploadService` uploads built
components to file storage.
- **`FALLBACK` availability type**: New enum value for command menu
items that appear as fallback options (e.g., "Search Records" fallback).
- **`CommandMenuContextApi` refactor**
- **Conditional availability enhancements**: New array-based helper
functions for evaluating multi-record conditions.
- **Frontend wiring** (twenty-front):
`useCommandMenuItemFrontComponentCommands`

## Next steps

Only simple commands have been implemented for now:
- **Navigation (9)** -- `CommandLink`: go-to-companies,
go-to-dashboards, go-to-notes, go-to-opportunities, go-to-people,
go-to-runs, go-to-settings, go-to-tasks, go-to-workflows
- **Side panel (4)** -- `CommandOpenSidePanelPage`: ask-ai,
search-records, search-records-fallback, view-previous-ai-chats

We still have to implement front components for all the following
commands:
All have placeholder `execute` logic (`async () => {}`) with a `// TODO:
implement execute logic` comment:

**Record (22)**
- `add-to-favorites`, `remove-from-favorites`
- `create-new-record`, `create-new-view`
- `delete-single-record`, `delete-multiple-records`
- `destroy-single-record`, `destroy-multiple-records`
- `restore-single-record`, `restore-multiple-records`
- `export-from-record-index`, `export-from-record-show`,
`export-multiple-records`, `export-note-to-pdf`, `export-view`
- `hide-deleted-records`, `see-deleted-records`
- `import-records`, `merge-multiple-records`, `update-multiple-records`
- `navigate-to-next-record`, `navigate-to-previous-record`

**Page layout (3)** -- `cancel-record-page-layout`,
`edit-record-page-layout`, `save-record-page-layout`

**Dashboard (4)** -- `cancel-dashboard-layout`, `duplicate-dashboard`,
`edit-dashboard-layout`, `save-dashboard-layout`

**Workflow (10)** -- `activate-workflow`, `add-node-workflow`,
`deactivate-workflow`, `discard-draft-workflow`, `duplicate-workflow`,
`see-active-version-workflow`, `see-runs-workflow`,
`see-versions-workflow`, `test-workflow`, `tidy-up-workflow`

**Workflow version (4)** -- `see-runs-workflow-version`,
`see-versions-workflow-version`, `see-workflow-workflow-version`,
`use-as-draft-workflow-version`

**Workflow run (3)** -- `see-version-workflow-run`,
`see-workflow-workflow-run`, `stop-workflow-run`
This commit is contained in:
Raphaël Bosi
2026-03-10 18:36:41 +01:00
committed by GitHub
parent 25d9f2fcce
commit 2de022afcf
252 changed files with 10972 additions and 253 deletions
@@ -1,16 +1,18 @@
import { useRunWorkflowRecordCommands } from '@/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands';
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
import {
CommandMenuContext,
type CommandMenuContextType,
} from '@/command-menu-item/contexts/CommandMenuContext';
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
import { useCommandMenuItemFrontComponentCommands } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentCommands';
import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
import { useShouldCommandMenuItemBeRegisteredParams } from '@/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams';
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
import { useCommandMenuItemFrontComponentActions } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentActions';
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
import { useRunWorkflowRecordCommands } from '@/command-menu-item/record/workflow/hooks/useRunWorkflowRecordCommands';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
export const CommandMenuContextProviderDefault = ({
objectMetadataItem,
@@ -56,7 +58,11 @@ export const CommandMenuContextProviderDefault = ({
const commandMenuContextApi = useCommandMenuContextApi();
const commandMenuItemFrontComponentActions =
useCommandMenuItemFrontComponentActions(commandMenuContextApi);
useCommandMenuItemFrontComponentCommands(commandMenuContextApi);
const isCommandMenuItemEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
);
return (
<CommandMenuContext.Provider
@@ -64,12 +70,13 @@ export const CommandMenuContextProviderDefault = ({
isInSidePanel,
displayType,
containerType,
commandMenuItems: [
...commandMenuItems,
...runWorkflowRecordCommands,
...runWorkflowRecordAgnosticCommands,
...commandMenuItemFrontComponentActions,
],
commandMenuItems: isCommandMenuItemEnabled
? commandMenuItemFrontComponentActions
: [
...commandMenuItems,
...runWorkflowRecordCommands,
...runWorkflowRecordAgnosticCommands,
],
}}
>
{children}
@@ -1,12 +1,12 @@
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
import {
CommandMenuContext,
type CommandMenuContextType,
} from '@/command-menu-item/contexts/CommandMenuContext';
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
import { useCommandMenuItemFrontComponentCommands } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentCommands';
import { useRegisteredCommandMenuItems } from '@/command-menu-item/hooks/useRegisteredCommandMenuItems';
import { useShouldCommandMenuItemBeRegisteredParams } from '@/command-menu-item/hooks/useShouldCommandMenuItemBeRegisteredParams';
import { useCommandMenuContextApi } from '@/command-menu-item/hooks/useCommandMenuContextApi';
import { useCommandMenuItemFrontComponentActions } from '@/command-menu-item/hooks/useCommandMenuItemFrontComponentActions';
import { useRunWorkflowRecordAgnosticCommands } from '@/command-menu-item/record-agnostic/workflow/hooks/useRunWorkflowRecordAgnosticCommands';
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
@@ -14,7 +14,9 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useWorkflowWithCurrentVersion } from '@/workflow/hooks/useWorkflowWithCurrentVersion';
import { type WorkflowWithCurrentVersion } from '@/workflow/types/Workflow';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { isDefined } from 'twenty-shared/utils';
import { FeatureFlagKey } from '~/generated-metadata/graphql';
type CommandMenuContextProviderWorkflowObjectsProps = {
objectMetadataItem: ObjectMetadataItem;
@@ -55,8 +57,30 @@ const CommandMenuContextProviderWorkflowObjectsContent = ({
const commandMenuContextApi = useCommandMenuContextApi();
const enrichedSelectedRecords = isDefined(workflowWithCurrentVersion)
? commandMenuContextApi.selectedRecords.map((record) =>
record.id === workflowWithCurrentVersion.id
? {
...record,
currentVersion: workflowWithCurrentVersion.currentVersion,
versions: workflowWithCurrentVersion.versions,
statuses: workflowWithCurrentVersion.statuses,
}
: record,
)
: commandMenuContextApi.selectedRecords;
const enrichedCommandMenuContextApi = {
...commandMenuContextApi,
selectedRecords: enrichedSelectedRecords,
};
const commandMenuItemFrontComponentActions =
useCommandMenuItemFrontComponentActions(commandMenuContextApi);
useCommandMenuItemFrontComponentCommands(enrichedCommandMenuContextApi);
const isCommandMenuItemEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
);
return (
<CommandMenuContext.Provider
@@ -64,11 +88,9 @@ const CommandMenuContextProviderWorkflowObjectsContent = ({
isInSidePanel,
displayType,
containerType,
commandMenuItems: [
...commandMenuItems,
...runWorkflowRecordAgnosticCommands,
...commandMenuItemFrontComponentActions,
],
commandMenuItems: isCommandMenuItemEnabled
? commandMenuItemFrontComponentActions
: [...commandMenuItems, ...runWorkflowRecordAgnosticCommands],
}}
>
{children}
@@ -104,7 +126,11 @@ const CommandMenuContextProviderWorkflowObjectsWithoutWorkflow = ({
const commandMenuContextApi = useCommandMenuContextApi();
const commandMenuItemFrontComponentActions =
useCommandMenuItemFrontComponentActions(commandMenuContextApi);
useCommandMenuItemFrontComponentCommands(commandMenuContextApi);
const isCommandMenuItemEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
);
return (
<CommandMenuContext.Provider
@@ -112,11 +138,9 @@ const CommandMenuContextProviderWorkflowObjectsWithoutWorkflow = ({
isInSidePanel,
displayType,
containerType,
commandMenuItems: [
...commandMenuItems,
...runWorkflowRecordAgnosticCommands,
...commandMenuItemFrontComponentActions,
],
commandMenuItems: isCommandMenuItemEnabled
? commandMenuItemFrontComponentActions
: [...commandMenuItems, ...runWorkflowRecordAgnosticCommands],
}}
>
{children}