From 0df83eceb2f1188a44f11458d36bd813ca836421 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Wed, 24 Jun 2026 15:05:28 +0200 Subject: [PATCH] fix(front): restore loading state on third-party app command menu actions (#22073) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem Headless command-menu actions provided by third-party applications (e.g. the "Twenty Eng" app actions like *Fetch Pull Requests*, *Recompute Build Tasks*) no longer show a loading/progress indicator while they run, so users can't see that the action is in progress. ## Root cause In `CommandMenuItemSelectableRenderer`, [#21020](https://github.com/twentyhq/twenty/pull/21020) added an early-return branch for third-party application actions that renders `AppMenuItem`: ```tsx if (isThirdPartyApp) { return ( // no loader passed ); } ``` This branch returns **before** the `listItem` path that builds the `loaderComponent` (spinner + progress %), and `AppMenuItem` had no way to render a right-side loader. So `progress` / `showDisabledLoader` from `useCommandMenuItemClick` were computed but dropped for third-party app actions. Native (non third-party) actions kept their loader because they go through the `listItem` path. ## Fix - Add an optional `RightComponent` prop to `AppMenuItem`, forwarded to the underlying `MenuItem` (which already renders it). - Hoist the `loaderComponent` computation in `CommandMenuItemSelectableRenderer` above the branches and pass it to both the third-party `AppMenuItem` path and the existing `listItem` path (no behavior change for the latter). The loader now appears for third-party app actions exactly as it does for native ones — `` once progress is reported, falling back to a `` spinner before the first progress update. ## Verification - `oxlint --type-aware` clean on both changed files. - `typecheck` clean for the changed files. - Manual browser repro requires a third-party application with a progress-reporting headless action installed in the workspace (as in the reported screenshot), which isn't available in a stock dev workspace. The fix mirrors the already-working native `listItem` loader path. Review in cubic --- .../applications/components/AppMenuItem.tsx | 4 ++++ .../components/CommandMenuItemRenderer.tsx | 19 ++++++++++--------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/twenty-front/src/modules/applications/components/AppMenuItem.tsx b/packages/twenty-front/src/modules/applications/components/AppMenuItem.tsx index dc1316090d..461fde08ad 100644 --- a/packages/twenty-front/src/modules/applications/components/AppMenuItem.tsx +++ b/packages/twenty-front/src/modules/applications/components/AppMenuItem.tsx @@ -1,5 +1,6 @@ import { AppChip } from '@/applications/components/AppChip'; import { useApplicationChipData } from '@/applications/hooks/useApplicationChipData'; +import { type ReactNode } from 'react'; import { MenuItem } from 'twenty-ui/navigation'; type AppMenuItemProps = { @@ -8,6 +9,7 @@ type AppMenuItemProps = { onClick?: () => void; focused?: boolean; disabled?: boolean; + RightComponent?: ReactNode; }; export const AppMenuItem = ({ @@ -16,6 +18,7 @@ export const AppMenuItem = ({ onClick, focused, disabled, + RightComponent, }: AppMenuItemProps) => { const { applicationChipData } = useApplicationChipData({ applicationId, @@ -32,6 +35,7 @@ export const AppMenuItem = ({ onClick={onClick} focused={focused} disabled={disabled} + RightComponent={RightComponent} /> ); }; diff --git a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx index dca66eae5b..df2acf1da7 100644 --- a/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx +++ b/packages/twenty-front/src/modules/command-menu-item/display/components/CommandMenuItemRenderer.tsx @@ -118,6 +118,15 @@ const CommandMenuItemSelectableRenderer = ({ handleClick(); }; + const loaderComponent = + disabled && showDisabledLoader ? ( + isDefined(progress) ? ( + + ) : ( + + ) + ) : undefined; + if (isThirdPartyApp) { return ( @@ -127,21 +136,13 @@ const CommandMenuItemSelectableRenderer = ({ onClick={disabled ? undefined : handleClick} focused={!disabled && isSelectedItemId} disabled={disabled} + RightComponent={loaderComponent} /> ); } if (displayType === 'listItem') { - const loaderComponent = - disabled && showDisabledLoader ? ( - isDefined(progress) ? ( - - ) : ( - - ) - ) : undefined; - return (