Skip to content

Add workflow node actions and side panel controls - #24411

Merged
Bonapara merged 10 commits into
mainfrom
bonapara/workflow-node-actions-side-panel-controls
Aug 25, 2026
Merged

Add workflow node actions and side panel controls#24411
Bonapara merged 10 commits into
mainfrom
bonapara/workflow-node-actions-side-panel-controls

Conversation

@Bonapara

@Bonapara Bonapara commented Aug 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Add contextual change, duplicate, and delete actions to selected workflow nodes using shared side-panel menu items.
  • Add labeled close and expand controls while preserving layered Escape and expand shortcuts.
  • Align tertiary icon button color with secondary text.

After

blue button issue is a main problem, not related to this branch

imageimage

@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Aug 20, 2026

Copy link
Copy Markdown

🔍 Visual Regression Review — twenty-ui

✅ 2 visual change(s) reviewed — all explained by this PR.

Changed: 0 · Added: 2 · Removed: 0 · Unchanged: 256

2 new stories
  • ui-input-button-iconbutton--tertiary-default-uses-secondary-text-color
  • ui-input-button-iconbuttonwithtooltip--default

View run details · advisory mode

@Bonapara
Bonapara marked this pull request as ready for review August 20, 2026 08:01
@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Aug 20, 2026

Copy link
Copy Markdown

🚀 Preview Environment Ready!

Your preview environment is available at: https://unlock-aspects-mill-characters.trycloudflare.com

This environment will automatically shut down after 5 hours.

@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Aug 20, 2026

Copy link
Copy Markdown

🔍 Automated Pre-Review

No issues detected - This PR is ready for human review.


View details

Automated pre-review — human approval still required.

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds contextual workflow-node actions, extracts reusable workflow option items, and enhances side-panel close and expand controls with labels and shortcuts.

  • Adds change, duplicate, and delete menus to selected workflow diagram nodes.
  • Reuses workflow option items between diagram nodes and the side-panel footer.
  • Adds tooltip-enabled side-panel controls and adjusts tertiary icon coloring.
  • Extends dropdown configurability and related component tests and stories.

Confidence Score: 4/5

The PR appears safe to merge, with non-blocking repository-convention issues in the new button wrapper and Storybook interaction coverage.

The workflow and side-panel behavior has no established blocking failure, while the accepted findings concern explicit prop forwarding and missing Storybook play functions.

Files Needing Attention: packages/twenty-ui/src/input/IconButtonWithTooltip/IconButtonWithTooltip.tsx; packages/twenty-ui/src/input/IconButtonWithTooltip/stories/IconButtonWithTooltip.stories.tsx; packages/twenty-front/src/modules/workflow/workflow-diagram/components/stories/WorkflowDiagramStepNodeEditableContent.stories.tsx

Important Files Changed

Filename Overview
packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeEditable.tsx Wires selected diagram nodes to change, duplicate, and delete operations using existing workflow navigation and mutation hooks.
packages/twenty-front/src/modules/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeOptionsDropdown.tsx Adds the contextual node-options dropdown and composes the shared workflow menu items.
packages/twenty-front/src/modules/workflow/workflow-steps/components/WorkflowStepOptionsMenuItems.tsx Extracts reusable selectable menu rows for changing, duplicating, and deleting workflow nodes.
packages/twenty-front/src/modules/side-panel/components/SidePanelTopBar.tsx Integrates labeled controls and globally layered Escape handling while preserving input-specific behavior.
packages/twenty-ui/src/input/IconButtonWithTooltip/IconButtonWithTooltip.tsx Adds a tooltip wrapper for IconButton, but forwards button properties through a prohibited broad prop spread.
packages/twenty-ui/src/input/IconButtonWithTooltip/stories/IconButtonWithTooltip.stories.tsx Adds a representative tooltip-button story without the required interaction play function.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Node[Selected workflow node] --> Menu[Node options dropdown]
  Menu --> Change[Change node type]
  Menu --> Duplicate[Duplicate action node]
  Menu --> Delete[Delete node]
  Footer[Workflow step footer] --> Shared[Shared option menu items]
  Menu --> Shared
  Shared --> Change
  Shared --> Duplicate
  Shared --> Delete
Loading

Reviews (1): Last reviewed commit: "Merge branch 'main' into bonapara/workfl..." | Re-trigger Greptile

Comment on lines +27 to +28
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
<IconButton {...iconButtonProps} />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Avoid broad button prop forwarding

