- Add FILES field on attachment
- Adapt Attachment logic in front to use new resolver/controller
- Update files-field logic to infer applicationId from fieldMetadataId +
ask for fieldMetadataId in upload resolver
- Design update
To do in next PR :
- Adapt activity files logic
## Root Cause
The `DateTimePicker` component lazy-loads `react-datepicker` using
React's `lazy()` with a `Suspense` fallback that shows skeleton loaders:
```typescript
const ReactDatePicker = lazy<ComponentType<DatePickerPropsType>>(() =>
import('react-datepicker').then(...)
);
// In render:
<Suspense fallback={<SkeletonLoader />}>
<ReactDatePicker ... />
</Suspense>
```
On slower CI runners (GitHub Actions vs depot.dev), the lazy load takes
longer, causing tests to timeout while still showing skeletons instead
of the actual date picker content.
## Fix
Increased `findByText` timeout from the default 1000ms to 10000ms for
tests that wait for the date picker to load:
- `DateTimeFieldInput.stories.tsx` - 4 stories fixed
- `InternalDatePicker.stories.tsx` - 2 stories fixed
## Why This Wasn't Flaky Before
depot.dev runners have faster I/O and more consistent performance, so
the lazy load completed quickly. GitHub Actions runners have more
variable performance, causing the load to sometimes exceed the 1000ms
default timeout.
Fixes#17111
### Problem
When creating a Relation field, after deselecting the pre-selected
default object (e.g., Company) and selecting a different object (e.g.,
Opportunity), both objects would end up being selected, showing "2
Objects" instead of just the newly selected one.
### Root Cause
The `initialMorphRelationsObjectMetadataIds` array was being recreated
on every component render, causing react-hook-form's `Controller` to
treat the `defaultValue` prop as a new value and re-apply it after user
changes.
### Solution
1. **Memoized the initial value**: Used `useMemo` to ensure
`initialMorphRelationsObjectMetadataIds` has a stable reference across
renders
2. **Added initialization guard**: Used `useEffect` with a `useRef` flag
to ensure the default value is only set once during component mount
3. **Maintained Controller defaultValue**: Kept the `defaultValue` prop
on the Controller, which now works correctly with the stable memoized
value
### Changes
- `SettingsDataModelFieldRelationForm.tsx`: Added memoization and
initialization guard to prevent default value re-application
---------
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Fixes: #16872
## Summary
Fixes the DateTimePicker to respect user's 12H/24H time format
preference. Previously, the time display at the top of the
DateTimePicker always showed 24-hour format (e.g., "14:30") regardless
of the user's time format setting.
## Screenshots
_After:_ Time respects user preference, showing 12H with AM/PM (e.g.,
"03:28 PM") or 24H format
<img width="357" height="491" alt="image"
src="https://github.com/user-attachments/assets/4a03f195-a52b-4f74-84ba-c5eb2fc6c8c1"
/>
## Implementation Details
### Changes Made:
1. **TimeMask.ts** - Added `getTimeMask()` to return appropriate mask
pattern based on time format
2. **TimeBlocks.ts** - Restored as constant file per linting
requirements
3. **getTimeBlocks.ts** (new) - Dynamic block generator supporting 12H
(1-12 + AM/PM) and 24H (0-23)
4. **DateTimeBlocks.ts** - Simplified to static constant
5. **getDateTimeMask.ts** - Updated to accept and use `timeFormat`
parameter
6. **DateTimePickerInput.tsx** - Dynamically generates mask and blocks
based on user's time format preference, increased input width to
accommodate AM/PM
7. **useParseJSDateToIMaskDateTimeInputString.ts** - Formats dates with
correct time pattern
8. **useParseDateTimeInputStringToJSDate.ts** - Parses dates with
correct time pattern
9. **date-utils.ts** - Added `getTimePattern()` helper (returns 'hh:mm
a' for 12H, 'HH:mm' for 24H)
10. **parseDateTimeToString.ts** - Added optional `timeFormat` parameter
for consistency
### Technical Approach:
- Leverages existing `useDateTimeFormat()` hook to get user's
`timeFormat` preference
- Supports `TimeFormat.SYSTEM`, `TimeFormat.HOUR_12`, and
`TimeFormat.HOUR_24`
- Uses IMask blocks with appropriate ranges: 1-12 for 12H, 0-23 for 24H
- Adds 'aa' (AM/PM) block for 12-hour format with enum validation
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
## Implement Navigation Menu Items Frontend
Implements the frontend for navigation menu items, the new system
replacing favorites.
### Changes
- Added GraphQL fragments and queries for navigation menu items
- Added hooks for managing navigation menu items (create, update,
delete, sorting, filtering)
- Updated components to use navigation menu items instead of favorites
- Added test coverage for utility functions
### Migration Note
The favorites and navigation menu item modules currently exist in
parallel. The favorites code will be removed once all data has been
migrated to navigation menu items.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Replaces Favorites with feature-flagged `NavigationMenuItem` across
frontend and backend, while keeping Favorites as fallback until
migration completes.
>
> - UI: new `navigation-menu-item` components (folders, orphan items,
drag provider/droppable, icons, skeleton), dispatcher components to
switch from Favorites, and updated “Add to favorites” action to create
`NavigationMenuItem` when `IS_NAVIGATION_MENU_ITEM_ENABLED`
> - DnD: shared `validateAndExtractFolderId` and droppable id utils
moved to `ui/layout/draggable-list`; favorites DnD updated to use shared
utils
> - GraphQL (client): add fragments, queries, mutations, hooks
(create/update/delete/find), and generated types; added
`RecordIdentifier` and `targetRecordIdentifier` on `NavigationMenuItem`
> - Prefetch: new prefetch state/effect for navigation menu items; skip
favorites prefetch when flag enabled
> - Backend: add DTOs (`NavigationMenuItem`, `RecordIdentifier`),
resolver `targetRecordIdentifier` field, service logic to fetch record
identifiers with permission-aware access and image signing,
`getRecordImageIdentifier` util, entity relation to `view`, and
migration adding FK on `viewId`
> - Feature flags & seeding: add `IS_NAVIGATION_MENU_ITEM_ENABLED` to
enums, dev seeder enables it; standard app seeds workspace navigation
menu items instead of favorites when flag on
> - Tests: add unit tests for sorting/labels/folder id and related utils
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
c99746f08b9f84fc8cec4fcc3a7d7afb8ea92db7. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Aman Raj <92664006+araj00@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
## Summary
This PR fixes the `tsconfig` setup in `twenty-front` so that `tsgo -p
tsconfig.json` properly type-checks all files.
### Root Cause
The previous setup used TypeScript project references with `files: []`
in the main `tsconfig.json`. When running `tsgo -p tsconfig.json`, this
checks nothing because `tsgo` requires the `-b` (build) flag for project
references, but the configs weren't set up for composite mode.
### Changes
**Simplified tsconfig architecture (4 files → 2):**
- `tsconfig.json` - All files (dev, tests, stories) for
typecheck/IDE/lint
- `tsconfig.build.json` - Production files only (excludes tests/stories)
**Removed redundant configs:**
- `tsconfig.dev.json`
- `tsconfig.spec.json`
- `tsconfig.storybook.json`
**Updated references:**
- `jest.config.mjs` → uses `tsconfig.json`
- `eslint.config.mjs` → uses `tsconfig.json`
- `vite.config.ts` → uses `tsconfig.json` for dev
**Type fixes (pre-existing errors revealed by proper typechecking):**
- Made `applicationId` optional in `FieldMetadataItem` and
`ObjectMetadataItem`
- Added missing `navigationMenuItem` translation
- Added `objectLabelSingular` to Search GraphQL query
- Fixed `sortMorphItems.test.ts` mock data
## Test plan
- [ ] Run `npx nx typecheck twenty-front` - should pass
- [ ] Run `npx nx lint twenty-front` - should work
- [ ] Run `npx nx test twenty-front` - should work
- [ ] Run `npx nx build twenty-front` - should work
- [ ] Verify IDE type checking works correctly
Previously we introduced useUpdateOneRecordV2 to handle morph relations.
In this PR we are removing it to only use useUpdateOneRecord which has
been adapted not to requires objectMetadataNameSingular in its props.
**Summary**
Issue #16963: When an object is renamed, the morph relation picker still
shows the old name.
**Root cause**
The picker used searchRecord.objectNameSingular from the GraphQL search
response, which can be stale after a rename.
The search record stores the object name at query time, not the current
metadata.
**Solution**
- Updated SingleRecordPickerMenuItem:
- Added useObjectMetadataItems to access current object metadata.
- Look up the current object metadata by morphItem.objectMetadataId.
- Use labelSingular (or nameSingular as fallback) instead of
searchRecord.objectNameSingular for display.
- Updated MultipleRecordPickerMenuItemContent:
- Use objectMetadataItem.labelSingular (already available as a prop)
instead of searchRecord.objectNameSingular.
---------
Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
In this PR :
- New field type FieldMetadataType.FILES added (as jsonb in DB)
- Backend: GraphQL types, validation, REST API schema, data processor
handling
- Frontend: field configuration in settings
- Feature flag IS_FILES_FIELD_ENABLED to gate the feature
- Integration tests for create/filter validation
- Dev seed: Feature flag enabled + FILES field on survey result object
To do in next PRs :
- Backend :
-- new workspaceFile controller (for download) & new workspaceFile
upload resolver (for upload)
-- pre-hook/post-hook to ensure fileId existence + listener to ensure
cleaning
- Frontend : FILES field display/edit in table view, record, ...
- Create resolvers for each type of charts which needs data
transformation after the group by operation: Bar Chart, Line Chart and
Pie Chart
- Move all the utils to the backend and refactored some into services
This allows all the computation to be done in the backend, improving
performances in the frontend.
## Summary
Moves the custom ESLint rules from `tools/eslint-rules` to
`packages/twenty-eslint-rules` for better organization within the
monorepo packages structure.
## Changes
- Move `eslint-rules` from `tools/` to `packages/twenty-eslint-rules`
- Use `loadWorkspaceRules` from `@nx/eslint-plugin` to load custom rules
- Update all ESLint configs to use the `twenty/` rule prefix instead of
`@nx/workspace-`
- Update `project.json`, `jest.config.mjs` with new paths
- Update `package.json` workspaces and `nx.json` cache inputs
- Update Dockerfile reference
## Technical Details
The custom ESLint rules are now loaded using Nx's `loadWorkspaceRules`
utility which:
- Handles TypeScript transpilation automatically
- Allows loading workspace rules from any directory
- Provides a cleaner approach than the previous `@nx/workspace-`
convention
## Testing
- Verified all 17 custom ESLint rules load correctly from the new
location
- Verified linting works on dependent packages (twenty-front,
twenty-server, etc.)
Upgraded to Storybook 10. We still use `@storybook/test-runner` for
testing since it appears it'd require more work to move from Jest to
Vitest than I initially anticipated, but I completed this PR to fix
`storybook:serve:dev` - it takes time to load, but it works the way it
used to with Storybook 8.
https://github.com/user-attachments/assets/7afc32c6-4bcf-4b37-b83b-8d00d28dda15
Upgraded from 8.6.15 to 9.1.17 in two steps:
- 8.6.15 -> 9.0.0
- 9.0.0 -> 9.1.17
I had to disable `storybook-addon-cookie` since it is not supported for
Storybook 9. However, I do intend to upgrade to Storybook 10 when this
is merged, so we can replace the aforementioned add-on with this fork
specifically created to support Storybook 10 and above:
https://www.npmjs.com/package/@storybook-community/storybook-addon-cookie.
Additionally, once we upgrade to Version 10 successfully, I will start
looking into integrating the official Vitest add-on.
- moved a few gql types to twenty shared to re-use in server
- added a new endpoint, onEventSubscription that expect a streamId to
create a connection
- two new endpoints to store queries in Redis
- updated the existing batch channel to directly use object record type
The useInlineCell hook was only managing the dropdown focus state
(activeDropdownFocusIdState/previousDropdownFocusIdState) but not the
focus stack (focusStackState). Since the hotkey system checks
currentFocusIdSelector which reads from focusStackState, closing an
inline cell in the side panel would leave the focus stack in an
incorrect state, causing hotkeys to not work properly.
This fix adds proper focus stack management:
- openInlineCell: pushes the inline cell to the focus stack
- closeInlineCell: removes the inline cell from the focus stack
Fixes#16224
### Tests Added
- Added unit tests for
[useInlineCell](cci:1://file:///Users/apple/WebstormProjects/twenty/packages/twenty-front/src/modules/object-record/record-inline-cell/hooks/useInlineCell.ts:15:0-83:2)
hook to verify focus stack management
- Tests cover: opening inline cell, closing inline cell, and full
open/close cycles
- Tests ensure `focusStackState` and `activeDropdownFocusIdState` are
properly synchronized
---------
Co-authored-by: Joker <apple@Apples-MacBook-Pro.local>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Closes [571](https://github.com/twentyhq/core-team-issues/issues/571).
Replicates the behavior of number field and allows specifying the number
of decimals for currency field.
<img width="931" height="858" alt="image"
src="https://github.com/user-attachments/assets/f5100d58-b1b0-4a88-a090-e98b2feeebd0"
/>
Currencies around the world have a maximum of three decimal places -
BHD, KWD etc. However, I have added a maximum of five decimal places in
case someone wants to use the currency field type for displaying things
like `per-second-billing` or `exchange rates`.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Adds configurable decimals (0–5) for currency fields, updating
settings UI, types/schema, display/aggregate formatting, and input
precision.
>
> - **Currency field settings**:
> - Add `decimals` (0–5) to `FieldCurrencyMetadata.settings` and
validate via `currencyFieldSettingsSchema`.
> - Update `SettingsDataModelFieldCurrencyForm` to manage `format`
(short/full) and `decimals` with counter when `full`; wire defaults via
`useCurrencySettingsFormInitialValues`.
> - **Display & aggregation**:
> - `CurrencyDisplay` and
`transformAggregateRawValueIntoAggregateDisplayValue` honor `decimals`
when `format` is `full`; keep short format using `formatToShortNumber`.
> - **Input**:
> - Increase currency `IMaskInput` precision with `scale=5`.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
959a8fc1ea726d206548ea32cada28d77c1c6eb9. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
## Summary
This PR enforces the use of `@/` alias for imports instead of relative
parent imports (`../`).
## Changes
### ESLint Configuration
- Added `no-restricted-imports` pattern in `eslint.config.react.mjs` to
block `../*` imports with the message "Relative parent imports are not
allowed. Use @/ alias instead."
- Removed the non-working `import/no-relative-parent-imports` rule
(doesn't work properly in ESLint flat config)
### VS Code Settings
- Added `javascript.preferences.importModuleSpecifier: non-relative` to
`.vscode/settings.json` (TypeScript setting was already there)
### Code Fixes
- Fixed **941 relative parent imports** across **706 files** in
`packages/twenty-front`
- All `../` imports converted to use `@/` alias
## Why
- Consistent import style across the codebase
- Easier to move files without breaking imports
- Better IDE support for auto-imports
- Clearer understanding of where imports come from
Fixes https://github.com/twentyhq/twenty/issues/16110
This PR implements Temporal to replace the legacy Date object, in all
features that are time zone sensitive. (around 80% of the app)
Here we define a few utils to handle Temporal primitives and obtain an
easier DX for timezone manipulation, front end and back end.
This PR deactivates the usage of timezone from the graph configuration,
because for now it's always UTC and is not really relevant, let's handle
that later.
Workflows code and backend only code that don't take user input are
using UTC time zone, the affected utils have not been refactored yet
because this PR is big enough.
# New way of filtering on date intervals
As we'll progressively rollup Temporal everywhere in the codebase and
remove `Date` JS object everywhere possible, we'll use the way to filter
that is recommended by Temporal.
This way of filtering on date intervals involves half-open intervals,
and is the preferred way to avoid edge-cases with DST and smallest time
increment edge-case.
## Filtering endOfX with DST edge-cases
Some day-light save time shifts involve having no existing hour, or even
day on certain days, for example Samoa Islands have no 30th of December
2011 : https://www.timeanddate.com/news/time/samoa-dateline.html, it
jumps from 29th to 31st, so filtering on `< next period start` makes it
easier to let the date library handle the strict inferior comparison,
than filtering on `≤ end of period` and trying to compute manually the
end of the period.
For example for Samoa Islands, is end of day `2011-12-29T23:59:59.999`
or is it `2011-12-30T23:59:59.999` ? If you say I don't need to know and
compute it, because I want everything strictly before
`2011-12-29T00:00:00 + start of next day (according to the library which
knows those edge-cases)`, then you have a 100% deterministic way of
computing date intervals in any timezone, for any day of any year.
Of course the Samoa example is an extreme one, but more common ones
involve DST shifts of 1 hour, which are still problematic on certain
days of the year.
## Computing the exact _end of period_
Having an open interval filtering, with `[included - included]` instead
of half-open `[included - excluded)`, forces to compute the open end of
an interval, which often involves taking an arbitrary unit like minute,
second, microsecond or nanosecond, which will lead to edge-case of
unhandled values.
For example, let's say my code computes endOfDay by setting the time to
`23:59:59.999`, if another library, API, or anything else, ends up
giving me a date-time with another time precision `23:59:59.999999999`
(down to the nanosecond), then this date-time will be filtered out,
while it should not.
The good deterministic way to avoid 100% of those complex bugs is to
create a half-open filter :
`≥ start of period` to `< start of next period`
For example :
`≥ 2025-01-01T00:00:00` to `< 2025-01-02T00:00:00` instead of `≥
2025-01-01T00:00:00` to `≤ 2025-01-01T23:59:59.999`
Because, `2025-01-01T00:00:00` = `2025-01-01T00:00:00.000` =
`2025-01-01T00:00:00.000000` = `2025-01-01T00:00:00.000000000` => no
risk of error in computing start of period
But `2025-01-01T23:59:59` ≠ `2025-01-01T23:59:59.999` ≠
`2025-01-01T23:59:59.999999` ≠ `2025-01-01T23:59:59.999999999` =>
existing risk of error in computing end of period
This is why an half-open interval has no risk of error in computing a
date-time interval filter.
Here is a link to this debate :
https://github.com/tc39/proposal-temporal/issues/2568
> For this reason, we recommend not calculating the exact nanosecond at
the end of the day if it's not absolutely necessary. For example, if
it's needed for <= comparisons, we recommend just changing the
comparison code. So instead of <= zdtEndOfDay your code could be <
zdtStartOfNextDay which is easier to calculate and not subject to the
issue of not knowing which unit is the right one.
>
> [Justin Grant](https://github.com/justingrant), top contributor of
Temporal
## Application to our codebase
Applying this half-open filtering paradigm to our codebase means we
would have to rename `IS_AFTER` to `IS_AFTER_OR_EQUAL` and to keep
`IS_BEFORE` (or even `IS_STRICTLY_BEFORE`) to make this half-open
interval self-explanatory everywhere in the codebase, this will avoid
any confusion.
See the relevant issue :
https://github.com/twentyhq/core-team-issues/issues/2010
In the mean time, we'll keep this operand and add this semantic in the
naming everywhere possible.
## Example with a different user timezone
Example on a graph grouped by week in timezone Pacific/Samoa, on a
computer running on Europe/Paris :
<img width="342" height="511" alt="image"
src="https://github.com/user-attachments/assets/9e7d5121-ecc4-4233-835b-f59293fbd8c8"
/>
Then the associated data in the table view, with our **half-open
date-time filter** :
<img width="804" height="262" alt="image"
src="https://github.com/user-attachments/assets/28efe1d7-d2fc-4aec-b521-bada7f980447"
/>
And the associated SQL query result to see how DATE_TRUNC in Postgres
applies its internal start of week logic :
<img width="709" height="220" alt="image"
src="https://github.com/user-attachments/assets/4d0542e1-eaae-4b4b-afa9-5005f48ffdca"
/>
The associated SQL query without parameters to test in your SQL client :
```SQL
SELECT "opportunity"."closeDate" as "close_date", TO_CHAR(DATE_TRUNC('week', "opportunity"."closeDate", 'Pacific/Samoa') AT TIME ZONE 'Pacific/Samoa', 'YYYY-MM-DD') AS "DATE_TRUNC by week start in timezone Pacific/Samoa", "opportunity"."name" FROM "workspace_1wgvd1injqtife6y4rvfbu3h5"."opportunity" "opportunity" ORDER BY "opportunity"."closeDate" ASC NULLS LAST
```
# Date picker simplification (not in this PR)
Our DatePicker component, which is wrapping `react-datepicker` library
component, is now exposing plain dates as string instead of Date object.
The Date object is still used internally to manage the library
component, but since the date picker calendar is only manipulating plain
dates, there is no need to add timezone management to it, and no need to
expose a handleChange with Date object.
The timezone management relies on date time inputs now.
The modification has been made in a previous PR :
https://github.com/twentyhq/twenty/issues/15377 but it's good to
reference it here.
# Calendar feature refactor
Calendar feature has been refactored to rely on Temporal.PlainDate as
much as possible, while leaving some date-fns utils to avoid re-coding
them.
Since the trick is to use utils to convert back and from Date object in
exec env reliably, we can do it everywhere we need to interface legacy
Date object utils and Temporal related code.
## TimeZone is now shown on Calendar :
<img width="894" height="958" alt="image"
src="https://github.com/user-attachments/assets/231f8107-fad6-4786-b532-456692c20f1d"
/>
## Month picker has been refactored
<img width="503" height="266" alt="image"
src="https://github.com/user-attachments/assets/cb90bc34-6c4d-436d-93bc-4b6fb00de7f5"
/>
Since the days weren't useful, the picker has been refactored to remove
the days.
# Miscellaneous
- Fixed a bug with drag and drop edge-case with 2 items in a list.
# Improvements
## Lots of chained operations
It would be nice to create small utils to avoid repeated chained
operations, but that is how Temporal is designed, a very small set of
primitive operations that allow to compose everything needed. Maybe
we'll have wrappers on top of Temporal in the coming years.
## Creation of Temporal objects is throwing errors
If the input is badly formatted Temporal will throw, we might want to
adopt a global strategy to avoid that.
Example :
```ts
const newPlainDate = Temporal.PlainDate.from('bad-string'); // Will throw
```
## Summary
Move the Support and Documentation links from the bottom left navigation
drawer to the workspace switcher dropdown menu.
## Changes
- **Workspace Switcher**: Added Support (conditional) and Documentation
links before Log out
- **Settings Navigation**: Added Support and Documentation links in the
Other section
- **Support visibility**: Support link only appears when FrontChat is
configured (not waiting for script to load)
- **FrontChat loading**: Created `SupportChatEffect` component to ensure
FrontChat script loads at app startup for popup notifications
- **Bug fix**: Fixed settings navigation items without a path appearing
highlighted
- **Cleanup**: Removed unused `SupportDropdown`, `SupportButton`, and
`SupportButtonSkeletonLoader` components
- **Hidden**: Temporarily commented out Integrations page
## Screenshots
The Support and Documentation links now appear in:
1. Workspace switcher dropdown (top left)
2. Settings navigation (Other section)
## Summary
Adds Notion-style resizable panels for the navigation drawer (left
sidebar) and command menu (right panel).
## Behavior
- **Hover** at panel edge → resize cursor appears
- **Click** → collapse/close the panel
- **Drag** → resize the panel (5px movement threshold to distinguish
from click)
## Constraints
| Panel | Min | Max | Default | Collapse Threshold |
|-------|-----|-----|---------|-------------------|
| Navigation Drawer | 180px | 350px | 220px | 150px |
| Command Menu | 320px | 600px | 400px | 250px |
## Performance Optimizations
- **CSS variables** for smooth 60fps resize (no React re-renders during
drag)
- **Table resize observer disabled** during panel resize to prevent
expensive recalculations
- **React.memo wrapper** on page body to prevent unnecessary re-renders
## Architecture
- `useResizablePanel` hook following the same pattern as
`useResizeTableHeader`
- `ResizablePanelEdge` - resize handle positioned at panel edge
- `ResizablePanelGap` - resize handle in the gap between panels
- `cssVariableEffect` - Recoil effect to sync CSS variables with state
## Refactoring
- Split `recoil-effects.ts` into separate files in `utils/recoil/` (one
export per file)
- Persist panel widths to localStorage via existing `localStorageEffect`
## Summary
This PR adds the `lingui/no-unlocalized-strings` ESLint rule to detect
untranslated strings and fixes translation issues across multiple
components.
## Changes
### ESLint Configuration (`eslint.config.react.mjs`)
- Added comprehensive `ignore` patterns for non-translatable strings
(CSS values, HTML attributes, technical identifiers)
- Added `ignoreNames` for props that don't need translation (className,
data-*, aria-*, etc.)
- Added `ignoreFunctions` for console methods, URL APIs, and other
non-user-facing functions
- Disabled rule for debug files, storybook, and test files
### Components Fixed (~19 files)
- Object record components (field inputs, pickers, merge dialogs)
- Settings components (accounts, admin panel)
- Serverless function components
- Record table and title cell components
## Status
🚧 **Work in Progress** - ~124 files remaining to fix
This PR is being submitted as draft to allow progressive fixing of
remaining translation issues.
## Testing
- Run `npx eslint "src/**/*.tsx"` in `packages/twenty-front` to check
remaining issues
Title: "feat: Add second, minute & hour resolution options to relative
date Filter action"
---
## Summary
This PR enables support for smaller time units — **Seconds, Minutes, and
Hours** — in the *Relative Date* filter used in workflows, rather than
being limited to days only.
---
## What Changed
This PR extends the relative date filter to include support for the
following units:
✔️ `SECOND`
✔️ `MINUTE`
✔️ `HOUR`
✔️ (Existing: `DAY`, `WEEK`, `MONTH`, etc.)
Changes include:
- Adding `SECOND`, `MINUTE`, and `HOUR` options to the internal relative
date unit enum/constant.
- Updating utility functions and parsers to correctly interpret and
evaluate these new units.
- Enhancing existing tests and adding new tests to cover second, minute,
and hour relative filters.
---
## Testing
New and updated tests include:
- Unit tests for serialization of relative filter values including
seconds, minutes, and hours.
- Workflow filter evaluation tests that verify minute/hour resolution
behaves correctly.
Tests are included in the changeset.
---
## Backward Compatibility
This change is fully backward compatible:
- All existing relative date filters using days or larger units behave
exactly as before.
- Adding finer units does not alter existing stored data or workflow
definitions.
---
## Issue Reference
Fixes: **twentyhq/twenty#15525**
<img width="1909" height="896" alt="image"
src="https://github.com/user-attachments/assets/328d03dc-ca0b-4c3f-84e5-58961c178398"
/>
---------
Co-authored-by: Guillim <guillim@users.noreply.github.com>
Co-authored-by: guillim <guigloo@msn.com>
# Closes Issue: Can't distinguish between fields with identical names
(#16285)
There was a UX bug in the workflow filter interface where **two
variables coming from different steps but sharing the same field name**
were displayed identically. This made it difficult for users to tell
them apart when used in a filter group, leading to confusion when
building workflows.
---
# Fix: Add Full Path Label Tooltip for Workflow Filter Field
- Adds a **tooltip/label showing the complete path** so users can
distinguish fields from different workflow steps even if they share the
same name.
## UX Improvements
<img width="495" height="288" alt="image"
src="https://github.com/user-attachments/assets/fa26f381-835b-4d14-bf73-f04b59c8d0b5"
/>
Closes [89](https://github.com/twentyhq/core-team-issues/issues/89)
## Problem
When users attempt to create or update a record with a duplicate value
for a unique field (e.g., duplicate email or domain name), they receive
a generic error message: "This record already exists. Please check your
data and try again." This provides no actionable way to locate and view
the existing conflicting record, forcing users to manually search for
it.
## Solution
This PR enhances duplicate key constraint error handling to
automatically detect the conflicting record and display a "View existing
record" link in the error notification. When clicked, users are
navigated directly to the existing record's detail page.
## Backend Changes
### 1. PostgreSQL Error Parsing
(`parse-postgres-constraint-error.util.ts`)
- Extracts structured information from PostgreSQL `QueryFailedError`
messages.
### 2. Conflicting Record Lookup (`find-conflicting-record.util.ts`)
- Queries the database to find the existing record with the conflicting
value
### 3. Error Handling Orchestration
(`handle-duplicate-key-error.util.ts`)
- Parses PostgreSQL error to extract column name and conflicting value
- Attempts to find the conflicting record
- Enriches `TwentyORMException` with `conflictingRecordId` and
`conflictingObjectNameSingular` if found
### 4. Exception Computation Updates (`compute-twenty-orm-exception.ts`)
- Made function `async` and added optional `entityManager` and
`internalContext` parameters
- Needed to support async database queries for conflicting record lookup
### 5. GraphQL Error Handler
(`twenty-orm-graphql-api-exception-handler.util.ts`)
- **Changes**: Enhanced `DUPLICATE_ENTRY_DETECTED` case to include
`conflictingRecordId` and `conflictingObjectNameSingular` in GraphQL
error extensions
## Frontend Changes
### 1. Error Extraction Utility
(`get-conflicting-record-from-apollo-error.util.ts`)
- Accesses GraphQL error extensions
- Validates that both `conflictingRecordId` and
`conflictingObjectNameSingular` exist and are strings
- Returns `null` if validation fails
### 2. SnackBar Enhancement (`useSnackBar.ts`)
- Extracts conflicting record info from Apollo error
- Constructs URL using `getAppPath` utility
- Adds link object to snackbar options with text "View existing record"
<img width="931" height="858" alt="image"
src="https://github.com/user-attachments/assets/28137dc7-18ab-4ffe-b669-1f2d4ec264d1"
/>
This PR adds relative date filter operand to dashboard filters :
<img width="1685" height="558" alt="image"
src="https://github.com/user-attachments/assets/a1f927e7-8c99-4171-b487-4b6a28779547"
/>
It also refactors the logic to add timezone and first day of the week
taking users preferences.
It has been tested on workflows and regular advanced filters also.
For step filters, which use a JSON format to store relative date
filters, I kept the current way to handle it.
There are a few workspaces that use a relative date filter in step
filter, so we want to avoid a migration, and instead handle both code
paths, and refactor everything to JSON later.
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
- moves applicationRoleId to application entity
- add new `APPLICATION` FieldActorSource and `APPLICATION`
JwtTokenTypeEnum value
- create a new token with applicationId when executing a function
- when applicationId is in token, check for application.defaultRole
permissions
-use twenty-shared types in `twenty-sdk/application`
- create a new import from generate called "Twenty" that you can use
directly without having to set TWENTY_API_KEY AND TWENTY_API_URL (keep
metadata or core parameter only)
- provide to serverless unique one time BEARER TOKEN to run it
Result
<img width="977" height="566" alt="image"
src="https://github.com/user-attachments/assets/e78428a0-5b13-4975-aa13-58ee3b32450c"
/>
<img width="910" height="596" alt="image"
src="https://github.com/user-attachments/assets/6ec72bf5-7655-4093-a45e-ad269595a324"
/>
<img width="741" height="568" alt="image"
src="https://github.com/user-attachments/assets/7683944c-fd79-4417-8fb2-8e4815cc112f"
/>
Fixes Issue: #15797
This PR adds a configurable click behavior setting for Phone, Email, and
Links field types, allowing users to customize what happens when
clicking on these fields.
A new option (**Click Behaviour**) to configure the default behaviour
for onClick of data is added in the settings page (Settings → Data Model
→ Object → Field Edit) for Phone, Email and Links data types.
Users can now choose between two actions:
- **Copy to clipboard**: Copies the value to clipboard with a success
toast message
- **Open as link**: Opens the value as a link (tel:, mailto:, or
http/https)
The default behaviour is persisted for all these three types(phone- Copy
to clipboard, email & links: open link) to maintain backward
compatibility.
**Screenshots :**
<img width="2084" height="1736" alt="image"
src="https://github.com/user-attachments/assets/eb5d129c-e3e0-4334-b426-eb38d8a4840c"
/>
<img width="1474" height="1428" alt="image"
src="https://github.com/user-attachments/assets/487f4e44-6151-4254-bc5c-5398be5fc087"
/>
<img width="1594" height="1398" alt="image"
src="https://github.com/user-attachments/assets/abdbc213-7223-4d57-809e-4d55c90cda80"
/>
---------
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
# **feat: Add tooltips for truncated names in navigation components**
### **Summary**
Adds hover tooltips across navigation components so users can view full
names when text is truncated due to limited space.
---
# **Changes Made**
### **Sidebar Navigation**
* Added tooltips to navigation drawer items and menu labels.
* Long workspace or company names now show full text on hover.
### **Top Bar**
* Added tooltip support to the View Picker dropdown header.
* Long view names (e.g., *“AuraML, very very long name”*) now reveal
their full value on hover.
---
# **Implementation**
* Integrated `OverflowingTextWithTooltip` from `twenty-ui/display`.
* Wrapped navigation and view labels with the component.
* Updated layout styles to allow accurate overflow detection.
* Tooltip appears **only** when truncation occurs; short names show no
tooltip.
---
# **User Experience**
Users can hover over any truncated workspace, company, navigation, or
view name to see the complete text—improving clarity, accessibility, and
overall navigation usability.
[https://github.com/user-attachments/assets/97f9ea6c-409c-4ec5-a27c-a7b1c5aa5682](https://github.com/user-attachments/assets/97f9ea6c-409c-4ec5-a27c-a7b1c5aa5682)
---
### **Fixes**
Closes **#16412**
---
---------
Co-authored-by: Devessier <baptiste@devessier.fr>
Issue : #15794
## Context
As shown in the linked issue, slash menu dropdown for rich text editor
at bottom of notes editor screen extended beyond the page container,
causing main page to scroll.
## Changes Made
Added floating UI's `flip()` middleware to the component in
`CustomSlashMenu.tsx` so that the menu automatically flips upward when
detecting not enough space for dropdown. This prevents overflow beyond
the page container.
Additionally, added offset to menu when flipping up to prevent
overlapping of the current line / caret.
## Results
https://github.com/user-attachments/assets/47a79468-779c-4fd7-8e22-2b496456ea92
---------
Co-authored-by: Hyeonjae Park <hyeonjaep@Hyeonjaes-MacBook-Pro.local>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
## What’s done
- Clicking a favorite folder on mobile now opens a clean drill-down
layer with only its contents
- Added a back button (icon + folder name) — visually 100% identical to
`NavigationDrawerBackButton`
- Sidebar no longer collapses when opening a folder
- Tree line is removed on mobile (consistent with current Twenty design)
## Intentional deviations from the mockup
1. **Back button does not replace `MultiWorkspaceDropdownButton`**
Kept `NavigationDrawerHeader` untouched — it’s responsible for the
hamburger menu and workspace name.
Added the back button as a separate block below.
→ This feels cleaner architecturally and avoids breaking other places
that use `NavigationDrawerHeader`.
2. **Vertical spacing between items is slightly tighter than in the
mockup**
Used `NavigationDrawerSubItem` — the standard component for nested items
in Twenty.
Current production spacing matches this exactly, so I preferred
consistency over pixel-perfect mockup matching.
Happy to adjust either point if the team prefers to follow the mockup
1:1.
Closes#15950
---------
Co-authored-by: Baptiste Devessier <baptiste@devessier.fr>