b338a7a1d2f1e60a3cea0812d98219ef125bd9e1
607 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b338a7a1d2 |
feat(settings): discovery hero rollout + ephemeral playground token (#21072)
## Summary
Two intertwined streams of work:
### UI — discovery hero pattern, settings shell, AI/API redesign
- **Generalize `SettingsDiscoveryHeroCard`** and use it on Layout, Data
Model, Apps, AI, API/Webhooks, Members. Drops 4 per-page wrapper files
(`SettingsObjectCoverImage`, `SettingsLayoutCoverImage`,
`SettingsLayoutCustomizeVideoModal`,
`SettingsDataModelVisualizeVideoModal`). Each page now supplies cover
src, modal id, and tab list.
- **Modal**: swap `<video>` placeholder for the Vimeo iframe pattern
from `twenty-docs`, per-tab `vimeoId`. Drop the parallel border-bottom
on the header (TabList draws its own baseline) and the grey background
behind the video. Note: Vimeo's embed allowlist applies — the iframes
load with the correct URL on `localhost` but the player itself requires
the video owner to allow the dev/staging domains in Vimeo settings.
- **AI page** rebuilt into a Cockpit pattern (Overview / Models / Skills
/ Tools / Usage). New `SettingsAiOverviewTab` with default Smart/Fast
pickers, at-a-glance stats, and an MCP signpost that deep-links to
`/settings/api-webhooks#mcp`. System Prompt link moved under Models.
Advanced tab removed.
- **API & Webhooks** now has 4 tabs (Playground / MCP / API Keys /
Webhooks). Hero card above tabs. Playground tab inverted to "Core API" /
"Metadata API" sections, each containing REST + GraphQL cards — schema
is the meaningful axis, protocol is secondary. Hash deep-link sync
delegated to the shared `TabListFromUrlOptionalEffect`.
- **Settings shell**: unified drawer outer padding (kill `isSettings`
branch), extract `CollapsibleNavigationDrawerSection`, add `iconColor`
on settings nav items, fix Exit Settings button alignment, 880px content
cap.
### Backend — strategy C: ephemeral playground token
The legacy paste-your-API-key flow is replaced by an on-demand
short-lived token scoped to the calling user's permissions. No shared
"Playground" API key to manage or revoke.
- New `JwtTokenTypeEnum.PLAYGROUND`. `PlaygroundTokenJwtPayload =
Omit<AccessTokenJwtPayload, 'type' | impersonation fields>` so any
future ACCESS claim flows through automatically.
- `AccessTokenService.generatePlaygroundToken` signs an access-shaped
JWT with `type: PLAYGROUND` and a configurable short TTL. A shared
private `resolveTokenSubject` helper parallelizes the user / workspace /
userWorkspace lookups for both generators.
- `JwtAuthStrategy.validateAccessToken` widened to accept
`AccessTokenJwtPayload | PlaygroundTokenJwtPayload`; impersonation gated
on `payload.type === ACCESS` so the union narrows without `as unknown
as` casts. The two branches in `validate()` collapse into one.
- New `PLAYGROUND_TOKEN_EXPIRES_IN` config var (default `2h`).
- New `generatePlaygroundToken` mutation (`WorkspaceAuthGuard`, no args,
returns `AuthToken`).
- Frontend `useOpenPlayground` hook centralizes mint → atom write →
navigate, with Apollo `onError` snackbar and a "use cached PLAYGROUND
token if still fresh" short-circuit (decodes via `jwt-decode`, checks
both `type` AND `exp`). Old API_KEY tokens left in localStorage from the
prior paste-form flow are rejected on `type` alone and force a re-mint —
this is what was causing the "This API Key is revoked" symptom on stale
browsers.
### Drive-by cleanups
- `PlaygroundToken` DTO removed (identical shape to `AuthToken` already
in use).
- 5 `customize-sidebar.webm` imports and the dead placeholder pipeline
removed.
## Test plan
### Discovery hero
- [ ] `/settings/layout`, `/settings/data-model`,
`/settings/applications`, `/settings/ai`, `/settings/api-webhooks`,
`/settings/members` each render the discovery hero card with its
illustration + play button + tabbed modal
- [ ] Modal tabs show the correct Vimeo embed URL per tab; aspect ratio
stays at 1440/900; no parallel border-bottom jog at the tab baseline
- [ ] AI Overview tab shows Smart/Fast model pickers + stats grid + MCP
signpost card; the MCP card lands on `/settings/api-webhooks#mcp` with
the MCP tab active
### API playground (ephemeral token)
- [ ] With an empty `playgroundApiKeyState` in localStorage, clicking
REST or GraphQL playground card opens the playground and the cached
token has `type: "PLAYGROUND"` with ~2h exp
- [ ] Clicking the card again within the freshness window does **not**
re-mint (`iat` / fingerprint stable across visits)
- [ ] Planting a fake API_KEY-shaped JWT in localStorage and clicking
the card forces a fresh mint (old token rejected on `type`)
- [ ] `GET /rest/companies?limit=1` with the cached token returns 200 +
real data
- [ ] `POST /graphql { __typename }` returns 200
### Settings shell
- [ ] Settings nav matches main app drawer padding; sections collapse;
Exit Settings button aligns with the workspace links above
- [ ] Active nav items have a right-gap (cleaner active state)
- [ ] Content area capped at 880px
### Verify
- [ ] `npx nx typecheck twenty-front` passes
- [ ] `npx nx typecheck twenty-server` passes
- [ ] `npx nx lint:diff-with-main twenty-front` passes
- [ ] `npx nx lint:diff-with-main twenty-server` passes
|
||
|
|
4ba9c0ca0b |
[Navigation Drawer] Multiple fixes in settings and app drawer (#20634)
closes - https://discord.com/channels/1130383047699738754/1487720717192527942 https://github.com/user-attachments/assets/6db2df8b-be01-4b5f-a958-575d87b41559 ~~waiting on @Bonapara 's feedback!~~ |
||
|
|
695a374efd |
fix - nav drawer expansion (#20545)
[PR](https://github.com/twentyhq/twenty/pull/20505), I merge, does not fix the whole issue Closes https://github.com/twentyhq/twenty/issues/20502 |
||
|
|
ee6c0ef904 |
Replace sign-in mocked metadata with hardcoded BackgroundMock (#20308)
## Summary When the user is logged out, we render the auth modal on top of a sample table to make the empty page feel alive. So far this was achieved by **loading a full set of mocked object / field / view / navigation-menu metadata into the runtime metadata store** and then mounting the real `RecordTable` and `AppNavigationDrawer` behind the modal. This had a few downsides: - Significant bundle weight pulled in for unauthenticated users (mocked GraphQL fixtures + the real `RecordTable` virtualization stack). - Plenty of code paths that had to know about the "showAuthModal" case (`useRecordIndexTableQuery`, `useTriggerInitialRecordTableDataLoad`, `MainContextStoreProvider`, `IsMinimalMetadataReadyEffect`...). - Any change to metadata-store internals or to the record-table runtime risked breaking the logged-out background. This PR replaces the entire flow with a small, self-contained `BackgroundMock` component tree that **does not consume any metadata** and **does not load any mocked metadata at runtime**. ### What changed - New module under `sign-in-background-mock`: - `BackgroundMockPage` + `BackgroundMockViewBar` + `BackgroundMockTable` + `BackgroundMockTableRow` render a hardcoded "Companies" table that visually mirrors the real one. - `BackgroundMockNavigationDrawer` renders a hardcoded sidebar with People / Companies / Opportunities / Tasks / Notes (with their standard colors). - Hardcoded constants in `BackgroundMockCompanies.ts`, `BackgroundMockColumns.ts`, `BackgroundMockNavigationItems.ts`. - `MinimalMetadataLoadEffect` no longer calls `loadMockedMetadataAtomic` for unauthenticated users — it just doesn't load anything. - `IsMinimalMetadataReadyEffect` now reports ready immediately when there is no access token pair, so the skeleton loader doesn't hang waiting for metadata that will never come. - `MainContextStoreProvider`, `useRecordIndexTableQuery`, and `useTriggerInitialRecordTableDataLoad` drop their `showAuthModal` branches — the real `RecordTable` is no longer mounted behind the modal. - `DefaultLayout` and `NotFound` now lazily load `BackgroundMockPage` / `BackgroundMockNavigationDrawer` instead of the deleted `SignInBackgroundMockPage` / `SignInAppNavigationDrawerMock`. - Removed: `SignInBackgroundMockPage`, `SignInBackgroundMockContainer`, `SignInBackgroundMockContainerEffect`, `SignInAppNavigationDrawerMock`, `SignInBackgroundMockColumnDefinitions`, `SignInBackgroundMockCompanies`, `SignInBackgroundMockViewFields`. `useLoadMockedMetadata` and `preloadMockedMetadata` are kept on purpose: Storybook decorators (`ObjectMetadataItemsDecorator`, `WorkflowStepDecorator`) still rely on the mocked metadata fixtures, but **production** unauthenticated runtime no longer touches them. ### Visual parity Side-by-side at 1440×900 on `/sign-in`: **Before** (loads mocked metadata + real RecordTable):  **After** (purely hardcoded BackgroundMock):  ## Test plan - [ ] `npx nx typecheck twenty-front` ✅ (passes locally) - [ ] `npx nx lint:diff-with-main twenty-front` ✅ (oxlint + prettier clean) - [ ] `npx jest useRecordIndexTableQuery` ✅ - [ ] Manually verify `/sign-in` renders the table + nav drawer behind the modal - [ ] Manually verify `/not-found` still renders the background - [ ] Verify CI: storybook, unit tests, e2e tests |
||
|
|
f19b3bfd38 |
Fix left/right arrow keys not working in dropdown search inputs (#19759)
Closes #12847 Implements a two-mode focus management pattern for `SelectableList` components with search inputs, resolving the conflict between text input cursor movement and grid/list navigation. ### How it works **Input mode** (search input focused): - Left/right arrow keys move the text cursor normally (`enableOnFormTags: false` on ArrowLeft/ArrowRight hotkeys) - Up/down arrow keys blur the input and transfer focus to the grid, entering grid mode **Grid mode** (search input blurred): - All arrow keys navigate the selectable list grid - Pressing up arrow from the top row clears the grid selection and refocuses the search input, returning to input mode - Typing any printable character refocuses the search input (wildcard hotkey with `enableOnFormTags: false`) ### Demo https://github.com/user-attachments/assets/825ad603-a5f8-4863-8269-3ecf35965847 https://github.com/user-attachments/assets/9d07346d-18a0-40fa-8874-21040c11f03d |
||
|
|
96fc98e710 |
Fix Apps UI: replace 'Managed' label with actual app name and unify app icons (#19897)
## Summary
- The Data Model table was labeling core Twenty objects (e.g. Person,
Company) as **Managed** even though they are part of the standard
application. This PR teaches the frontend to resolve an `applicationId`
back to its real application name (`Standard`, `Custom`, or any
installed app), and removes the misleading **Managed** label entirely.
- Introduces a single, consistent way to render an "app badge" across
the settings UI:
- new `Avatar` variant `type="app"` (rounded 4px corners + 1px
deterministic border derived from `placeholderColorSeed`)
- new `AppChip` component (icon + name) backed by a new
`useApplicationChipData` hook
- new `useApplicationsByIdMap` hook + `CurrentApplicationContext` so the
chip can render **This app** when shown inside the matching app's detail
page
- Reuses these primitives on:
- the application detail page header (`SettingsApplicationDetailTitle`)
- the Installed / My apps tables (`SettingsApplicationTableRow`)
- the NPM packages list (`SettingsApplicationsDeveloperTab`)
- Backend: exposes a minimal `installedApplications { id name
universalIdentifier }` field on `Workspace` (resolved from the workspace
cache, soft-deleted entries filtered out) so the frontend can resolve
`applicationId` -> name without N+1 fetches.
- Cleanup: deletes `getItemTagInfo` and inlines its tiny
responsibilities into the components that need them, matching the
`RecordChip` pattern.
|
||
|
|
381f3ba7d9 |
Fix app design 1/2 (#19735)
comply with https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=96977-349627&m=dev ## After <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 40 37" src="https://github.com/user-attachments/assets/6d80191a-79a9-4f0f-aa4f-0e447fff4f6d" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 40 22" src="https://github.com/user-attachments/assets/4f763272-027e-4246-b455-7d46babf7d8c" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 39 11" src="https://github.com/user-attachments/assets/b9b35e18-8068-447e-821d-5ec28bb5bd16" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 39 05" src="https://github.com/user-attachments/assets/57d9318a-902f-4fd7-a2a3-5795ebe0b9dc" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 39 02" src="https://github.com/user-attachments/assets/78a33fa8-6bdd-484e-a82d-bd0f7592a623" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 38 58" src="https://github.com/user-attachments/assets/f7987aed-c6e1-4032-a611-86817655137d" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 38 55" src="https://github.com/user-attachments/assets/d1c451ab-1d2d-41e4-a059-cf4303ecabe7" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 38 48" src="https://github.com/user-attachments/assets/593cae36-2320-443f-a955-93b211a6ee3f" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 37 40" src="https://github.com/user-attachments/assets/c9f602b1-8de3-4e82-a3a6-344594a0c153" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 37 34" src="https://github.com/user-attachments/assets/b54ddddf-5dda-46c8-ace3-cffe6015825a" /> ## before <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42 18" src="https://github.com/user-attachments/assets/c0976a0a-0124-48ec-8e7c-78627cea7063" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42 16" src="https://github.com/user-attachments/assets/d2db926c-4040-411d-9091-8b60e7c519e6" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42 13" src="https://github.com/user-attachments/assets/2d69f2ff-f26e-4249-91a3-2cf3d261e840" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42 07" src="https://github.com/user-attachments/assets/1028aabc-77ac-4c51-a8c3-9a194faba87f" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42 01" src="https://github.com/user-attachments/assets/1caa9f5e-3eaa-433c-9d3b-e0f094f16e8e" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41 56" src="https://github.com/user-attachments/assets/f42b6976-3a8f-4591-9283-bda79bdb424b" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41 53" src="https://github.com/user-attachments/assets/93d00df8-0091-4dfa-9ac0-f6f376be5962" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41 43" src="https://github.com/user-attachments/assets/9deae7e5-39c1-4518-a463-6d79bc5bf132" /> <img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41 37" src="https://github.com/user-attachments/assets/3e21b521-c47d-482c-ad41-66abfe973772" /> |
||
|
|
3b85747d3f |
Go back to the original command K button (#19727)
## Before https://github.com/user-attachments/assets/868e5293-8843-44c8-8011-b7130e97fa95 ## After https://github.com/user-attachments/assets/89cf46cc-1fb1-4e78-bfef-53e1d04e8ebf |
||
|
|
4b3a46d953 |
Refactor command menu items deprecated code (#19508)
- Removes the intermediate `CommandMenuItemConfig` / `CommandConfigContext` / `CommandMenuItemDisplay` abstraction layers, replacing them with a single `CommandMenuItemRenderer` that renders directly from the command menu items from the backend - Eliminates the server-items/ subdirectory by moving its contents (hooks/, contexts/, states/, display/, edit/) up into the parent command-menu-item/ module, removing an unnecessary nesting level. |
||
|
|
50c5a1a8de |
[AI] new model tab design (#19384)
closes https://discord.com/channels/1130383047699738754/1480979892610007121 <img width="1839" height="1316" alt="CleanShot 2026-04-07 at 15 22 38" src="https://github.com/user-attachments/assets/d8f6048c-4e0f-425f-b7ea-4913d116020e" /> |
||
|
|
83d30f8b76 |
feat: Send email from UI — inline reply composer & SendEmail mutation (#19363)
## Summary - **Inline email reply**: Replace external email client redirects (Gmail/Outlook deeplinks) with an in-app email composer. Users can reply to email threads directly from the email thread widget or via the command menu. - **SendEmail GraphQL mutation**: New backend mutation that reuses `EmailComposerService` for body sanitization, recipient validation, and SMTP dispatch via the existing outbound messaging infrastructure. - **Side panel compose page**: Command menu "Reply" action now opens a side-panel compose email page with pre-filled To, Subject, and In-Reply-To fields. ### Backend - `SendEmailResolver` with `SendEmailInput` / `SendEmailOutputDTO` - `SendEmailModule` wired into `CoreEngineModule` - Reuses `EmailComposerService` + `MessagingMessageOutboundService` ### Frontend - `EmailComposer` / `EmailComposerFields` components - `useSendEmail`, `useReplyContext`, `useEmailComposerState` hooks - `useOpenComposeEmailInSidePanel` + `SidePanelComposeEmailPage` - `EmailThreadWidget` inline Reply bar with toggle composer - `ReplyToEmailThreadCommand` now opens side-panel instead of external links ### Seeds - Added `handle` field to message participant seeds for realistic email addresses - Seed `connectedAccount` and `messageChannel` in correct batch order ## Test plan - [ ] Open an email thread on a person/company record → verify "Reply..." bar appears below the last message - [ ] Click "Reply..." → composer opens inline with pre-filled To and Subject - [ ] Type a message and click Send → email is sent via SMTP, composer closes - [ ] Use command menu Reply action → side panel opens with compose email page - [ ] Verify Send/Cancel buttons work correctly in side panel - [ ] Test with Cc/Bcc toggle in composer fields - [ ] Verify error handling: invalid recipients, missing connected account Made with [Cursor](https://cursor.com) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> |
||
|
|
f8c3960cf2 |
Disable setting tab when empty (#19315)
## before <img width="1063" height="520" alt="image" src="https://github.com/user-attachments/assets/db92432b-a30c-49e8-9246-a09d0815e6c9" /> ## after <img width="1049" height="490" alt="image" src="https://github.com/user-attachments/assets/e926b065-6a65-417b-b802-0e1df6ca638f" /> |
||
|
|
291792f864 |
Repair Edit Layout command menu item and add a button in settings to start edition too (#19208)
https://github.com/user-attachments/assets/f23f0777-abf3-4ff4-8fab-ec2004df60bc |
||
|
|
a51b5ed589 |
fix: resolve settings/usage chart crash and add ClickHouse usage event seeds (#19039)
## Summary
- **Fix settings/usage page crash**: The `GraphWidgetLineChart`
component used on `settings/usage` was crashing with "Instance id is not
provided and cannot be found in context" because it requires
`WidgetComponentInstanceContext` (for tooltip/crosshair component
states) which is only provided inside the widget system. Wraps the
standalone chart usages with the required context provider.
- **Avoid mounting `GraphWidgetLegend` when hidden**: The legend
component calls `useIsPageLayoutInEditMode()` which requires
`PageLayoutEditModeProviderContext` — another context only available
inside the widget system. Since the settings page passes
`showLegend={false}`, the fix conditionally unmounts the legend instead
of always mounting it with a `show` prop. Applied consistently across
all four chart types (line, bar, pie, gauge).
- **Add ClickHouse usage event seeds**: Generates ~400 realistic
`usageEvent` rows spanning the past 35 days with weighted user activity,
weekday/weekend patterns, and gradual ramp-up. Enables developers to see
the usage analytics page with data locally.
## Test plan
- [ ] Navigate to `settings/usage` — page should render without errors
- [ ] Verify the daily usage line chart displays correctly
- [ ] Navigate to a user detail page from the usage list
- [ ] Verify the user detail chart renders without errors
- [ ] Run `npx nx clickhouse:seed twenty-server` and confirm usage
events are seeded
- [ ] Verify chart legend still works correctly on dashboard widgets (no
regression)
Made with [Cursor](https://cursor.com)
|
||
|
|
afeef8e04a |
feat: add command menu item to layout customization (#18764)
figma https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=93630-365000&t=QesAkY3JOyI9D3UU-0 https://github.com/user-attachments/assets/cf3b354d-0b08-41ae-91ae-386054b5cb2e https://github.com/user-attachments/assets/73bfd676-f823-44c9-a70d-107c20523664 --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
cfefe9273b |
Use record page layouts in the merge records feature (#18961)
There was a design issue. It revealed that we weren't using record page layouts in the merge records feature. ## Before <img width="802" height="1283" alt="image" src="https://github.com/user-attachments/assets/e4238144-e10e-47a6-83e6-7cc03ca89b15" /> ## After https://github.com/user-attachments/assets/c0fa9cf5-2b28-4696-bf20-8271dce9e62c |
||
|
|
cec23e89fa |
Fix batch update optimistic and prevent accidental mass-update (#17213)
## Problem - ⚠️ Multi-edit could silently update ALL records of an object with no undo, no ctrl-z - After a batch update the table showed stale data — `useIncrementalUpdateManyRecords` had no explicit `findMany` refetch and `skipOptimisticEffect` was missing - Clicking inside the side panel deselected all kanban cards — `RecordBoardClickOutsideEffect` uses `refs:[]` and the side panel had no `data-click-outside-id` - Clicking a currency/select dropdown inside the panel also triggered deselection — `FloatingPortal` renders outside the side panel DOM, bypassing the click-outside-id exclusion - An empty selection silently matched every record — `computeContextStoreFilters` returned `undefined` filter when `selectedRecordIds` was `[]` ## Fix - Table refreshes correctly after batch update — `useRefetchFindManyRecords` explicitly refetches `FindMany<Object>` queries; `useIncrementalUpdateManyRecords` adds `skipOptimisticEffect: true` and calls it in `finally` - Clicking the side panel no longer deselects kanban cards — `SidePanelForDesktop` carries `data-click-outside-id`; `RecordBoardClickOutsideEffect` + `RecordTableBodyFocusClickOutsideEffect` exclude it - Clicking dropdowns inside the panel no longer deselects either — `ParentClickOutsideIdContext` propagates the side panel ID into `FloatingPortal` content via `DropdownInternalContainer` - Empty selection can no longer match all records — `computeContextStoreFilters` returns `{ id: { in: [] } }` instead of `undefined` - Apply is disabled with no selection; a confirmation modal shows the count + no-undo warning before executing — `UpdateMultipleRecordsContainer` ## Not included - Undo / snapshot restore — requires backend changes, out of scope ## Blast radius - `ParentClickOutsideIdContext` touches `DropdownInternalContainer` (207 `<Dropdown>` usages). `parentClickOutsideId` is `undefined` everywhere outside the side panel → attribute not rendered → zero behavioral change for existing consumers. --------- Co-authored-by: Samuel Arbibe <samuelarbibe@Samuels-MacBook-Pro.local> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> |
||
|
|
c6d4162b73 |
Fix: Background color selected row + border bottom settings tab (#18870)
<img width="1285" height="73" alt="Capture d’écran 2026-03-23 à 18 58 09" src="https://github.com/user-attachments/assets/a01cfc8e-6492-4ebe-a47a-b504a73e616c" /> <img width="574" height="59" alt="Capture d’écran 2026-03-23 à 18 58 39" src="https://github.com/user-attachments/assets/4947ec02-e151-48fb-87e9-86dbc0ed4fe9" /> |
||
|
|
77d4bd9158 |
Add billing usage analytics dashboard with ClickHouse integration (#18592)
## Summary This PR adds a comprehensive billing usage analytics feature that provides detailed breakdowns of credit consumption across execution types, users, resources, and time periods. The implementation includes a new ClickHouse-backed analytics service, GraphQL API endpoint, and a frontend dashboard component. ## Key Changes ### Backend - **New BillingAnalyticsService**: Queries ClickHouse for usage breakdowns by user, resource, execution type, and time series data - **BillingEventWriterService**: Writes billing events to ClickHouse for analytics while maintaining best-effort semantics (never blocks Stripe billing) - **ClickHouse Schema**: Added `billingEvent` table with 3-year TTL for storing detailed billing event data - **GraphQL Resolver**: New `getBillingAnalytics` query that aggregates usage data for the current billing period, protected by feature flag and billing permissions - **Enhanced BillingUsageEvent**: Added `userWorkspaceId` field to track per-user credit consumption - **AI Billing Integration**: Updated AI billing service to pass `userWorkspaceId` when recording usage events ### Frontend - **SettingsBillingAnalyticsSection**: New component displaying: - Usage breakdown by execution type with progress bars - Daily usage time series chart (28-day view) - Per-user credit consumption breakdown - Per-resource (agent/workflow) credit consumption breakdown - **SettingsUsage Page**: Dedicated page for viewing usage analytics - **GraphQL Query**: `GetBillingAnalytics` query with generated hooks - **Navigation**: Added Usage menu item in settings (feature-flagged) - **Mock Data**: Included screenshot mock data for preview/testing ### Feature Flag - Added `IS_USAGE_ANALYTICS_ENABLED` feature flag to control visibility and access to analytics features ## Implementation Details - Analytics data is queried in parallel for performance - ClickHouse writes are non-blocking to ensure billing operations never fail - Progress bars use dynamic coloring from a predefined palette - Time series visualization normalizes bar heights relative to max value - Empty state handling when no analytics data is available - Responsive UI with proper text truncation for long names https://claude.ai/code/session_01Y1EqrX6PFq3EJxJq89h7DF --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
9f7c29bce8 |
refactor(twenty-front): unify Favorites and Workspace navigation menu item code (#18697)
## Summary - Consolidate duplicated Favorites and Workspace navigation menu item frontend code into a single unified codebase within `navigation-menu-item/` - Move all DnD-related code from `navigation/` module into `navigation-menu-item/display/dnd/`, renaming `workspaceDndKit*` files to `navigationMenuItemDndKit*` - Unify duplicated components (DnD providers, DnD hooks, folder components, orphan items, section shell) using a `NavigationSections` enum to parameterize section-specific behavior - Rename residual workspace-prefixed symbols (`WorkspaceDndKitSortableItem`, `WorkspaceDndKitDroppableSlot`, `useWorkspaceSectionItems`, etc.) to `navigationMenuItem`-prefixed equivalents - Clean `navigation/` module to only contain app-level concerns (drawer layout, settings, routing) ### Key changes | Before (duplicated) | After (unified) | |---|---| | `FavoritesDndKitProvider` + `WorkspaceDndKitProvider` | `NavigationMenuItemDndKitProvider` with `section` prop | | `useFavoritesDndKit` + `useWorkspaceDndKit` | `useNavigationMenuItemDndKit(section)` | | `FavoritesFolderItem` + `WorkspaceNavigationMenuItemsFolder` | `NavigationMenuItemFolder` with `section` prop | | `FavoritesOrphanItems` | `NavigationMenuItemOrphanItems` with `section` prop | | Separate section shells | Shared `NavigationMenuItemSection` with thin wrappers | | `WorkspaceDndKitSortableItem` | `NavigationMenuItemSortableItem` | | `WorkspaceDndKitDroppableSlot` | `NavigationMenuItemDroppableSlot` | | `useWorkspaceSectionItems` | `useNavigationMenuItemSectionItems` | | `useWorkspaceFolderOpenState` | `useNavigationMenuItemFolderOpenState` | Net result: **~1650 lines deleted** across 51 files. ## Test plan - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx lint twenty-front` passes (0 errors) - [x] `npx nx test twenty-front` passes (763 suites, 4467 tests) - [ ] Smoke test: Favorites section renders, DnD reorder works, folder create/rename/delete works - [ ] Smoke test: Workspace section renders, edit mode works, DnD reorder works - [ ] Smoke test: Add-to-navigation from side panel works for both sections Made with [Cursor](https://cursor.com) |
||
|
|
ab881350b2 |
refactor: unify layout customization mode (record pages + navigation) (#18640)
### What Unifies record page layout editing and navigation menu editing into a single global "layout customization" session. Dashboard editing stays separate. ### How it works **Two edit mode systems, one context-based read:** - `isLayoutCustomizationModeEnabledState` -- global atom for record pages + navigation - `isDashboardInEditModeComponentState` -- dashboard-only, independent per-component atom - `PageLayoutEditModeProvider` -- context that dispatches to `RecordPageLayoutEditModeProvider` (reads global atom) or `DashboardPageLayoutEditModeProvider` (reads component atom), one component per file **Session registry + independent atoms:** - `activeCustomizationPageLayoutIdsState` -- accumulates page layout IDs as user navigates during customization (`string[]`) - Save/cancel iterate the ID list and read each layout's draft/persisted atoms independently - Follows the same pattern as `settingsRoleIdsState` + `settingsDraftRoleFamilyState` **Unified UI:** - `LayoutCustomizationBar` replaces the old `NavigationMenuEditModeBar` - Enter once -- edit record layouts + navigation -- save/cancel everything together - `useSaveLayoutCustomization` orchestrates sequential save: navigation draft -- page layouts -- field widget groups - Error snackbar on partial save failure (with TODO for future atomic server mutation) **Draft protection during customization:** - `PageLayoutRelationWidgetsSyncEffect` guarded -- only updates persisted state from server, skips draft/currentLayouts while customization is active - `useExecuteTasksOnAnyLocationChange` skips draft reset when customization mode is enabled - Command execution blocked during layout customization ### Cleanup - Deleted `NavigationMenuEditModeBar`, `isNavigationMenuInEditModeState`, `isPageLayoutInEditModeComponentState`, `useIsGlobalLayoutCustomizationActive` - `DraftPageLayout` type changed from `Omit` to `Pick` (explicit fields) - Removed save/cancel from `DefaultRecordCommandMenuItemsConfig` (bar handles it now) - Extracted `useSaveFieldsWidgetGroups` from save orchestration - Split `PageLayoutEditModeProvider` into 3 separate files (one component per file, Twenty convention) ### Known issues - **Stale deleted widget after save (pre-existing on `main`)**: Delete widget -- save -- exit customization -- Apollo cache stale -- sync effect overwrites Jotai from stale data -- widget reappears until refresh. Separate PR needed, likely tied to the planned server-side `saveLayoutCustomization` atomic endpoint. ### Open questions - **Module location**: Layout customization hooks/states live in `/app` -- should they move to their own `modules/layout-customization/`? - **Atomic server mutation**: All save mutations are on metadata schema (`createNavigationMenuItem`, `deleteNavigationMenuItem`, `updateNavigationMenuItem`, `updatePageLayoutWithTabsAndWidgets`, `upsertFieldsWidget`). A single `saveLayoutCustomization` endpoint could make saves truly atomic. https://github.com/user-attachments/assets/036ef542-97f3-485b-a68f-3726002c81fb |
||
|
|
f1dfcfd163 |
refactor(twenty-front): reorganize NavigationMenuItem module into common/display/edit subfolders (#18691)
## Summary - Reorganizes the `navigation-menu-item` frontend module from a flat structure into `common/`, `display/`, and `edit/` subfolders with type-specific subdirectories (`link/`, `folder/`, `object/`, `view/`, `record/`) - Every file is now in a leaf folder describing its type: `components/`, `hooks/`, `utils/`, `types/`, or `constants/` - Moves 14 NavigationMenuItem-related components out of `object-metadata/` and `side-panel/pages/` into the `navigation-menu-item` module where they belong - Creates type-specific display utility functions (e.g., `getLinkNavigationMenuItemLabel`, `getObjectNavigationMenuItemComputedLink`) to replace generic switch-based functions - Unifies the Favorites section drag-and-drop from `@hello-pangea/dnd` to `@dnd-kit/react`, matching the Workspace section's DnD library - Renames Favorites section components from `CurrentWorkspaceMember*` to `Favorites*` for clarity - Deletes unused `FavoritesDragDropProviderContent` and `NavbarDragProvider` ## Test plan - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx lint:diff-with-main twenty-front` passes (0 warnings, 0 errors) - [x] `npx nx test twenty-front` passes (763 suites, 4467 tests) - [ ] Verify favorites drag-and-drop still works in the UI (reorder items, move between folders) - [ ] Verify workspace edit mode drag-and-drop still works - [ ] Verify "add to navigation" drag from command menu/side panel still works |
||
|
|
c1da7be6d7 |
Billing for self-hosts (#18075)
## Summary Implements enterprise licensing and per-seat billing for self-hosted environments, with Stripe as the single source of truth for subscription data. ### Components - **twenty-website** hosts the private key to sign `ENTERPRISE_KEY` and `ENTERPRISE_VALIDITY_TOKEN`. It communicates with Stripe to emit the daily `ENTERPRISE_VALIDITY_TOKEN` if the subscription is active, based on the user's Stripe subscription ID stored in `ENTERPRISE_KEY`. - **Stripe** is the single source of truth for subscription data (status, seats, billing). - **The client** (twenty-server + DB + workers) saves `ENTERPRISE_KEY` in the `keyValuePair` table (or `.env` if `IS_CONFIG_VARIABLES_IN_DB_ENABLED` is false) and the daily-renewed `ENTERPRISE_VALIDITY_TOKEN` in the `appToken` table. `ENTERPRISE_VALIDITY_TOKEN` is verified client-side using a public key to grant access to enterprise features (RLS, SSO, audit logs, etc.). ### Flow 1. When requesting an upgrade to an enterprise plan (from **Enterprise** in settings), the user is shown a modal to choose monthly/yearly billing, then redirected to Stripe to enter payment details. After checkout, they land on twenty-website where they are exposed to their `ENTERPRISE_KEY`, which they paste in the UI. It is saved in the `keyValuePair` table. On activation, a first `ENTERPRISE_VALIDITY_TOKEN` with 30-day validity is stored in the `appToken` table. 2. **Every day**, a cron job runs and does two things: - **Refreshes the validity token**: communicates with twenty-website to get a new `ENTERPRISE_VALIDITY_TOKEN` with 30-day validity if the Stripe subscription is still active. If the subscription is in cancellation, the emitted token has a validity equal to the cancellation date. If it's no longer valid, the token is not replaced. The cron only needs to run every 30 days in practice, but runs daily so it's resilient to occasional failures. - **Reports seat count**: counts active (non-soft-deleted) `UserWorkspace` entries and sends the count to twenty-website, which updates the Stripe subscription quantity with proration. Seats are also reported on first activation. If the subscription is canceled or scheduled for cancellation, the seat update is skipped. 3. `ENTERPRISE_VALIDITY_TOKEN` is verified server-side via a public key to grant access to enterprise features. ### Key concepts Three distinct checks are exposed as GraphQL fields on `Workspace`: | Field | Meaning | |---|---| | `hasValidEnterpriseKey` | Has any valid enterprise key (signed JWT **or** legacy plain string) | | `hasValidSignedEnterpriseKey` | `ENTERPRISE_KEY` is a properly signed JWT (billing portal makes sense) | | `hasValidEnterpriseValidityToken` | `ENTERPRISE_VALIDITY_TOKEN` is present and not expired (expiration depends on signed token payload, not on "expiresAt" on appToken table which is only indicative) | Feature access is gated by `isValid()` = `hasValidEnterpriseValidityToken || hasValidEnterpriseKey` (to support both new and legacy keys during transition). After transition isValid() = hasValidEnterpriseValidityToken ### Frontend states The Enterprise settings page handles multiple states: - **No key**: show "Get Enterprise" with checkout modal - **Orphaned validity token** (token valid but no signed key): prompt user to set a valid enterprise key - **Active/trialing but no validity token**: show subscription status with a "Reload validity token" action - **Active/trialing**: show full subscription info, billing portal access, cancel option - **Cancellation scheduled**: show cancellation date, billing portal - **Canceled**: show billing history link and option to start a new subscription - **Past due / Incomplete**: prompt to update payment or restart ### Temporary retro-compatibility: legacy plain-text keys Previously, enterprise features were gated by a simple check: any non-empty string in `ENTERPRISE_KEY` granted access. With this PR, we transition to a controlled system relying on signed JWTs. To avoid breaking existing self-hosted users: - **Legacy plain-text keys still grant access** to enterprise features. `hasValidEnterpriseKey` returns `true` for both signed JWTs and plain strings, and `isValid()` checks `hasValidEnterpriseKey` as a fallback when no validity token is present. - **A deprecation banner** is shown at the top of the app when `hasValidEnterpriseKey` is `true` but `hasValidSignedEnterpriseKey` is `false`, informing the user that their key format is deprecated and they should activate a new signed key. - **No billing portal or subscription management** is available for legacy keys since there is no Stripe subscription to manage. This retro-compatibility will be removed in a future version. At that point, `isValid()` will only check `hasValidEnterpriseValidityToken`. ### Edge cases - **Air-gapped / production environments**: for self-hosted clients that block external traffic (or for our own production), provide a long-lived `ENTERPRISE_VALIDITY_TOKEN` (e.g. 99 years) directly in the `appToken` table, with no `ENTERPRISE_KEY`. The daily cron will skip the refresh (no enterprise key to authenticate with), but the pre-seeded validity token will be used to grant feature access. No billing or seat reporting occurs in this mode. - **`IS_CONFIG_VARIABLES_IN_DB_ENABLED` is false**: if the user tries to activate an enterprise key but DB config writes are disabled, the backend returns a clear error asking them to add `ENTERPRISE_KEY` to their `.env` file manually. - **Canceled subscriptions**: the `/seats` endpoint skips Stripe updates for canceled or cancellation-scheduled subscriptions to avoid Stripe API errors. ### How to test - launch twenty-website on a different url (eg localhost:1002) - add ENTERPRISE_API_URL=http://localhost:3002/api/enterprise (or else) in your server .env - ask me for twenty-website's .env file content (STRIPE_SECRET_KEY; STRIPE_ENTERPRISE_MONTHLY_PRICE_ID;STRIPE_ENTERPRISE_YEARLY_PRICE_ID; ENTERPRISE_JWT_PRIVATE_KEY; ENTERPRISE_JWT_PUBLIC_KEY; NEXT_PUBLIC_WEBSITE_URL) - visit Admin panel / enterprise |
||
|
|
6e628bb751 |
Fix add more tab bottom separator (#18529)
Adding a full height prop Before <img width="409" height="139" alt="Capture d’écran 2026-03-10 à 14 06 13" src="https://github.com/user-attachments/assets/6f9bb8d1-8834-44b9-a0cf-1253dffc9ea0" /> After <img width="409" height="139" alt="Capture d’écran 2026-03-10 à 14 06 33" src="https://github.com/user-attachments/assets/1d3551fe-0b4b-4bbf-88e4-fd466c9bb704" /> |
||
|
|
dd58eb6814 |
Fix linaria css regressions (#18492)
before <img width="310" height="60" alt="SCR-20260309-ctul" src="https://github.com/user-attachments/assets/7141d495-b8f2-4fd6-bf3b-36bb2b11d1aa" /> after (fixed icon alignment) <img width="235" height="67" alt="SCR-20260309-ctel" src="https://github.com/user-attachments/assets/36078039-93dc-4c2c-b553-0bbcde4cb81c" /> before <img width="637" height="318" alt="SCR-20260309-ctnp" src="https://github.com/user-attachments/assets/34b66129-d619-43a2-8896-aa92b511644e" /> after (fixed chart colors) <img width="650" height="317" alt="SCR-20260309-cthj" src="https://github.com/user-attachments/assets/82c095b1-34bb-4ae4-a8f2-7a3746a31b0a" /> before <img width="909" height="650" alt="image" src="https://github.com/user-attachments/assets/14649aed-bfa8-4b9d-aa35-f4de2bfaddd6" /> after (fixed buttons text color) <img width="930" height="646" alt="SCR-20260309-csob" src="https://github.com/user-attachments/assets/c724a849-dabe-406c-8258-0674211374f2" /> before <img width="544" height="141" alt="image" src="https://github.com/user-attachments/assets/815c3b70-2f7c-42ca-8a32-3fbd5fe4c556" /> after (fixed missing border on :active state) <img width="554" height="145" alt="image" src="https://github.com/user-attachments/assets/845b1afd-36b6-4ae4-b6ef-c49ccbd89c10" /> --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
033d297695 |
feat: add visual time picker to DateTimePicker (#15057) (#17952)
## Summary Added a visual time picker dropdown for DateTime fields, replacing the previous text input. Users can now select hours and minutes through an intuitive scrollable interface. (Fixes #15057 ## Changes - **New component**: Add a `TimePickerDropdown` - Visual picker with scrollable hour/minute columns - **Updated**: `DateTimePickerHeader` - Implemented time picker dropdown in `DateTimePickerHeader` and Move month/year picker to right side ## Snapshots <img width="493" height="421" alt="image" src="https://github.com/user-attachments/assets/3bd1f0a0-0ac2-473d-935e-d9f28b0e40e2" /> https://github.com/user-attachments/assets/daa5cba5-c86c-46aa-a634-0f5c04523af1 **If there is no enough place at right, auto-move month/year selector to the left side** Hi, @Bonapara I followed the Figma you shared to complete this feature. Could you please review it for me? Thanks a lot. --------- Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> |
||
|
|
1656bb5568 |
[Feat] : add source to actor fields (#18118)
fixes #18099 Simple implementation of the matchingSourceValues to be searched in the ACTOR case in turnRecordFilterIntoRecordGqlOperationFilter <img width="1239" height="494" alt="Screenshot 2026-02-20 at 5 21 14 PM" src="https://github.com/user-attachments/assets/20ee076e-dccd-4747-a1ab-38d649f6591e" /> --------- Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> |
||
|
|
ef499b6d47 |
Re-enable disabled lint rules and right-size CI runners (#18461)
## Summary - Re-enable one lint rule that was temporarily disabled during the ESLint-to-Oxlint migration: - **`twenty/sort-css-properties-alphabetically`** in twenty-front — 578 violations auto-fixed across 390 files - Document why **`typescript/consistent-type-imports`** cannot be auto-fixed in twenty-server: NestJS relies on `emitDecoratorMetadata` for DI, so converting constructor parameter imports to `import type` erases them at compile time and breaks dependency injection at runtime - Right-size CI runners, reducing 8-core usage from 18 jobs to 3: | Change | Jobs | Rationale | |--------|------|-----------| | **Keep 8-core** | `ci-merge-queue/e2e-test`, `ci-front/front-sb-build`, `ci-front/front-build` | Heavy builds needing max CPU + memory (10GB NODE_OPTIONS, full Storybook webpack bundling) | | **8-core → 4-core** | `ci-server` (build, lint-typecheck, validation, test, integration-test), `ci-front/front-sb-test`, `ci-zapier/server-setup`, `ci-sdk/sdk-e2e-test` | Already sharded into 10-12 parallel instances, I/O-bound (DB/Redis), or moderate single builds | | **8-core → 2-core** | `ci-emails/emails-test` | Trivially lightweight (build + curl health check) | | **Removed** | `ci-front/front-chromatic-deployment` | Dead code — permanently disabled with `if: false` | - Fix merge queue CI issues: - **Concurrency**: Use `merge_group.base_ref` instead of unique merge group ref so new queue entries cancel previous runs - **Required status checks**: Add `merge_group` trigger to all 6 required CI workflows (front, server, shared, website, docker-compose, sdk) with `changed-files-check` auto-skipped for merge_group events — status check jobs auto-pass without re-running full CI - **Build caching**: Add Nx build cache restore/save to E2E test job with fallback to `main` branch cache for faster frontend and server builds ## Test plan - [ ] CI passes on this PR (verifies lint rule auto-fix works) - [ ] Verify 4-core runner jobs complete within their 30-minute timeouts - [ ] Verify merge queue status checks auto-pass (ci-front-status-check, ci-server-status-check, etc.) - [ ] Verify merge queue E2E concurrency cancels previous runs when a new PR enters the queue |
||
|
|
364c944ca6 |
Improve build performance 2x (#18449)
## Summary Front Before: <img width="1199" height="670" alt="image" src="https://github.com/user-attachments/assets/b978f67c-c0a6-49fc-bedd-a443f11c365d" /> Front After: <img width="1199" height="670" alt="image" src="https://github.com/user-attachments/assets/a4939dbb-a8b4-4c74-978c-daa7f27d00f3" /> Server Before: <img width="1199" height="670" alt="image" src="https://github.com/user-attachments/assets/da53e97f-ec65-4224-a656-ca41040aef6e" /> Server After: <img width="1199" height="670" alt="image" src="https://github.com/user-attachments/assets/8cdf3885-f515-4d6c-989f-a421a4e8206c" /> ### CI Server Pipeline Restructuring - Split monolithic `server-setup` job into three parallel jobs: `server-build`, `server-lint-typecheck`, and `server-validation` - `server-build` only handles build + Nx cache save (~1m vs old 3.5m), unblocking downstream jobs faster - `server-lint-typecheck` runs in parallel with no DB dependency - `server-validation` handles DB setup, migration checks, and GraphQL generation checks in parallel with tests - Make `server-test` (unit tests) fully independent — no longer waits for server-setup, builds its own artifacts - Increase integration test shards from 8 to 10 for better parallelism - Expected critical path reduction: ~10m → ~7m (~30% faster) ### CI Front Pipeline Improvements - Use artifact upload/download for storybook build instead of rebuilding in test shards - Serve pre-built storybook via `http-server` in test jobs, with `STORYBOOK_URL` env var - Update `vitest.config.ts` to use `storybookUrl` when `STORYBOOK_URL` is set - Remove redundant `twenty-shared`, `twenty-ui`, `twenty-sdk` builds from storybook test shards ### Vite Build Optimizations - Conditionally enable `rollup-plugin-visualizer` behind `ANALYZE=true` env var (not loaded by default) - Broaden Istanbul coverage exclusions to skip test files, stories, mocks, and decorators - Remove `@tabler/icons-react` alias from twenty-front and storybook configs - Bundle `@tabler/icons-react` into twenty-ui instead of treating it as an external dependency - Add lazy loading with `React.lazy` + `Suspense` for all page-level route components in `useCreateAppRouter` |
||
|
|
9d57bc39e5 |
Migrate from ESLint to OxLint (#18443)
## Summary Fully replaces ESLint with OxLint across the entire monorepo: - **Replaced all ESLint configs** (`eslint.config.mjs`) with OxLint configs (`.oxlintrc.json`) for every package: `twenty-front`, `twenty-server`, `twenty-emails`, `twenty-ui`, `twenty-shared`, `twenty-sdk`, `twenty-zapier`, `twenty-docs`, `twenty-website`, `twenty-apps/*`, `create-twenty-app` - **Migrated custom lint rules** from ESLint plugin format to OxLint JS plugin system (`@oxlint/plugins`), including `styled-components-prefixed-with-styled`, `no-hardcoded-colors`, `sort-css-properties-alphabetically`, `graphql-resolvers-should-be-guarded`, `rest-api-methods-should-be-guarded`, `max-consts-per-file`, and Jotai-related rules - **Migrated custom rule tests** from ESLint `RuleTester` + Jest to `oxlint/plugins-dev` `RuleTester` + Vitest - **Removed all ESLint dependencies** from `package.json` files and regenerated lockfiles - **Updated Nx targets** (`lint`, `lint:diff-with-main`, `fmt`) in `nx.json` and per-project `project.json` to use `oxlint` commands with proper `dependsOn` for plugin builds - **Updated CI workflows** (`.github/workflows/ci-*.yaml`) — no more ESLint executor - **Updated IDE setup**: replaced `dbaeumer.vscode-eslint` with `oxc.oxc-vscode` extension, configured `source.fixAll.oxc` and format-on-save with Prettier - **Replaced all `eslint-disable` comments** with `oxlint-disable` equivalents across the codebase - **Updated docs** (`twenty-docs`) to reference OxLint instead of ESLint - **Renamed** `twenty-eslint-rules` package to `twenty-oxlint-rules` ### Temporarily disabled rules (tracked in `OXLINT_MIGRATION_TODO.md`) | Rule | Package | Violations | Auto-fixable | |------|---------|-----------|-------------| | `twenty/sort-css-properties-alphabetically` | twenty-front | 578 | Yes | | `typescript/consistent-type-imports` | twenty-server | 3814 | Yes | | `twenty/max-consts-per-file` | twenty-server | 94 | No | ### Dropped plugins (no OxLint equivalent) `eslint-plugin-project-structure`, `lingui/*`, `@stylistic/*`, `import/order`, `prefer-arrow/prefer-arrow-functions`, `eslint-plugin-mdx`, `@next/eslint-plugin-next`, `eslint-plugin-storybook`, `eslint-plugin-react-refresh`. Partial coverage for `jsx-a11y` and `unused-imports`. ### Additional fixes (pre-existing issues exposed by merge) - Fixed `EmailThreadPreview.tsx` broken import from main rename (`useOpenEmailThreadInSidePanel`) - Restored truthiness guard in `getActivityTargetObjectRecords.ts` - Fixed `AgentTurnResolver` return types to match entity (virtual `fileMediaType`/`fileUrl` are resolved via `@ResolveField()`) ## Test plan - [x] `npx nx lint twenty-front` passes - [x] `npx nx lint twenty-server` passes - [x] `npx nx lint twenty-docs` passes - [x] Custom oxlint rules validated with Vitest: `npx nx test twenty-oxlint-rules` - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx typecheck twenty-server` passes - [x] CI workflows trigger correctly with `dependsOn: ["twenty-oxlint-rules:build"]` - [x] IDE linting works with `oxc.oxc-vscode` extension |
||
|
|
4797f97a95 |
fix: vertical alignment of +N More tab overflow button (#18446)
## Summary
- Add `align-items: center` to tab list `StyledContainer` so the
overflow button aligns vertically with tabs
- Remove ineffective `> * { height }` hack from `TabMoreButton` (was
being reset by `all: unset` in `StyledTabButton`)
## Test plan
- Open a record detail page with enough tabs to trigger the "+N More"
overflow
- Verify the overflow button is vertically centered with the visible
tabs
|
||
|
|
c53a13417e |
Remove all styled(Component) patterns in favor of parent wrappers and props (#18430)
## Summary
Eliminates all ~350 `styled(Component)` usages across `twenty-front` and
`twenty-ui` (212 files changed). Each was replaced following these
rules:
- **Margin/layout CSS** (margin, padding, flex, align-self, width) →
wrapped in a `styled.div`/`styled.span` parent container
- **Third-party components** (Link, TextareaAutosize,
ReactPhoneNumberInput, Handle, etc.) → parent container with child CSS
selectors (`> a`, `> textarea`, `> input`, etc.)
- **Intrinsic behavior via existing props** (TableRow
`gridTemplateColumns`, TableCell `color`/`align`) → replaced
`styled(TableRow)` / `styled(TableCell)` with direct prop usage
- **Other visual overrides on twenty-ui components** (Card, Section,
TabList, Button, MenuItem, ScrollWrapper, etc.) → parent wrappers with
`> div` / `> *` child selectors
- **Extending styled.div/span** → merged all CSS into a single
`styled.div`/`styled.span`
Also adds `overflow: hidden` to parent containers wrapping
`ScrollWrapper` so scroll activates correctly with the new wrapper
structure.
### Migration patterns
| Before | After |
|--------|-------|
| `styled(Avatar)` with `margin-right` | `<StyledAvatarContainer><Avatar
/></StyledAvatarContainer>` |
| `styled(Link)` with `text-decoration: none` |
`<StyledLinkContainer><Link /></StyledLinkContainer>` with `> a { ... }`
|
| `styled(TableRow)` with `grid-template-columns` | `<TableRow
gridTemplateColumns="..." />` |
| `styled(TableCell)` with `color` / `align` | `<TableCell color={...}
align="right" />` |
| `styled(Card)` with `margin-top` | `<StyledCardContainer><Card
/></StyledCardContainer>` |
| `styled(TabList)` with `background` |
`<StyledTabListContainer><TabList /></StyledTabListContainer>` with `>
div { ... }` |
| `styled(StyledBase)` extending a `styled.div` | Single merged
`styled.div` with all styles inlined |
|
||
|
|
5853891b02 | refactor!: rename Command Menu page/navigation layer to Side Panel (#18393) | ||
|
|
647c32ff3e |
Deprecate runtime theme objects in favor of CSS variables (#18402)
## Summary
- **Eliminate `ICON_SIZES` / `ICON_STROKES` constants**: all icon
dimensions are now resolved at runtime via
`resolveThemeVariableAsNumber(themeCssVariables.icon.size.X)`, ensuring
values always come from computed CSS variables
- **No more consumer imports from `twenty-ui/theme`**: moved
`ColorSchemeContext`, `ColorSchemeProvider`, `ThemeColor`,
`MAIN_COLOR_NAMES`, `getNextThemeColor`, `AnimationDuration` to
`twenty-ui/theme-constants`
- **Remove `ThemeContext` / `ThemeContextProvider` / `ThemeProvider` /
`ThemeType`**: replaced across ~300 files with `themeCssVariables` (for
CSS contexts) or `resolveThemeVariable` / `resolveThemeVariableAsNumber`
(for JS runtime values)
- **Simplify provider chain**: only `ColorSchemeProvider` remains — it
toggles `light`/`dark` class on `document.documentElement` and provides
`colorScheme` via React context
- **Fix pre-existing test failures**: `useIcons.test.ts`
(non-configurable ES module spy) and
`turnRecordFilterGroupIntoGqlOperationFilter.test.ts`
(`Omit<RecordFilter, 'id'>` type mismatch)
### Theme access pattern (before → after)
| Context | Before | After |
|---------|--------|-------|
| CSS (Linaria) | `${({ theme }) => theme.font.color.primary}` |
`${themeCssVariables.font.color.primary}` |
| JS runtime (icon size, animation) | `theme.icon.size.md` /
`ICON_SIZES.md` |
`resolveThemeVariableAsNumber(themeCssVariables.icon.size.md)` |
| Color scheme check | `theme.name === 'dark'` |
`useContext(ColorSchemeContext).colorScheme === 'dark'` |
|
||
|
|
eda905f271 |
[DevXP] Improve Linaria pre-build speed (#18382)
## Summary This PR improves Linaria/WYW pre-build speed and continues the migration of `twenty-ui` components away from runtime `ThemeContext` reads toward static CSS variables and theme constants. ### Linaria/WYW profiling plugin improvements (`twenty-shared`) - **Babel JIT warmup**: added a `buildStart` warmup step that triggers WYW's Babel JIT compilation before the real build starts, so the first real file doesn't pay the cold-start penalty - **`configResolved` hook**: detects dev vs prod mode and resolves the correct warmup file path relative to `config.root` - **Dev-only per-file logging**: slow file warnings are now gated behind `isDevMode`, keeping production/CI build output clean - **`closeBundle` summary**: moved the final top-slow-files report to `closeBundle` for accurate end-of-build reporting - **Removed noisy progress interval logging** in favor of the warmup log + final summary ### Migration from `ThemeContext` to static CSS variables / constants Across `twenty-ui`, replaced runtime `useTheme()` reads with: - `themeCssVariables` CSS custom properties (colors, spacing) - Hard-coded design-system constants (`ICON.size.md` → `16`, `ICON.stroke.sm` → `1.6`) so components no longer need a React context at render time — enabling Linaria static extraction **Components migrated:** - `Button`, `AnimatedButton`, `LightButton`, `LightIconButton`, `AnimatedLightIconButton`, `ButtonIcon`, `ButtonSoon` - `ProgressBar` (Framer Motion width animation → CSS `transition`) - `Info`, `HorizontalSeparator`, `LinkChip` - `MenuPicker`, `MenuItemLeftContent`, `MenuItemIconWithGripSwap`, `NavigationBarItem` - `JsonArrow`, `JsonNestedNode` - `ModalHeader` ### Other - Added `aria-valuenow` to `ProgressBar` for accessibility - `VisibilityHidden` component updated to inline accessibility styles |
||
|
|
b11f77df2a |
[FRONT COMPONENTS] Introduce conditionalAvailabilityExpression to command menu items (#18319)
## PR Description - Uses `expr-eval` to enable front components (SDK plugins) to define conditional availability as declarative expressions. - Moves shared types and constants to `twenty-shared` - Introduces a `conditionalAvailabilityExpression` field on `CommandMenuItemEntity`, allowing command menu items to store an `expr-eval` compatible expression string that is evaluated against a CommandMenuContext to determine if the item should be shown. - Creates an esbuild transform plugin `conditional-availability-transform-plugin` in `twenty-sdk` that converts TypeScript conditional availability expressions into `expr-eval` compatible syntax at build time, so SDK developers can write natural TS expressions that get transformed to evaluable strings. - Removes deprecated `forceRegisteredActionsByKey` state and its usage. - Creates `useCommandMenuContext` hook that builds the full `CommandMenuContext` object from React state, which is then passed to `useCommandMenuItemFrontComponentActions` for evaluating conditional availability expressions. |
||
|
|
3b2bf39565 |
Refactor modal (#18377)
## Summary - Move Modal UI components (`Modal`, `ModalContent`, `ModalHeader`, `ModalFooter`, `ModalBackdrop`) from `twenty-front` to `twenty-ui` as stateless, reusable components - Create `ModalStatefulWrapper` in `twenty-front` that connects Jotai state (`isModalOpenedComponentState`) to the stateless `Modal` via an `isOpen` prop - Rename `modalVariant` prop to `overlay` with clearer values: `'dark'` (default), `'light'` (in-container), `'transparent'` (invisible panel). Remove unused `'medium'` overlay - Rename `modalId` to `modalInstanceId` across the entire modal zone (~30 consumer files) - Extract `ModalProps` to its own file in `twenty-ui/types/ModalProps.ts`; extract `ModalStatefulWrapperProps` to its own file using `Pick<ModalProps, ...>` for shared props - Extract `ModalBackdrop` to its own file and export from `twenty-ui`; use it in `UserOrMetadataLoader` instead of a local styled component - Use `ModalFooter` in `StepNavigationButton` and `ModalHeader` in `SpreadsheetImportStepperContainer` instead of duplicated `styled.div` definitions - Remove unused `onClose` prop from stateless `Modal`; fix `typeof document` guard in `ModalStatefulWrapper` - Split shared types into individual files: `ModalSize.ts`, `ModalPadding.ts`, `ModalOverlay.ts` - Extract wyw profiling instrumentation from `vite.config.ts` into reusable `createWywProfilingPlugin` with parametrized threshold and improved logging - Delete old `Modal.tsx`, `Modal.styles.ts`, `ModalContent.tsx`, `ModalHeader.tsx`, `ModalFooter.tsx` from `twenty-front` - Add comprehensive Storybook stories in `twenty-ui` covering Default, Confirmation, Small, ExtraLarge, Closed, and Interactive variants |
||
|
|
7a2e397ad1 |
Complete linaria migration (#18361)
## Summary
Completes the migration of the frontend styling system from **Emotion**
(`@emotion/styled`, `@emotion/react`) to **Linaria** (`@linaria/react`,
`@linaria/core`), a zero-runtime CSS-in-JS library where styles are
extracted at build time.
This is the final step of the migration — all ~494 files across
`twenty-front`, `twenty-ui`, `twenty-website`, and `twenty-sdk` are now
fully converted.
## Changes
### Styling Migration (across ~480 component files)
- Replaced all `@emotion/styled` imports with `@linaria/react`
- Converted runtime theme access patterns (`({ theme }) => theme.x.y`)
to build-time `themeCssVariables` CSS custom properties
- Replaced `useTheme()` hook (from Emotion) with
`useContext(ThemeContext)` where runtime theme values are still needed
(e.g., passing colors to non-CSS props like icon components)
- Removed `@emotion/react` `css` helper usages in favor of Linaria
template literals
### Dependency & Configuration Changes
- **Removed**: `@emotion/react`, `@emotion/styled` from root
`package.json`
- **Added**: `@wyw-in-js/babel-preset`, `next-with-linaria` (for
twenty-website SSR support)
- Updated Nx generator defaults from `@emotion/styled` to
`@linaria/react` in `nx.json`
- Simplified `vite.config.ts` (removed Emotion-specific configuration)
- Updated `twenty-website/next.config.js` to use `next-with-linaria` for
SSR Linaria support
### Storybook & Testing
- Removed `ThemeProvider` from Emotion in Storybook previews
(`twenty-front`, `twenty-sdk`)
- Now relies solely on `ThemeContextProvider` for theme injection
### Documentation
- Removed the temporary `docs/emotion-to-linaria-migration-plan.md`
(migration complete)
- Updated `CLAUDE.md` and `README.md` to reflect Linaria as the styling
stack
- Updated frontend style guide docs across all locales
## How it works
Linaria extracts styles at build time via the `@wyw-in-js/vite` plugin.
All expressions in `styled` template literals must be **statically
evaluable** — no runtime theme objects or closures over component state.
- **Static styles** use `themeCssVariables` which map to CSS custom
properties (`var(--theme-color-x)`)
- **Runtime theme access** (for non-CSS use cases like icon `color`
props) uses `useContext(ThemeContext)` instead of Emotion's `useTheme()`
|
||
|
|
3bfdc2c83f |
chore(twenty-front): migrate command-menu, workflow, page-layout and UI modules from Emotion to Linaria (PR 4-6/10) (#18342)
## Summary
Continues the Emotion → Linaria migration (PR 4-6 from the [migration
plan](docs/emotion-to-linaria-migration-plan.md)). Migrates **311
files** across four module groups:
| Module | Files |
|---|---|
| command-menu | 53 |
| workflow | 84 |
| page-layout | 84 |
| UI (partial - first ~80 files) | ~80 |
| twenty-ui (TEXT_INPUT_STYLE) | 1 |
| misc (hooks, keyboard-shortcut-menu, file-upload) | ~9 |
### Migration patterns applied
- `import styled from '@emotion/styled'` → `import { styled } from
'@linaria/react'`
- `import { useTheme } from '@emotion/react'` → `import { useContext }
from 'react'` + `import { ThemeContext } from 'twenty-ui/theme'`
- `${({ theme }) => theme.X.Y.Z}` → `${themeCssVariables.X.Y.Z}` (static
CSS variables)
- `theme.spacing(N)` → `themeCssVariables.spacing[N]`
- `styled(motion.div)` → `motion.create(StyledBase)` (11 components)
- `styled(Component)<TypeParams>` → wrapper div approach for non-HTML
elements
- Multi-declaration interpolations split into one CSS property per
interpolation
- Interpolation return types fixed (`&&` → ternary `? : ''`)
- `TEXT_INPUT_STYLE` converted from function to static string constant
(backward compatible)
- Emotion `<Global>` replaced with `useEffect` style injection
- Complex runtime-dependent styles use CSS custom properties via
`style={}` prop
### After this PR
- **Remaining files**: ~400 (object-record: ~160, settings: ~200, UI:
~44)
- **No breaking changes**: CSS variables resolve identically to the
previous Emotion theme values
|
||
|
|
802a5b0af6 |
chore(twenty-front): migrate auth, activities, AI, pages and small modules from Emotion to Linaria (PR 2-3/10) (#18328)
## Summary - Migrate ~200 files from `@emotion/styled` / `@emotion/react` to `@linaria/react` + `themeCssVariables`, continuing the zero-runtime CSS-in-JS migration (PR 2-3 of the [migration plan](docs/emotion-to-linaria-migration-plan.md)) - Modules covered: **auth** (19), **activities** (53), **ai** (30), **pages** (70), **action-menu** (3), **object-metadata** (4), **onboarding** (2), **workspace** (2), **file** (3), **error-handler** (2), **front-components** (1), **geo-map** (1), **loading** (5), **testing** (5), plus a `style` prop addition to `TableRow` - Handles `styled(FunctionComponent)<Props>` incompatibility with Linaria by using CSS custom properties via `style` + `var()` references ## Test plan - [x] `npx nx lint:diff-with-main twenty-front` passes - [x] `npx nx typecheck twenty-front` passes - [ ] Visual spot-check of auth, onboarding, settings, activities, and AI chat screens - [ ] No remaining `@emotion/styled` or `@emotion/react` imports in migrated files Made with [Cursor](https://cursor.com) |
||
|
|
c4140f85df |
chore(twenty-front): migrate small modules from Emotion to Linaria (PR 1/10) (#18314)
## Emotion → Linaria migration — PR 1 of 10
First batch of the `twenty-front` migration from Emotion (runtime
CSS-in-JS) to Linaria (zero-runtime, build-time extraction via
wyw-in-js). Covers **100 files** across 10 small standalone modules —
chosen as the lowest-risk starting point.
### Modules migrated
spreadsheet-import (28) · navigation-menu-item (17) · views (14) ·
billing (10) · blocknote-editor (7) · advanced-text-editor (7) ·
favorites (7) · navigation (4) · information-banner (3) ·
sign-in-background-mock (3)
### Migration pattern
Every file follows the same mechanical transformation:
| Emotion | Linaria |
|---|---|
| `import styled from '@emotion/styled'` | `import { styled } from
'@linaria/react'` |
| `${({ theme }) => theme.font.color.primary}` |
`${themeCssVariables.font.color.primary}` |
| `${({ theme }) => theme.spacing(4)}` |
`${themeCssVariables.spacing[4]}` |
| `const theme = useTheme()` | `const { theme } =
useContext(ThemeContext)` |
| `import { type Theme } from '@emotion/react'` | `import { type
ThemeType } from 'twenty-ui/theme'` |
`themeCssVariables` is a build-time object where every leaf is a
`var(--t-xxx)` CSS custom property reference, evaluated statically by
wyw-in-js. Runtime theme access (icon sizes, colors passed as props)
uses `useContext(ThemeContext)`.
### Gotchas encountered & fixed
- **Interpolation return types** — wyw-in-js requires `string | number`,
never `false`/`undefined`. Replaced `condition && 'css'` with `condition
? 'css' : ''`.
- **`css` tag inside `styled` templates** — Linaria `css` returns a
class name, not CSS text. Replaced with plain template strings.
- **`styled(Component)` needs `className`** — added `className` prop to
`NavigationDrawerSection`, `DropdownMenuItemsContainer`, and `Heading`.
- **`shouldForwardProp` not supported** — Linaria filters invalid DOM
props automatically for HTML elements. For custom components, used
wrapper divs where needed.
- **`FormFieldPlaceholderStyles`** — converted from Emotion `css`
function to a static string using `themeCssVariables`.
|
||
|
|
8a7a19f312 |
Improve test tooling (#18259)
## Summary Unifies test mocking tooling across Jest and Storybook, replaces handcrafted mock data with auto-generated server-fetched data, and restructures the mock data generation script for maintainability. ### Mock data generation - Split `generate-mock-data.ts` into three focused modules under `scripts/mock-data/`: - `utils.ts` — shared authentication, GraphQL client, and file writer - `generate-metadata.ts` — fetches object metadata from `/metadata` - `generate-record-data.ts` — fetches record data from `/graphql` using metadata-driven dynamic queries - The orchestrator (`generate-mock-data.ts`) authenticates once and passes the token to both generators - Company records are now fetched from the actual server (limited to 10 records) instead of being handcrafted - Generated files are organized under `generated/metadata/objects/` and `generated/data/companies/` ### Unified test utilities - Consolidated Jest and MSW mocking into shared utilities that compose production code (`prefillRecord`, `getRecordNodeFromRecord`, `getRecordConnectionFromRecords`) with mock metadata - Renamed `generateEmptyJestRecordNode` → `generateMockRecordNode` and moved to `testing/utils/` - Extracted `generateMockRecordConnection` into its own file - Removed `sanitizeInputForPrefill` workaround (no longer needed with correctly shaped generated data) |
||
|
|
bc4ae35bc4 |
Rework atom naming (#18240)
## Summary - **Enhanced the `matching-state-variable` ESLint rule** to enforce consistent naming for `ComponentState`, `FamilyState`, and `ComponentFamilyState` hooks — previously it only covered `useAtomState` and `useAtomStateValue` - **The rule now checks 12 hooks** across three categories: value hooks (`useAtomComponentStateValue`, `useAtomFamilyStateValue`, etc.), state hooks (`useAtomComponentState`, `useAtomComponentFamilyState`), and setter hooks (`useSetAtomState`, `useSetAtomComponentState`, `useSetAtomFamilyState`, `useSetAtomComponentFamilyState`) - **Fixed all 225 resulting lint violations** across 151 files, renaming variables to match their state atom names (e.g. `currentViewId` → `contextStoreCurrentViewId`, `selectedRecord` → `recordStore`). Cases where the same state is accessed with different family keys/instance IDs are suppressed with `eslint-disable-next-line`. ## Naming convention | Hook | State argument | Valid | Invalid | |------|---------------|-------|---------| | `useAtomStateValue` | `fooState` | `const foo = ...` | `const bar = ...` | | `useAtomComponentStateValue` | `fooComponentState` | `const foo = ...` | `const bar = ...` | | `useAtomFamilyStateValue` | `fooFamilyState` | `const foo = ...` | `const bar = ...` | | `useAtomComponentFamilyStateValue` | `fooComponentFamilyState` | `const foo = ...` | `const bar = ...` | | `useAtomState` | `fooState` | `const [foo, setFoo] = ...` | `const [bar, setBar] = ...` | | `useAtomComponentState` | `fooComponentState` | `const [foo, setFoo] = ...` | `const [bar, setBar] = ...` | | `useAtomComponentFamilyState` | `fooComponentFamilyState` | `const [foo, setFoo] = ...` | `const [bar, setBar] = ...` | | `useSetAtomState` | `fooState` | `const setFoo = ...` | `const setBar = ...` | | `useSetAtomComponentState` | `fooComponentState` | `const setFoo = ...` | `const setBar = ...` | | `useSetAtomFamilyState` | `fooFamilyState` | `const setFoo = ...` | `const setBar = ...` | | `useSetAtomComponentFamilyState` | `fooComponentFamilyState` | `const setFoo = ...` | `const setBar = ...` | |
||
|
|
50be97422d | Fix dropdown scroll by restoring scroll container nesting order (#18239) | ||
|
|
121788c42f |
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`). |
||
|
|
d81f006979 |
Rename Jotai state hooks and utilities to consistent Atom-based naming (#18209)
## Summary Rename all Jotai-based state management hooks and utilities to a consistent naming convention that: 1. Removes legacy Recoil naming (`useRecoilXxxV2`, `createXxxV2`) 2. Always includes `Atom` in the name to distinguish from jotai native hooks 3. Follows a consistent ordering: `Atom` → `Component` → `Family` → `Type` (`State`/`Selector`) 4. Includes the type qualifier (`State` or `Selector`) in all value-reading hooks to avoid naming conflicts with jotai's native `useAtomValue` ## Naming Convention **Hooks**: `use[Set]Atom[Component][Family][State|Selector][Value|State|CallbackState]` **Utils**: `createAtom[Component][Family][State|Selector]` ## Changes ### Hooks renamed (definition files + all usages across ~1,500 files): | Old Name (Recoil) | Intermediate (Atom) | Final Name | |---|---|---| | `useRecoilValueV2` | `useAtomValue` | **`useAtomStateValue`** | | `useRecoilStateV2` | `useAtomState` | `useAtomState` | | `useSetRecoilStateV2` | `useSetAtomState` | `useSetAtomState` | | `useFamilyRecoilValueV2` | `useFamilyAtomValue` | **`useAtomFamilyStateValue`** | | `useSetFamilyRecoilStateV2` | `useSetFamilyAtomState` | **`useSetAtomFamilyState`** | | `useFamilySelectorValueV2` | `useFamilySelectorValue` | **`useAtomFamilySelectorValue`** | | `useFamilySelectorStateV2` | `useFamilySelectorState` | **`useAtomFamilySelectorState`** | | `useRecoilComponentValueV2` | `useAtomComponentValue` | **`useAtomComponentStateValue`** | | `useRecoilComponentStateV2` | `useAtomComponentState` | `useAtomComponentState` | | `useSetRecoilComponentStateV2` | `useSetAtomComponentState` | `useSetAtomComponentState` | | `useRecoilComponentFamilyValueV2` | `useAtomComponentFamilyValue` | **`useAtomComponentFamilyStateValue`** | | `useRecoilComponentFamilyStateV2` | `useAtomComponentFamilyState` | `useAtomComponentFamilyState` | | `useSetRecoilComponentFamilyStateV2` | `useSetAtomComponentFamilyState` | `useSetAtomComponentFamilyState` | | `useRecoilComponentSelectorValueV2` | `useAtomComponentSelectorValue` | `useAtomComponentSelectorValue` | | `useRecoilComponentFamilySelectorValueV2` | `useAtomComponentFamilySelectorValue` | `useAtomComponentFamilySelectorValue` | | `useRecoilComponentStateCallbackStateV2` | `useAtomComponentStateCallbackState` | `useAtomComponentStateCallbackState` | | `useRecoilComponentSelectorCallbackStateV2` | `useAtomComponentSelectorCallbackState` | `useAtomComponentSelectorCallbackState` | | `useRecoilComponentFamilyStateCallbackStateV2` | `useAtomComponentFamilyStateCallbackState` | `useAtomComponentFamilyStateCallbackState` | | `useRecoilComponentFamilySelectorCallbackStateV2` | `useAtomComponentFamilySelectorCallbackState` | `useAtomComponentFamilySelectorCallbackState` | ### Utilities renamed: | Old Name (Recoil) | Final Name | |---|---| | `createStateV2` | **`createAtomState`** | | `createFamilyStateV2` | **`createAtomFamilyState`** | | `createSelectorV2` | **`createAtomSelector`** | | `createFamilySelectorV2` | **`createAtomFamilySelector`** | | `createWritableSelectorV2` | **`createAtomWritableSelector`** | | `createWritableFamilySelectorV2` | **`createAtomWritableFamilySelector`** | | `createComponentStateV2` | **`createAtomComponentState`** | | `createComponentFamilyStateV2` | **`createAtomComponentFamilyState`** | | `createComponentSelectorV2` | **`createAtomComponentSelector`** | | `createComponentFamilySelectorV2` | **`createAtomComponentFamilySelector`** | ## Details - All definition files renamed to match new convention - All import paths and usages updated across the entire `twenty-front` package - Jotai's native `useAtomValue` is aliased as `useJotaiAtomValue` in the wrapper hook to avoid collision - Legacy Recoil files in `state/utils/` and `component-state/utils/` left untouched (separate naming scope) - Typecheck passes cleanly |
||
|
|
eaf9fe27b2 | Improve Jotai work (#18205) | ||
|
|
0f2af6a6cb |
Jotai 13 (#18178)
## Recoil to Jotai Migration — PR 13: Workflow, Page Layout, AI, Activities, Settings, SSE & Remaining Modules ### Summary This is the 13th PR in the [14-PR migration series](packages/twenty-front/src/modules/ui/utilities/state/jotai/MIGRATION_PLAN.md) to replace Recoil with Jotai as the state management library in `twenty-front`. This PR migrates the remaining consumer code across all modules — updating ~1,076 files with ~10k insertions/deletions. ### What changed **New Jotai infrastructure (13 new files):** - `createComponentSelectorV2` and `createComponentFamilySelectorV2` — instance-scoped derived atoms, replacing Recoil's component selectors - 6 new hooks: `useRecoilComponentSelectorValueV2`, `useRecoilComponentFamilySelectorValueV2`, `useRecoilComponentSelectorCallbackStateV2`, `useRecoilComponentFamilySelectorCallbackStateV2`, `useRecoilComponentStateCallbackStateV2`, `useRecoilComponentFamilyStateCallbackStateV2` - New types: `ComponentSelectorV2`, `ComponentFamilySelectorV2`, `SelectorCallbacksV2` - Extended `buildGetHelper` to support the new selector patterns **State definition migrations:** - `createState()` → `createStateV2()` (Jotai `atom()`) - `createFamilyState()` → `createFamilyStateV2()` (Jotai `atom()` with family cache) - Recoil `selector`/`selectorFamily` → Jotai derived `atom()` via V2 utilities **Hook migrations (~2,400 removed, ~1,545 added V2 equivalents):** - `useRecoilValue` → `useRecoilValueV2` - `useRecoilState` → `useRecoilStateV2` - `useSetRecoilState` → `useSetRecoilStateV2` - `useRecoilCallback` → `useCallback` + `jotaiStore.get/set` - `useRecoilComponentValue` → `useRecoilComponentValueV2` - `useSetRecoilComponentState` → `useSetRecoilComponentStateV2` - `useRecoilComponentFamilyValue` → `useRecoilComponentFamilyValueV2` / `useFamilySelectorValueV2` - ~397 direct `import from 'recoil'` removed **Deleted files (9 files):** - `dateLocaleState.ts` — consolidated - `currentAIChatThreadTitleStateV2.ts` — renamed to replace the original - `isCommandMenuOpenedState.ts` — removed - `filesFieldUploadState.ts` — replaced by V2 - `recordStoreFamilySelector.ts`, `recordStoreFieldValueSelector.ts`, `recordStoreRecordsSelector.ts` — replaced by V2 equivalents - `settingsAllRolesSelector.ts` — replaced by `useSettingsAllRoles` hook - `settingsRoleIdsStateV2.ts` — consolidated **Modules migrated:** - Workflow (diagram, steps, variables, filters) - Page Layout (widgets, fields, graph, tabs) - AI (chat, agents, browsing context) - Activities (rich text editor, targets, timeline) - Action Menu (single/multiple record actions) - Command Menu (navigation, history, pages, workflow) - Context Store - Record Board, Record Table, Record Calendar, Record Index - Record Field, Record Filter, Record Sort, Record Group - Record Drag, Record Picker, Record Store - Views (view bar, view picker, filters, sorts) - Settings (roles, permissions, SSO, security, data model, developers, domains) - SSE (event streams, subscriptions) - Spreadsheet Import - Auth, Favorites, Front Components, Information Banner |
||
|
|
530f74e28c |
Migrate more to Jotai (#18087)
Continue jotai migration |
||
|
|
1a13258302 |
Keep migrating to jotai (#18064)
And we continue! |