fix: eliminate workflow editing flicker on active-to-draft transitions (#21176)

## Before

https://github.com/user-attachments/assets/5108a9d8-2017-41d5-855c-98714cbd4237

## After

https://github.com/user-attachments/assets/0a78d1e1-354f-4f3f-8ec4-6f46517619e4

## Summary
- Fixes visual flickering/glitching in the workflow show page header and
canvas when editing an active workflow or discarding a draft
- Root cause: SSE events re-added discarded drafts to Apollo cache, and
multiple hook instances had independent state causing version
oscillation between DRAFT and ACTIVE
- Rewrites `useWorkflowWithCurrentVersion` with a module-level
`discardedDraftId` variable shared across all instances, Apollo cache
seeding in mutation callbacks, and `lastValidResult` caching to prevent
null renders

## Test plan
- [x] Open a workflow show page with an ACTIVE workflow
- [x] Drag a node to change position → verify no flicker, status shows
DRAFT smoothly
- [x] Discard the draft → verify header does NOT flicker between
DRAFT/ACTIVE, position resets cleanly
- [x] Click on manual trigger and edit settings → verify the edit works
(draft created, settings saved)
- [x] Repeat discard + edit cycle multiple times to confirm stability
This commit is contained in:
Thomas Trompette
2026-06-03 17:38:38 +02:00
committed by GitHub
parent cc76b7bc50
commit ac0368e876
6 changed files with 133 additions and 62 deletions
@@ -1,9 +1,5 @@
import { useEffect } from 'react';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
import { shouldWorkflowRefetchRequestFamilyState } from '@/workflow/states/shouldWorkflowRefetchRequestFamilyState';
import { useEffectiveDraftVersionId } from '@/workflow/hooks/useEffectiveDraftVersionId';
import {
type Workflow,
type WorkflowVersion,
@@ -21,62 +17,45 @@ type WorkflowWithAllVersions = Omit<Workflow, 'versions'> & {
export const useWorkflowWithCurrentVersion = (
workflowId: string | undefined,
): WorkflowWithCurrentVersion | undefined => {
const shouldWorkflowRefetchRequest = useAtomFamilyStateValue(
shouldWorkflowRefetchRequestFamilyState,
workflowId ?? '',
);
const setShouldWorkflowRefetchRequest = useSetAtomFamilyState(
shouldWorkflowRefetchRequestFamilyState,
workflowId ?? '',
);
const { record: workflow, refetch: refetchWorkflow } =
useFindOneRecord<WorkflowWithAllVersions>({
objectNameSingular: CoreObjectNameSingular.Workflow,
objectRecordId: workflowId,
recordGqlFields: {
const { record: workflow } = useFindOneRecord<WorkflowWithAllVersions>({
objectNameSingular: CoreObjectNameSingular.Workflow,
objectRecordId: workflowId,
recordGqlFields: {
id: true,
name: true,
statuses: true,
lastPublishedVersionId: true,
versions: {
id: true,
status: true,
name: true,
statuses: true,
lastPublishedVersionId: true,
versions: {
id: true,
status: true,
name: true,
createdAt: true,
},
createdAt: true,
},
skip: !isDefined(workflowId),
});
},
skip: !isDefined(workflowId),
});
useEffect(() => {
if (shouldWorkflowRefetchRequest) {
setShouldWorkflowRefetchRequest(false);
refetchWorkflow();
}
}, [
shouldWorkflowRefetchRequest,
setShouldWorkflowRefetchRequest,
refetchWorkflow,
]);
const draftVersion = workflow?.versions.find(
const draftVersionFromServer = workflow?.versions.find(
(workflowVersion) => workflowVersion.status === 'DRAFT',
);
const workflowVersions = [...(workflow?.versions ?? [])];
const { effectiveDraftId, lastDiscardedDraftId } = useEffectiveDraftVersionId(
draftVersionFromServer,
);
workflowVersions.sort((a, b) => (a.createdAt > b.createdAt ? -1 : 1));
const workflowVersions = [...(workflow?.versions ?? [])]
.filter((version) => version.id !== lastDiscardedDraftId)
.sort((a, b) => (a.createdAt > b.createdAt ? -1 : 1));
const latestVersion = workflowVersions[0];
const currentVersionWithoutSteps = draftVersion ?? latestVersion;
const currentVersionId = effectiveDraftId ?? latestVersion?.id;
const { record: currentVersionWithSteps } = useFindOneRecord<WorkflowVersion>(
{
objectNameSingular: CoreObjectNameSingular.WorkflowVersion,
objectRecordId: currentVersionWithoutSteps?.id,
skip: !isDefined(currentVersionWithoutSteps?.id),
objectRecordId: currentVersionId,
skip: !isDefined(currentVersionId),
},
);