Fully deprecate old recoil (#18210)

## Summary

Removes the `recoil` dependency entirely from `package.json` and
`twenty-front/package.json`, completing the migration to Jotai as the
sole state management library.

Removes all Recoil infrastructure: `RecoilRoot` wrapper from `App.tsx`
and test decorators, `RecoilDebugObserver`, Recoil-specific ESLint rules
(`use-getLoadable-and-getValue-to-get-atoms`,
`useRecoilCallback-has-dependency-array`), and legacy Recoil utility
hooks/types (`useRecoilComponentState`, `useRecoilComponentValue`,
`createComponentState`, `createFamilyState`, `getSnapshotValue`,
`cookieStorageEffect`, `localStorageEffect`, etc.).

Renames all `V2`-suffixed Jotai state files and types to their canonical
names (e.g., `ComponentStateV2` -> `ComponentState`,
`agentChatInputStateV2` -> `agentChatInputState`, `SelectorCallbacksV2`
-> `SelectorCallbacks`), and removes the now-redundant V1 counterparts.

Updates ~433 files across the codebase to use the renamed Jotai imports,
remove Recoil imports, and clean up test wrappers (`RecoilRootDecorator`
-> `JotaiRootDecorator`).
This commit is contained in:
Charles Bochet
2026-02-25 12:26:42 +01:00
committed by GitHub
parent ecb7270a7b
commit 121788c42f
439 changed files with 1714 additions and 3929 deletions
@@ -9,7 +9,7 @@ import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/use
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
import { useDuplicateStep } from '@/workflow/workflow-steps/hooks/useDuplicateStep';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { workflowAiAgentActionAgentStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentStateV2';
import { workflowAiAgentActionAgentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentState';
import { useLingui } from '@lingui/react/macro';
import { useId } from 'react';
import { SettingsPath } from 'twenty-shared/types';
@@ -45,7 +45,7 @@ export const WorkflowStepFooter = ({
const { deleteStep } = useDeleteStep();
const navigateSettings = useNavigateSettings();
const workflowAiAgentActionAgent = useAtomStateValue(
workflowAiAgentActionAgentStateV2,
workflowAiAgentActionAgentState,
);
const shouldPinDeleteButton =
!isDefined(additionalActions) || additionalActions.length === 0;
@@ -1,5 +1,4 @@
import { act, renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
import { useCreateStep } from '@/workflow/workflow-steps/hooks/useCreateStep';
@@ -40,15 +39,13 @@ const wrapper = ({ children }: { children: React.ReactNode }) => {
'workflow-visualizer-instance-id';
return (
<RecoilRoot>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: workflowVisualizerComponentInstanceId,
}}
>
{children}
</WorkflowVisualizerComponentInstanceContext.Provider>
</RecoilRoot>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: workflowVisualizerComponentInstanceId,
}}
>
{children}
</WorkflowVisualizerComponentInstanceContext.Provider>
);
};
@@ -2,7 +2,6 @@ import React from 'react';
import { useDeleteStep } from '@/workflow/workflow-steps/hooks/useDeleteStep';
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
const mockDeleteWorkflowVersionStep = jest.fn();
@@ -55,15 +54,13 @@ const wrapper = ({ children }: { children: React.ReactNode }) => {
'workflow-visualizer-instance-id';
return (
<RecoilRoot>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: workflowVisualizerComponentInstanceId,
}}
>
{children}
</WorkflowVisualizerComponentInstanceContext.Provider>
</RecoilRoot>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: workflowVisualizerComponentInstanceId,
}}
>
{children}
</WorkflowVisualizerComponentInstanceContext.Provider>
);
};
@@ -1,6 +1,5 @@
import { useDuplicateStep } from '@/workflow/workflow-steps/hooks/useDuplicateStep';
import { act, renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
import { WorkflowVisualizerComponentInstanceContext } from '@/workflow/workflow-diagram/states/contexts/WorkflowVisualizerComponentInstanceContext';
const mockGetUpdatableWorkflowVersion = jest.fn();
@@ -38,15 +37,13 @@ const wrapper = ({ children }: { children: React.ReactNode }) => {
'workflow-visualizer-instance-id';
return (
<RecoilRoot>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: workflowVisualizerComponentInstanceId,
}}
>
{children}
</WorkflowVisualizerComponentInstanceContext.Provider>
</RecoilRoot>
<WorkflowVisualizerComponentInstanceContext.Provider
value={{
instanceId: workflowVisualizerComponentInstanceId,
}}
>
{children}
</WorkflowVisualizerComponentInstanceContext.Provider>
);
};
@@ -5,9 +5,9 @@ import { type WorkflowAiAgentAction } from '@/workflow/types/Workflow';
import { useWorkflowAiAgentPermissionActions } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useWorkflowAiAgentPermissionActions';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { workflowAiAgentActionAgentStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentStateV2';
import { workflowAiAgentPermissionsIsAddingPermissionStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsIsAddingPermissionStateV2';
import { workflowAiAgentPermissionsSelectedObjectIdStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsSelectedObjectIdStateV2';
import { workflowAiAgentActionAgentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentState';
import { workflowAiAgentPermissionsIsAddingPermissionState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsIsAddingPermissionState';
import { workflowAiAgentPermissionsSelectedObjectIdState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsSelectedObjectIdState';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import { useState } from 'react';
@@ -76,17 +76,17 @@ export const WorkflowAiAgentPermissionsTab = ({
refetchAgent,
}: WorkflowAiAgentPermissionsTabProps) => {
const workflowAiAgentActionAgent = useAtomStateValue(
workflowAiAgentActionAgentStateV2,
workflowAiAgentActionAgentState,
);
const [
workflowAiAgentPermissionsSelectedObjectId,
setWorkflowAiAgentPermissionsSelectedObjectId,
] = useAtomState(workflowAiAgentPermissionsSelectedObjectIdStateV2);
] = useAtomState(workflowAiAgentPermissionsSelectedObjectIdState);
const [
workflowAiAgentPermissionsIsAddingPermission,
setWorkflowAiAgentPermissionsIsAddingPermission,
] = useAtomState(workflowAiAgentPermissionsIsAddingPermissionStateV2);
] = useAtomState(workflowAiAgentPermissionsIsAddingPermissionState);
const { alphaSortedActiveNonSystemObjectMetadataItems: objectMetadataItems } =
useFilteredObjectMetadataItems();
@@ -1,7 +1,7 @@
import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput';
import { Select } from '@/ui/input/components/Select';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { workflowAiAgentActionAgentStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentStateV2';
import { workflowAiAgentActionAgentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentState';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { t } from '@lingui/core/macro';
import {
@@ -33,7 +33,7 @@ export const WorkflowAiAgentPromptTab = ({
onResponseFormatChange,
}: WorkflowAiAgentPromptTabProps) => {
const workflowAiAgentActionAgent = useAtomStateValue(
workflowAiAgentActionAgentStateV2,
workflowAiAgentActionAgentState,
);
return (
<>
@@ -13,8 +13,8 @@ import { WorkflowAiAgentPermissionsTab } from '@/workflow/workflow-steps/workflo
import { WORKFLOW_AI_AGENT_TABS } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/WorkflowAiAgentTabs';
import { useResetWorkflowAiAgentPermissionsStateOnCommandMenuClose } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/hooks/useResetWorkflowAiAgentPermissionsStateOnCommandMenuClose';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { workflowAiAgentActionAgentStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentStateV2';
import { workflowAiAgentPermissionsIsAddingPermissionStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsIsAddingPermissionStateV2';
import { workflowAiAgentActionAgentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentState';
import { workflowAiAgentPermissionsIsAddingPermissionState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsIsAddingPermissionState';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import {
@@ -66,7 +66,7 @@ export const WorkflowEditActionAiAgent = ({
const componentInstanceId = `${WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID}-${action.id}`;
const agentId = action.settings.input.agentId;
const [workflowAiAgentActionAgent, setWorkflowAiAgentActionAgent] =
useAtomState(workflowAiAgentActionAgentStateV2);
useAtomState(workflowAiAgentActionAgentState);
const { loading: agentLoading, refetch: refetchAgent } = useFindOneAgentQuery(
{
variables: { id: agentId || '' },
@@ -217,7 +217,7 @@ export const WorkflowEditActionAiAgent = ({
const [
workflowAiAgentPermissionsIsAddingPermission,
setWorkflowAiAgentPermissionsIsAddingPermission,
] = useAtomState(workflowAiAgentPermissionsIsAddingPermissionStateV2);
] = useAtomState(workflowAiAgentPermissionsIsAddingPermissionState);
const role = rolesData?.getRoles.find(
(item) => item.id === workflowAiAgentActionAgent?.roleId,
@@ -1,17 +1,17 @@
import { useListenToSidePanelClosing } from '@/ui/layout/right-drawer/hooks/useListenToSidePanelClosing';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { workflowAiAgentActionAgentStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentStateV2';
import { workflowAiAgentPermissionsIsAddingPermissionStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsIsAddingPermissionStateV2';
import { workflowAiAgentPermissionsSelectedObjectIdStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsSelectedObjectIdStateV2';
import { workflowAiAgentActionAgentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentState';
import { workflowAiAgentPermissionsIsAddingPermissionState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsIsAddingPermissionState';
import { workflowAiAgentPermissionsSelectedObjectIdState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsSelectedObjectIdState';
export const useResetWorkflowAiAgentPermissionsStateOnCommandMenuClose = () => {
const setSelectedObjectId = useSetAtomState(
workflowAiAgentPermissionsSelectedObjectIdStateV2,
workflowAiAgentPermissionsSelectedObjectIdState,
);
const setIsAddingPermission = useSetAtomState(
workflowAiAgentPermissionsIsAddingPermissionStateV2,
workflowAiAgentPermissionsIsAddingPermissionState,
);
const setAgentState = useSetAtomState(workflowAiAgentActionAgentStateV2);
const setAgentState = useSetAtomState(workflowAiAgentActionAgentState);
const resetPermissionState = () => {
setSelectedObjectId(undefined);
@@ -5,9 +5,9 @@ import { useSettingsRolePermissionFlagConfig } from '@/settings/roles/role-permi
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { CRUD_PERMISSIONS } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/WorkflowAiAgentCrudPermissions';
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
import { workflowAiAgentActionAgentStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentStateV2';
import { workflowAiAgentPermissionsIsAddingPermissionStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsIsAddingPermissionStateV2';
import { workflowAiAgentPermissionsSelectedObjectIdStateV2 } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsSelectedObjectIdStateV2';
import { workflowAiAgentActionAgentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentActionAgentState';
import { workflowAiAgentPermissionsIsAddingPermissionState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsIsAddingPermissionState';
import { workflowAiAgentPermissionsSelectedObjectIdState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentPermissionsSelectedObjectIdState';
import { t } from '@lingui/core/macro';
import { useMemo } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -37,7 +37,7 @@ export const useWorkflowAiAgentPermissionActions = ({
}: UseWorkflowAiAgentPermissionActionsParams) => {
const { enqueueSuccessSnackBar } = useSnackBar();
const [workflowAiAgentActionAgent, setWorkflowAiAgentActionAgent] =
useAtomState(workflowAiAgentActionAgentStateV2);
useAtomState(workflowAiAgentActionAgentState);
const { alphaSortedActiveNonSystemObjectMetadataItems: objectMetadataItems } =
useFilteredObjectMetadataItems();
const settingsPermissionsConfig = useSettingsRolePermissionFlagConfig({
@@ -48,10 +48,10 @@ export const useWorkflowAiAgentPermissionActions = ({
});
const [, setWorkflowAiAgentPermissionsSelectedObjectId] = useAtomState(
workflowAiAgentPermissionsSelectedObjectIdStateV2,
workflowAiAgentPermissionsSelectedObjectIdState,
);
const [, setWorkflowAiAgentPermissionsIsAddingPermission] = useAtomState(
workflowAiAgentPermissionsIsAddingPermissionStateV2,
workflowAiAgentPermissionsIsAddingPermissionState,
);
const [createRole] = useCreateOneRoleMutation();
@@ -118,7 +118,7 @@ export const useWorkflowAiAgentPermissionActions = ({
const { refetchedAgent } = await refetchAgentAndRoles();
// Update Recoil state with the refetched agent to ensure roleId is available
// Update state with the refetched agent to ensure roleId is available
// Apollo's onCompleted is not called on refetch, so we need to update manually
if (isDefined(refetchedAgent)) {
setWorkflowAiAgentActionAgent(refetchedAgent);
@@ -2,9 +2,9 @@ import { type Agent } from '~/generated-metadata/graphql';
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const workflowAiAgentActionAgentStateV2 = createAtomState<
export const workflowAiAgentActionAgentState = createAtomState<
Agent | undefined
>({
key: 'workflowAiAgentActionAgentStateV2',
key: 'workflowAiAgentActionAgentState',
defaultValue: undefined,
});
@@ -1,7 +1,7 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const workflowAiAgentPermissionsIsAddingPermissionStateV2 =
export const workflowAiAgentPermissionsIsAddingPermissionState =
createAtomState<boolean>({
key: 'workflowAiAgentPermissionsIsAddingPermissionStateV2',
key: 'workflowAiAgentPermissionsIsAddingPermissionState',
defaultValue: false,
});
@@ -0,0 +1,8 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const workflowAiAgentPermissionsSelectedObjectIdState = createAtomState<
string | undefined
>({
key: 'workflowAiAgentPermissionsSelectedObjectIdState',
defaultValue: undefined,
});
@@ -1,7 +0,0 @@
import { createAtomState } from '@/ui/utilities/state/jotai/utils/createAtomState';
export const workflowAiAgentPermissionsSelectedObjectIdStateV2 =
createAtomState<string | undefined>({
key: 'workflowAiAgentPermissionsSelectedObjectIdStateV2',
defaultValue: undefined,
});
@@ -3,7 +3,6 @@ import { type HttpRequestFormData } from '@/workflow/workflow-steps/workflow-act
import { useMutation } from '@apollo/client';
import { act, renderHook } from '@testing-library/react';
import React from 'react';
import { RecoilRoot } from 'recoil';
import { resolveInput } from 'twenty-shared/utils';
import { useTestHttpRequest } from '@/workflow/workflow-steps/workflow-actions/http-request-action/hooks/useTestHttpRequest';
@@ -87,7 +86,7 @@ describe('useTestHttpRequest', () => {
};
const wrapper = ({ children }: { children: React.ReactNode }) =>
React.createElement(RecoilRoot, null, children);
React.createElement(React.Fragment, null, children);
beforeEach(() => {
jest.clearAllMocks();