This wrapper collects every non-tooltip property into iconButtonProps and spreads it onto IconButton, exposing an ambiguous public API and allowing properties to be forwarded unintentionally. Explicitly pass only the button properties this wrapper supports.

Rule Used: Avoid spreading props for button components; only ... (source)

Learned From
twentyhq/twenty#12282

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the wrapper now exposes and forwards only its supported IconButton props explicitly.

Comment on lines +15 to +23
export const Default: Story = {
args: {
Icon: IconSearch,
ariaLabel: 'Search',
variant: 'tertiary',
size: 'small',
tooltipContent: 'Search',
},
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Add Storybook interaction coverage

The new tooltip-button story has no play function, so Storybook does not exercise the newly introduced tooltip interaction. Add an interaction that validates the tooltip behavior; the changed workflow-node catalog story has the same omission.

Rule Used: Always add 'play' functions to stories in Storyboo... (source)

Learned From
twentyhq/twenty#12354

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added play coverage for keyboard tooltip focus and the workflow node options interaction.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 996b6fb8a1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +135 to +136
{selected && (
<WorkflowDiagramStepNodeOptionsDropdown

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Hide options for the synthetic empty node

Keep the id !== EMPTY_NODE_ID guard when rendering this menu. The canvas maps the synthetic empty node to this component, and its click handler can put empty-node into workflowSelectedNodeComponentState; once selected, this menu exposes duplicate and delete actions that call the workflow mutations with the placeholder ID rather than a real step, resulting in failed operations.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the options menu is now guarded by id !== EMPTY_NODE_ID.

Comment on lines +26 to +28
<div data-tooltip-id={tooltipId}>
{/* oxlint-disable-next-line react/jsx-props-no-spreading */}
<IconButton {...iconButtonProps} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Anchor the tooltip to the focusable button

Attach the tooltip anchor to the actual button rather than this wrapper. AppTooltip registers a native focus listener on the matched anchor, but focus events do not bubble from the nested IconButton to this div, so keyboard users tabbing to the new close and expand controls never see their tooltips—including the expand shortcut hint—while mouse hover still works.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the tooltip now anchors to the nested focusable button.

@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Aug 20, 2026

Copy link
Copy Markdown

🔍 Visual Regression Review — twenty-front

✅ 1 visual change(s) reviewed — all explained by this PR.

Changed: 12 · Added: 0 · Removed: 0 · Unchanged: 752

1 item(s) to double-check (uncertain / low confidence)
Story Verdict Confidence Explained by
🟡 pages-settings-domains-settingssubdomain--valid-subdomain uncertain 45% IconButton.module.scss: tertiary default icon color changed from --t-font-color-…
Changed stories
Story Diff %
pages-settings-domains-settingssubdomain--valid-subdomain 7%
modules-aichat-aichatquestioncard--long-question 0%
modules-aichat-aichatquestioncard--multiple-questions 0%
modules-aichat-aichatquestioncard--multi-select-question 0%
modules-aichat-aichatquestioncard--single-question 0%
modules-settings-accounts-blocklist-settingsaccountsblocklisttablerow--default 0%
modules-settings-accounts-blocklist-settingsaccountsblocklisttablerow--delete-first-element-from-blocklist 0%
modules-settings-accounts-blocklist-settingsaccountsblocklisttable--default 0%
modules-settings-accounts-blocklist-settingsaccountsblocklisttable--delete-first-element-from-blocklist 0%
modules-workflow-workflowdiagramstepnodeeditablecontent--catalog 0%
modules-sidepanel-sidepaneltopbar--subpage 0%
modules-workflow-actions-filter-workflowstepfiltercolumn--default 0%

View run details · advisory mode

@thomtrp
thomtrp self-requested a review August 20, 2026 08:19

@thomtrp thomtrp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two on the new Escape hotkey.

const handleSidePanelBackspace = useHandleSidePanelBackspace();
const handleSidePanelEscape = useHandleSidePanelEscape();

useGlobalHotkeys({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This registers a hotkey straight inside a regular component, while the same PR extracts OptionsDropdownMenuHotkeyEffect to avoid exactly that. Pull it into an effect component too?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — Escape registration now lives in SidePanelTopBarEscapeHotkeyEffect.

const handleSidePanelBackspace = useHandleSidePanelBackspace();
const handleSidePanelEscape = useHandleSidePanelEscape();

useGlobalHotkeys({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleInputKeyDownCapture already calls handleSidePanelEscape on Escape, so the callback is now wired twice and they only avoid double-firing because the capture handler calls stopImmediatePropagation before the document listener runs. That's an undocumented dependency on listener phase — worth a comment, or gating the global one on the input not being focused?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed — the global handler now skips while the search input is focused; the capture handler owns that case.

@Bonapara
Bonapara requested a review from thomtrp August 20, 2026 10:41
@twenty-ci-bot-public

twenty-ci-bot-public Bot commented Aug 20, 2026

Copy link
Copy Markdown

✅ Standard review · no findings

Safe to merge — no outstanding findings; all three prior review findings are resolved

High-level — Cohesive extraction/reuse change (shared menu-items, IconButtonWithTooltip) across side-panel and workflow with no data-model, migration, or rollout risk.
Low-level — Line-by-line pass is clean — consistent isDefined/Pick<> usage, no as-casts, no WHAT comments, effect components covered via hosts, and every earlier nit (duplicated render tests, dedicated effect test) has been removed.


Reviewed against the pr-review standard — high-level then low-level. Advisory; human review still required. Run details.


import { SidePanelTopBarEscapeHotkeyEffect } from '@/side-panel/components/SidePanelTopBarEscapeHotkeyEffect';

describe('SidePanelTopBarEscapeHotkeyEffect', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Nit · Low-level · frontend tests — effect components

Dedicated .test.tsx on a render-null Effect component is a finding

SidePanelTopBarEscapeHotkeyEffect is a side-effect-only component that renders null, so a standalone test asserting mocked globals is exactly the case the standard says to fold into the host; the SidePanelTopBar test already covers the escape-while-input-not-focused path. Delete this file and cover the effect through SidePanelTopBar.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed — host-level Escape coverage remains in SidePanelTopBar.test.tsx.

import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { Provider as JotaiProvider } from 'jotai';

describe('WorkflowDiagramStepNodeOptionsDropdown', () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Nit · Low-level · frontend tests — components render a story

Component that renders a dropdown is covered by a testing-library render test rather than a Storybook story

The standard routes components that render something to a Storybook story with play/findBy, not a render test; the dropdown's behavior is already partly exercised by the WorkflowDiagramStepNodeEditableContent story. Move this coverage into a story play function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the dropdown coverage into the workflow node catalog play function and removed the render test.

@thomtrp thomtrp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice cleanup on the shared menu items. Two things to look at before merge (the failing ui-sb-test story and the Escape handling), rest is minor.


const canvasBody = within(canvasElement.ownerDocument.body);

await expect(await canvasBody.findByRole('tooltip')).toHaveTextContent(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ui-sb-test is red on this: the Default play can't find role="tooltip" and the story renders the error overlay — did this pass locally with nx storybook:test twenty-ui?

onEscape();
};

useGlobalHotkeys({

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RecordTableBodyEscapeHotkeyEffect (and the board/calendar ones) also listen for Escape on PageFocusId.RecordIndex and each side stops immediate propagation when it matches, so whichever registered first swallows the other — the table one even with no rows selected. On a record index with the panel open, which one should win, and is that deterministic today?

import { WorkflowDiagramHandleSource } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleSource';
import { WorkflowDiagramHandleTarget } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramHandleTarget';
import { WorkflowDiagramStepNodeIcon } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeIcon';
import { WorkflowDiagramStepNodeOptionsDropdown } from '@/workflow/workflow-diagram/workflow-nodes/components/WorkflowDiagramStepNodeOptionsDropdown';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This swap leaves WorkflowNodeDeleteButton with zero importers, delete the file too?

}

const tooltipContent = expandTarget.hasExpandShortcut
? `${expandTarget.label} | ${getOsControlSymbol()}⏎`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On non-mac this renders "Expand | Ctrl⏎" with Ctrl glued to ⏎ — want a separator like the hotkeys arrays elsewhere?

onChangeNode={handleChangeNode}
onDuplicateNode={handleDuplicateNode}
onDeleteNode={handleDeleteNode}
deleteNodeAccent="danger"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete is danger here but default accent in the footer's menu — intentional split?

@thomtrp thomtrp left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Bonapara
Bonapara added this pull request to the merge queue Aug 25, 2026
Merged via the queue into main with commit b275322 Aug 25, 2026
117 checks passed
@Bonapara
Bonapara deleted the bonapara/workflow-node-actions-side-panel-controls branch August 25, 2026 11:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants