96f3ff0e9051002d2fb04e6b513c1d34fe02ed0c
19 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
96f3ff0e90 |
Add record table widget to dashboards (#18747)
## Demo https://github.com/user-attachments/assets/584de452-544a-41f8-ae9f-4be9e9d0cd9f ## Problem - Dashboards only supported chart widgets — tabular record data had no inline widget type - `RecordTable` was tightly coupled to the record index: HTML IDs, CSS variables, and hover portals were global strings with no per-instance scoping, so multiple tables on the same page would collide - `updateRecordTableCSSVariable`, `RECORD_TABLE_HTML_ID`, and cell portal IDs were hardcoded — placing two tables caused hover portals and CSS column widths to bleed across instances - Grid drag-select captured record UUIDs as cell IDs, producing `NaN` layout coordinates and a full-page freeze on second widget creation ## Fix - `RECORD_TABLE` is now a valid widget type across the full stack — server DTOs, DB enum migration, universal config mapping, GraphQL codegen, shared types (`RecordTableConfigurationDto`, `WidgetType`, `addRecordTableWidgetType` migration) - A record table widget can be placed on a dashboard and boots from a View ID with no record index dependency — `StandaloneRecordTableProvider` + `StandaloneRecordTableViewLoadEffect` (wraps existing `RecordTableWithWrappers` unchanged) - Selecting a data source auto-creates a dedicated View with up to 6 initial fields; switching source or deleting the widget cleans up the View — `useCreateViewForRecordTableWidget` + `useDeleteViewForRecordTableWidget` - The settings panel exposes source, field visibility/reorder, filter conditions, sort rules, and editable widget title — `SidePanelPageLayoutRecordTableSettings` + sub-pages, matching chart widget pattern - Filters, sorts, and aggregate operations update the table in real time but only persist to the View on explicit dashboard save — `useSaveRecordTableWidgetsViewDataOnDashboardSave` (diff + flush on save) - Headers are always non-interactive (no dropdown, no cursor pointer); columns are resizable only in edit mode; cells are non-editable in both modes — `isRecordTableColumnHeadersReadOnlyComponentState`, `isRecordTableColumnResizableComponentState`, `isRecordTableCellsNonEditableComponentState` (Jotai component states) - Hover portals and CSS column widths no longer bleed between multiple table widgets — `getRecordTableHtmlId(tableId)`, `getRecordTableCellId(tableId, …)`, `updateRecordTableCSSVariable(tableId, …)` scope all DOM IDs and CSS variables per instance - Clicking inside a widget's content area no longer opens the settings panel — `WidgetCardContent` stops click propagation when editable, limiting settings-open to the card header and chrome - Second widget creation no longer freezes the page — `PageLayoutGridLayout` drag-select filters by `cell-` prefix to exclude record UUIDs from grid cell detection ## Follow-up fixes **Widget save flow** - Saving a dashboard silently dropped record table widget changes (column visibility, order, filters, sorts, aggregates) because widget data save was bundled inside the layout save and only ran when layout structure changed - Widget data now persists independently via `useSavePageLayoutWidgetsData`, called in all save paths (dashboard save, record page save, layout customization save); saves are also skipped when nothing has changed **Drag-and-drop / checkbox columns in widget** - Record table widgets showed the drag handle column and checkbox selection column even though row reordering and multi-select are meaningless in a read-only widget - Two new component states (`isRecordTableDragColumnHiddenComponentState`, `isRecordTableCheckboxColumnHiddenComponentState`) hide each column independently; widget tables now display only data columns **Sticky column layout** - Sticky positioning of the first three columns used `:nth-of-type` CSS selectors — when drag or checkbox columns were hidden, the selector targeted the wrong column and the first data column didn't stick - Sticky CSS now targets semantic class names (`RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME`, etc.) so sticky behavior is correct regardless of which columns are hidden **Save/Cancel buttons during edit mode** - Save and Cancel command-menu buttons were unpinned during dashboard edit mode because the pin logic excluded all items while `isPageInEditMode` was true - Items whose availability expression contains `isPageInEditMode` are now exempted from the unpin rule; Save/Cancel stay pinned during editing **Title input auto-focus** - Selecting "Record Table" as widget type auto-focused the title input, interrupting the configuration flow - `focusTitleInput` is now `false` when navigating to record table settings **Morph relation field error** - A field with missing `morphRelations` metadata crashed the page with a "refresh" error from `mapObjectMetadataToGraphQLQuery` - Now returns an empty array and silently omits the field from the query instead of crashing **`updateRecordMutation` prop removal** - `RecordTableWithWrappers` required callers to pass an `updateRecordMutation` callback, duplicating `useUpdateOneRecord` at every usage site - The mutation is now owned inside `RecordTableContextProvider` via `RecordTableUpdateContext`; the prop is gone **Standalone → Widget module rename** - `record-table-standalone` module renamed to `record-table-widget` — `StandaloneRecordTable` → `RecordTableWidget`, `StandaloneRecordTableViewLoadEffect` → `RecordTableWidgetViewLoadEffect`, etc. **RecordTableRow cell extraction** - Row rendering logic (`RecordTableCellDragAndDrop`, `RecordTableCellCheckbox`, `RecordTableFieldsCells`, hotkey/arrow-key effects) was duplicated between `RecordTableRow` and `RecordTableRowVirtualizedFullData` - Extracted `RecordTableRowCells` (shared cell content) and `RecordTableStaticTr` (non-draggable `<tr>` wrapper); when drag column is hidden, rows render inside a static `<tr>` instead of the draggable wrapper **View load effect metadata tracking** - `RecordTableWidgetViewLoadEffect` now tracks `objectMetadataItem.updatedAt` alongside `viewId` to re-load states when metadata changes (e.g. field additions), preventing stale column data **Data source dropdown deduplication** - Extracted `filterReadableActiveObjectMetadataItems` util, shared by both chart and record table data source dropdowns — removes duplicated permission-filtering logic **RECORD_TABLE view identifier mapping (server)** - Added `RECORD_TABLE` case to `fromPageLayoutWidgetConfigurationToUniversalConfiguration` and `fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration` so widget views are properly mapped during workspace import/export **GraphQL error handler typing (server)** - `formatError` parameter changed from `any` to `unknown`; `workspaceQueryRunnerGraphqlApiExceptionHandler` broadened from `QueryFailedErrorWithCode` to `Error | QueryFailedError` — removes unsafe type casts **Save hook signature** - `useSaveRecordTableWidgetsViewDataOnDashboardSave` no longer takes `pageLayoutId` in constructor; receives it as a callback parameter, eliminating the need for `useAtomComponentStateCallbackState` **Customize Dashboard hidden during edit mode** - The "Customize Dashboard" command was still visible while already editing — its `conditionalAvailabilityExpression` now includes `not isPageInEditMode` **Fields dropdown split** - `RecordTableFieldsDropdownContent` (300+ lines) split into `RecordTableFieldsDropdownVisibleFieldsContent` and `RecordTableFieldsDropdownHiddenFieldsContent` **Checkbox placeholder cleanup** - Removed unnecessary `StyledRecordTableTdContainer` wrapper from `RecordTableCellCheckboxPlaceholder` |
||
|
|
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> |
||
|
|
d5a7dec117 |
refactor: rename ObjectMetadataItem to EnrichedObjectMetadataItem and clean up metadata flows (#18830)
## Summary - Renames `ObjectMetadataItem` to `EnrichedObjectMetadataItem` across the entire frontend (~440 files) to clarify that this type includes derived fields (`readableFields`, `updatableFields`, nested `fields[]`, `indexMetadatas[]`) computed at read time from the metadata store - Creates `splitObjectMetadataGqlResponse` that goes directly from a GraphQL `ObjectMetadataItemsQuery` response to flat store items (combining the old `mapPaginatedObjectMetadataItemsToObjectMetadataItems` + `splitObjectMetadataItemWithRelated` two-step flow into one call) - Removes `ObjectMetadataItemWithRelated` type and all "WithRelated" naming - Renames `generatedMockObjectMetadataItems` to `generateTestEnrichedObjectMetadataItemsMock` to make it clear this is test-only enriched data - Deletes `useLoadMockedObjectMetadataItems` hook (consolidated into `useLoadMockedMinimalMetadata`) - Ensures nothing destined for the metadata store computes `readableFields`/`updatableFields` (preventing the localStorage bloat from #18809) ## Type hierarchy (before → after) **Before:** ``` ObjectMetadataItemsQuery → mapPaginated → ObjectMetadataItemWithRelated → enrich → ObjectMetadataItem → split → FlatObjectMetadataItem (store) ``` **After:** ``` ObjectMetadataItemsQuery → splitObjectMetadataGqlResponse → FlatObjectMetadataItem (store) → mapPaginated + enrich (tests only) → EnrichedObjectMetadataItem ``` ## Test plan - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx test twenty-front` passes (767 suites, 4505 tests) - [x] `npx nx lint twenty-front` passes - [ ] CI checks pass Made with [Cursor](https://cursor.com) |
||
|
|
fc9723949b |
Fix AI chat re-renders and refactored code (#18585)
This PR: - Breaks useAgentChatData into focused effect components (streaming, fetch, init, auto-scroll, diff sync) - Splits message list into non-last (stable) + last (streaming/error) to prevent full re-renders on each stream chunk - Adds scroll-to-bottom button and MutationObserver-based auto-scroll on thread switch - Lifts loading state from context to atoms - Adds areEqual to selector factories We could improve further but this sets up a robust architecture for further refactoring. ## Messages flow The flow of messages loading and streaming is now more solid. Everything goes out from `AgentChatAiSdkStreamEffect`, whether loaded from the DB or streaming directly, and every consumers is using only one atom `agentChatMessagesComponentFamilyState` ## Data sync effect with callbacks new hook See `packages/twenty-front/src/modules/apollo/hooks/useQueryWithCallbacks.ts` which allows to fix Apollo v4 migration leftovers and is an implementation of the pattern we talked about with @charlesBochet We could refine this pattern in another PR. # Before https://github.com/user-attachments/assets/84e7a96f-6790-405d-8a73-2dacbf783be5 # After https://github.com/user-attachments/assets/4c692e3a-2413-4513-abcc-44d0da311203 Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
fe4512f80c |
Add record page layout tabs editing (#18702)
- Fix duplication of tabs in dashboards that didn't open the duplicated tab in the side panel: replaced the logic that used Math.round with an algorithm that switches the positions of the elements. We will keep relying on integers, but it will work well in all cases. - Make dnd work with record page layouts, where there is a pinned tab - Make tab movements work with pinned tab, too - Allow user to set a tab as the pinned tab - Make backend changes to be able to save tabs with `layoutMode` https://github.com/user-attachments/assets/ce1130fa-71df-49ba-ba2f-6f971e15dd49 --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.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) |
||
|
|
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 |
||
|
|
1be87eb97b |
chore: frontend dead code removal and naming cleanup (#18690)
## Summary - **Delete 10 unused files**: 7 hooks (`useWorkflowRunUnsafe`, `useGetViewById`, `useCreateViewFieldGroup`, `useDeleteViewFieldGroup`, `useUpdateViewFieldGroup`, `useCreateManyViewFieldGroups`, `useMoveViewColumns` + test), 1 component (`SettingsSummaryCard`), 1 utility (`createEventContext`) - **Rename `objectMetadataItemsState` → `objectMetadataItemsSelector`** across ~85 files to accurately reflect it is a derived selector (via `createAtomSelector`), not a base Jotai atom ## Details ### Dead code removed | Type | Name | Reason | |------|------|--------| | Hook | `useWorkflowRunUnsafe` | Never imported — duplicate of `useWorkflowRun` without schema validation | | Hook | `useGetViewById` | Never imported — `useViewById` is used instead | | Hook | `useCreateViewFieldGroup` | Never imported — CRUD done via `usePerformViewFieldGroupAPIPersist` | | Hook | `useDeleteViewFieldGroup` | Same as above | | Hook | `useUpdateViewFieldGroup` | Same as above | | Hook | `useCreateManyViewFieldGroups` | Same as above | | Hook | `useMoveViewColumns` | Only imported by its own test — no production usage | | Component | `SettingsSummaryCard` | Never imported anywhere | | Utility | `createEventContext` | Never imported anywhere | ### Rename `objectMetadataItemsState` is created via `createAtomSelector` (it derives from `objectMetadataItemsWithFieldsSelector`), so naming it `*State` is misleading. Renamed to `objectMetadataItemsSelector` for consistency with sibling selectors like `objectMetadataItemsByNamePluralMapSelector`. |
||
|
|
1bb642d7cd |
refactor: remove ProcessedNavigationMenuItem, derive display fields at point of use (#18687)
## Summary - Removes `ProcessedNavigationMenuItem` type and the `sortNavigationMenuItems` enrichment pipeline that pre-computed display fields (label, link, icon, avatarUrl, etc.) for every navigation menu item upfront - Replaces with `filterAndSortNavigationMenuItems` (pure filter+sort returning raw `NavigationMenuItem[]`) and small utility functions (`getNavigationMenuItemLabel`, `getNavigationMenuItemComputedLink`, `getNavigationMenuItemObjectNameSingular`) that components call on demand - Eliminates the redundant `itemType` field (was identical to the raw `type` field) across ~30 consumer files - Each type-specific renderer (`NavigationDrawerItemForObjectMetadataItem`, `NavigationMenuItemIcon`, link/folder components) now derives only the 1-2 display fields it actually needs from the raw item + globally available Jotai atoms Net result: -1199 / +1177 lines, 7 files deleted, 4 new utility files. |
||
|
|
a121d00ddd |
feat: add color property to ObjectMetadata for object icon customization (#18672)
## Summary - Adds a `color` column to `ObjectMetadataEntity` with full GraphQL support so object icon colors are persisted at the metadata level - Adds a `type` column to `NavigationMenuItemEntity` (enum: `OBJECT`, `VIEW`, `FOLDER`, `LINK`, `RECORD`) replacing field-based type inference - Updates frontend to read object colors from `objectMetadata.color` (falling back to standard defaults) in the sidebar nav, record index header, and record show breadcrumb - Simplifies `NavigationMenuItemIcon` color resolution via `getEffectiveNavigationMenuItemColor` util ## Color rules | Item type | Color source | Editable in sidebar? | |-----------|-------------|---------------------| | **Object** | `objectMetadata.color` | Yes — persisted to `objectMetadata.color` on Save | | **Folder** | `navigationMenuItem.color` | Yes | | **Link** | Fixed default (`DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK`) | No | | **View** | `objectMetadata.color` (from the parent object) | No | | **Record** | None | No | - **Object** items represent the whole object (e.g. "Companies") and point to the INDEX view. Changing their color updates `objectMetadata.color` via `useSaveObjectMetadataColorsFromDraft`. - **View** items represent specific non-INDEX views. Their color comes from the parent object's metadata (read-only). - Only **folders** store their color on `navigationMenuItem.color` — enforced by `hasNavigationMenuItemOwnColor` util. - `getEffectiveNavigationMenuItemColor` returns `objectColor` for both OBJECT and VIEW items, folder's own color for folders, and the fixed default for links. ## NavigationMenuItemType enum - Shared enum created in `twenty-shared` with values: `OBJECT`, `VIEW`, `FOLDER`, `LINK`, `RECORD` - Registered as a GraphQL enum on the backend - Replaces string literals across entity, DTOs, input, converters, and frontend hooks - Migration backfills existing rows: INDEX views → `OBJECT`, non-INDEX views → `VIEW`, based on join with the view table ## Design decisions - **OBJECT vs VIEW distinction**: Items pointing to INDEX views are typed as `OBJECT` (represent the whole object, color editable). Items pointing to non-INDEX views are typed as `VIEW` (specific view, color read-only from parent object). - **Dual color storage**: `navigationMenuItem.color` is preserved for folders only. Objects use `objectMetadata.color` as their source of truth. - **Type discriminator**: The `type` column replaces field-based inference (checking `viewId`, `link`, `targetRecordId` presence) with an explicit enum, simplifying `isNavigationMenuItemLink` / `isNavigationMenuItemFolder` to simple `item.type ===` checks. - **No settings page color picker**: Object color editing is done from the sidebar edit panel, not the data model settings page. ## Test plan - [ ] Verify objects display their default standard colors in the sidebar - [ ] Verify object color editing works in the sidebar edit panel (persists to objectMetadata.color) - [ ] Verify folder color editing works in the sidebar edit panel - [ ] Verify views, links, and records do NOT show a color picker in the sidebar edit panel - [ ] Run `npx nx typecheck twenty-front` and `npx nx typecheck twenty-server` - [ ] Verify the database migrations add `color` to `objectMetadata` and `type` to `navigationMenuItem` Made with [Cursor](https://cursor.com) |
||
|
|
5c745059ad |
refactor: remove "core" naming from views and eliminate converter layer (#18667)
## Summary
- **Remove all "core" prefixes** from the views system — the
metadata-based storage migration is complete, so `CoreView`,
`coreViewsSelector`, `getCoreViews`, etc. are now just `View`,
`viewsSelector`, `getViews`
- **Eliminate the entire converter layer** (15 files, ~850 lines
deleted) — `convertCoreViewToView` and all sub-converters were either
no-ops or trivially adding `__typename` / mapping identical enum values.
Local enums now re-export from generated GraphQL types directly (single
source of truth)
- **Unify `View` and `ViewWithRelations`** into one type —
`ViewWithRelations` is now a type alias for `View`, selectors return
data directly without conversion
### Backend
- Rename `@ObjectType('CoreView')` → `@ObjectType('View')` (and all
sub-entities)
- Rename resolver methods: `getCoreViews` → `getViews`, `createCoreView`
→ `createView`, etc.
- Rename `FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION` →
`FIND_ALL_VIEWS_GRAPHQL_OPERATION`
### Frontend
- Delete 15 converter files (`convertGqlView*ToView*`,
`convertView*ToGql`, `convertViewWithRelationsToView`)
- Re-export `ViewType`, `ViewKey`, `ViewFilterGroupLogicalOperator` from
generated enums (no more duplicate enum definitions with different
casing)
- Replace `ViewOpenRecordInType` with `ViewOpenRecordIn` from generated
- Remove `__typename` from all local view sub-types
- Remove unused `variant` from `ViewFilter`, make `displayValue` and
`definition` optional
- Rename ~45 GraphQL query/mutation files and all selectors to drop
"core" prefix
- Delete unused `viewsWithRelationsSelector`
|
||
|
|
2a6fcfcfb3 |
Side Panel Sub Page Framework® (#18579)
Replace hard-coded implementations for sub pages in the side panel with a proper framework |
||
|
|
ae122f4bb1 | Add draft message persistence for AI chat threads (#18371) | ||
|
|
36bcc71f3d |
refactor(command-menu-item): rename Actions to CommandMenuItem (#18489)
actions are being renamed to command menu item, they will be migrated to server and will be served as headless front components --------- Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com> |
||
|
|
5b28e59ca7 | Navbar drag drop using dnd kit (#18288) | ||
|
|
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 |
||
|
|
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 |
||
|
|
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) |