Files
twenty/packages
Félix Malfait 0e6d96bb5e fix(workflow): stop trigger/action filter Conditions from flashing on edit (#21952)
## Problem

Adding a condition to a database-event **trigger** (the new Conditions
section), the **Filter** action, or the **If/Else** action causes the
just-added condition to flash out and back in.

## Root cause

`WorkflowEditActionFilterBodyEffect` seeds the builder's local jotai
atoms from the persisted `defaultValue` through an effect that
**resynced whenever the live atoms differed from `defaultValue`** (the
atoms were in the effect deps and the equality check compared atoms vs
`defaultValue`).

A local edit writes the atoms **synchronously**, then persists through
an **async** mutation — and for an *active* workflow that mutation first
creates a draft version over the network. During that window the atoms
are ahead of the still-stale `defaultValue`, so the effect treated it as
"out of sync" and overwrote the edit back to the stale value, then wrote
it again once the save landed. That round-trip is the flash.

The resync existed for a real reason: the atoms are module-cached per
`instanceId` and persist across mounts, and the trigger shares a single
**constant** `instanceId` (`'trigger'`), so a previous trigger's filters
must be overwritten when a different one is opened. (This is also why
the `?? { stepFilterGroups: [], stepFilters: [] }` fallback was added in
#21868 — to reset builder state deterministically between trigger
edits.) So a naive "init-once" fix would reintroduce that stale-state
leak.

## Fix

Resync from `defaultValue` **only when `defaultValue` itself changes**,
tracked via the last-synced value in `useState` (not the live atoms).
This:

- never clobbers an in-flight local edit → no flash;
- still re-seeds when switching the trigger/action being edited → no
stale-state leak;
- preserves reflecting genuine external `defaultValue` changes.

The `hasInitialized*` flags are no longer needed and are removed (along
with the now-unused `stepId` prop on the effect).

## Tests

Adds a regression test covering: seeding from `defaultValue` on mount,
the **no-clobber-while-stale** invariant (the flash), and resync on a
genuine `defaultValue` change. Verified the no-clobber test **fails**
against the old "resync against live atoms" behavior and passes with the
fix.

## Verification

- `nx typecheck twenty-front` 
- `nx lint:diff-with-main twenty-front`  (0 warnings / 0 errors)
- New unit test: 3 passing 

## Known residual / follow-up

On an *active* workflow, the first edit creates a draft version over the
network; making a second edit before that round-trip completes leaves a
narrow window where the optimistic echo of the first value could
momentarily win. Far narrower than the current flash-on-every-edit.
Eliminating it entirely (and resolving the still-open HIGH-severity
"constant `instanceId`" review flag from #21868) would mean giving the
trigger a unique `instanceId` per workflow version + a React `key` to
reset on remount — proposed as a separate, scoped follow-up.

https://claude.ai/code/session_01XnjtzFepMJX2VFwnQVcQbV

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XnjtzFepMJX2VFwnQVcQbV)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21952?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-23 10:44:56 +02:00
..