mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 21:47:38 +00:00
## Recoil → Jotai progressive migration: infrastructure + ChipFieldDisplay ### Benchmark In the beginning, there was no hope: <img width="1180" height="948" alt="image" src="https://github.com/user-attachments/assets/f8635991-52e6-4958-8240-6ba7214132b2" /> Then the hope was reborn <img width="2070" height="948" alt="image" src="https://github.com/user-attachments/assets/be1182b9-1c8d-4fdc-ab4c-1484ad74449d" /> ### Approach We introduce a **V2 state management layer** backed by Jotai that mirrors the existing Recoil API, enabling component-by-component migration without a big-bang rewrite. #### V2 API (Jotai-backed, Recoil-ergonomic) - `createStateV2` / `createFamilyStateV2` — drop-in replacements for `createState` / `createFamilyState`, returning wrapper types over Jotai atoms - `useRecoilValueV2`, `useRecoilStateV2`, `useFamilyRecoilValueV2`, etc. — thin wrappers around Jotai's `useAtomValue` / `useAtom` / `useSetAtom` - A shared `jotaiStore` (via `createStore()`) passed to a `<JotaiProvider>` wrapping `<RecoilRoot>`, also accessible imperatively for dual-writes #### Dual-write bridge for progressive migration For state shared between migrated and non-migrated components, we use **dual-write**: writers update both the Recoil atom and the Jotai V2 atom (via `jotaiStore.set()`). This avoids sync components or extra subscriptions. Write sites updated: `useUpsertRecordsInStore`, `useSetRecordTableData`, `ListenRecordUpdatesEffect`, `RecordShowEffect`, `useLoadRecordIndexStates`, `useUpdateObjectViewOptions`. #### First migration: ChipFieldDisplay render path - `useChipFieldDisplay` → reads `recordStoreFamilyStateV2` via `useFamilyRecoilValueV2` (was `useRecoilValue(recordStoreFamilyState)`) - `RecordChip` → reads `recordIndexOpenRecordInStateV2` via `useRecoilValueV2` (was `useRecoilValue(recordIndexOpenRecordInState)`) - `Avatar` (twenty-ui) and event handlers (`useOpenRecordInCommandMenu`) left on Recoil — not on the render path / in a different package #### Pattern for migrating additional state 1. Create V2 atom: `createStateV2` or `createFamilyStateV2` 2. Add `jotaiStore.set(v2Atom, value)` at each write site 3. Switch readers to `useRecoilValueV2(v2Atom)` 4. Once all readers are migrated, remove the Recoil atom and dual-writes #### Why not jotai-recoil-adapter? Evaluated [jotai-recoil-adapter](https://github.com/clockelliptic/jotai-recoil-adapter) — not production-ready (21 open issues, no React 19, forces providerless mode, missing types). We built a purpose-built thin layer instead.
178 lines
4.5 KiB
JSON
178 lines
4.5 KiB
JSON
{
|
|
"name": "twenty-ui",
|
|
"main": "dist/index.cjs",
|
|
"module": "dist/index.mjs",
|
|
"style": "./dist/style.css",
|
|
"type": "module",
|
|
"devDependencies": {
|
|
"@babel/preset-env": "^7.26.9",
|
|
"@babel/preset-react": "^7.26.3",
|
|
"@prettier/sync": "^0.5.2",
|
|
"@swc/plugin-emotion": "10.0.4",
|
|
"@types/babel__preset-env": "^7",
|
|
"@types/react": "^18.2.39",
|
|
"@types/react-dom": "^18.2.15",
|
|
"@wyw-in-js/babel-preset": "^0.6.0",
|
|
"babel-plugin-inline-import": "^3.0.0",
|
|
"babel-plugin-inline-react-svg": "^2.0.2",
|
|
"babel-plugin-module-resolver": "^5.0.2",
|
|
"tsx": "^4.19.3",
|
|
"vite-plugin-checker": "^0.10.2",
|
|
"vite-plugin-dts": "3.8.1",
|
|
"vite-plugin-svgr": "^4.3.0"
|
|
},
|
|
"dependencies": {
|
|
"@emotion/is-prop-valid": "^1.3.0",
|
|
"@emotion/react": "^11.11.1",
|
|
"@emotion/styled": "^11.11.0",
|
|
"@linaria/react": "^6.2.1",
|
|
"@monaco-editor/react": "^4.7.0",
|
|
"@sniptt/guards": "^0.2.0",
|
|
"@tabler/icons-react": "^3.31.0",
|
|
"date-fns": "^2.30.0",
|
|
"framer-motion": "^11.18.0",
|
|
"glob": "^11.1.0",
|
|
"hex-rgb": "^5.0.0",
|
|
"jotai": "^2.17.1",
|
|
"react": "^18.2.0",
|
|
"react-dom": "^18.2.0",
|
|
"react-responsive": "^9.0.2",
|
|
"react-router-dom": "^6.4.4",
|
|
"react-tooltip": "^5.13.1",
|
|
"recoil": "^0.7.7",
|
|
"twenty-shared": "workspace:*",
|
|
"zod": "^4.1.11"
|
|
},
|
|
"peerDependencies": {
|
|
"monaco-editor": ">= 0.25.0 < 1"
|
|
},
|
|
"scripts": {
|
|
"build": "npx vite build"
|
|
},
|
|
"files": [
|
|
"dist",
|
|
"accessibility",
|
|
"assets",
|
|
"components",
|
|
"display",
|
|
"feedback",
|
|
"input",
|
|
"json-visualizer",
|
|
"layout",
|
|
"navigation",
|
|
"testing",
|
|
"theme",
|
|
"utilities"
|
|
],
|
|
"sideEffects": [
|
|
"**/*.css"
|
|
],
|
|
"exports": {
|
|
".": {
|
|
"types": "./dist/index.d.ts",
|
|
"import": "./dist/index.mjs",
|
|
"require": "./dist/index.cjs"
|
|
},
|
|
"./style.css": "./dist/style.css",
|
|
"./accessibility": {
|
|
"types": "./dist/accessibility/index.d.ts",
|
|
"import": "./dist/accessibility.mjs",
|
|
"require": "./dist/accessibility.cjs"
|
|
},
|
|
"./assets": {
|
|
"types": "./dist/assets/index.d.ts",
|
|
"import": "./dist/assets.mjs",
|
|
"require": "./dist/assets.cjs"
|
|
},
|
|
"./components": {
|
|
"types": "./dist/components/index.d.ts",
|
|
"import": "./dist/components.mjs",
|
|
"require": "./dist/components.cjs"
|
|
},
|
|
"./display": {
|
|
"types": "./dist/display/index.d.ts",
|
|
"import": "./dist/display.mjs",
|
|
"require": "./dist/display.cjs"
|
|
},
|
|
"./feedback": {
|
|
"types": "./dist/feedback/index.d.ts",
|
|
"import": "./dist/feedback.mjs",
|
|
"require": "./dist/feedback.cjs"
|
|
},
|
|
"./input": {
|
|
"types": "./dist/input/index.d.ts",
|
|
"import": "./dist/input.mjs",
|
|
"require": "./dist/input.cjs"
|
|
},
|
|
"./json-visualizer": {
|
|
"types": "./dist/json-visualizer/index.d.ts",
|
|
"import": "./dist/json-visualizer.mjs",
|
|
"require": "./dist/json-visualizer.cjs"
|
|
},
|
|
"./layout": {
|
|
"types": "./dist/layout/index.d.ts",
|
|
"import": "./dist/layout.mjs",
|
|
"require": "./dist/layout.cjs"
|
|
},
|
|
"./navigation": {
|
|
"types": "./dist/navigation/index.d.ts",
|
|
"import": "./dist/navigation.mjs",
|
|
"require": "./dist/navigation.cjs"
|
|
},
|
|
"./testing": {
|
|
"types": "./dist/testing/index.d.ts",
|
|
"import": "./dist/testing.mjs",
|
|
"require": "./dist/testing.cjs"
|
|
},
|
|
"./theme": {
|
|
"types": "./dist/theme/index.d.ts",
|
|
"import": "./dist/theme.mjs",
|
|
"require": "./dist/theme.cjs"
|
|
},
|
|
"./utilities": {
|
|
"types": "./dist/utilities/index.d.ts",
|
|
"import": "./dist/utilities.mjs",
|
|
"require": "./dist/utilities.cjs"
|
|
}
|
|
},
|
|
"typesVersions": {
|
|
"*": {
|
|
"accessibility": [
|
|
"dist/accessibility/index.d.ts"
|
|
],
|
|
"assets": [
|
|
"dist/assets/index.d.ts"
|
|
],
|
|
"components": [
|
|
"dist/components/index.d.ts"
|
|
],
|
|
"display": [
|
|
"dist/display/index.d.ts"
|
|
],
|
|
"feedback": [
|
|
"dist/feedback/index.d.ts"
|
|
],
|
|
"input": [
|
|
"dist/input/index.d.ts"
|
|
],
|
|
"json-visualizer": [
|
|
"dist/json-visualizer/index.d.ts"
|
|
],
|
|
"layout": [
|
|
"dist/layout/index.d.ts"
|
|
],
|
|
"navigation": [
|
|
"dist/navigation/index.d.ts"
|
|
],
|
|
"testing": [
|
|
"dist/testing/index.d.ts"
|
|
],
|
|
"theme": [
|
|
"dist/theme/index.d.ts"
|
|
],
|
|
"utilities": [
|
|
"dist/utilities/index.d.ts"
|
|
]
|
|
}
|
|
}
|
|
}
|