**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>
Fixes#17253
When clicking 'No {Field}' in a polymorphic relation picker, the
relation was not being cleared. This commit implements the missing
detach logic:
- RecordDetailMorphRelationSectionDropdownManyToOne: Call onSubmit with
newValue: null when no item is selected
- MorphRelationManyToOneFieldInput: Call persistMorphManyToOne with
valueToPersist: null for detach case
- useMorphPersistManyToOne: Implement actual detach logic that sets all
morph relation ID fields to null via updateOneRecord
Also adds unit tests for the useMorphPersistManyToOne hook.
This PR handles SSE create event.
It also fixes a regression on board, which was making it flash with
skeletons on any update / create.
This PR was a good test case to experience how each main components of
the app : table, board, calendar, reacts to a create event.
It is not straightforward for now, because each component handles
records with its own state management to turn those records into a
coherent display of rows or cards.
The requests for each component are also different : fetch more, group
by, multiple requests in parallel, so the cleanest way to handle
optimistic effect requires to create one small optimistic engine per
component tuned for its internal data logic.
For now we've decided to implement what's doable in a reasonable amount
of time and that includes not handling table with groups for now.
Creating a clean optimistic logic for each component will be done later.
# QA
## Importing 100 records via the API (could be a script, a workflow, an
AI calling tools, a CSV import, etc.)
https://github.com/user-attachments/assets/d38a3770-8b0a-4f83-8275-5e7d1be0a5c6
## Creating from different components and seeing the SSE event being
processed by other components
https://github.com/user-attachments/assets/106b2f49-19cb-4190-92b7-0653c8373366
## Overview
This PR implements **generic many-to-many relation support** through
junction tables (also known as associative entities or join tables).
This replaces the need for hardcoded taskTarget/noteTarget logic and
provides a flexible foundation for modeling complex entity
relationships.
## Architecture
### Data Model
Many-to-many relationships are implemented using a **junction object
pattern**:
```
┌─────────┐ ┌──────────────────┐ ┌─────────┐
│ Pet │──────>│ PetRocket │<──────│ Rocket │
│ │ 1:N │ (junction) │ N:1 │ │
│ rockets ├───────┤ pet : Pet ├───────┤ │
└─────────┘ │ rocket : Rocket │ └─────────┘
└──────────────────┘
```
The junction object (PetRocket) has:
- A `MANY_TO_ONE` relation to **Pet** (the source)
- A `MANY_TO_ONE` relation to **Rocket** (the target)
The source object (Pet) has a `ONE_TO_MANY` relation pointing to the
junction, with **field settings** that specify which target field to
follow.
### Field Settings Schema
Junction configuration is stored in `FieldMetadataRelationSettings`:
```typescript
{
relationType: "ONE_TO_MANY",
// Points to the target field on the junction object
junctionTargetFieldId?: string; // For regular relations
junctionTargetMorphId?: string; // For polymorphic relations
}
```
**Two configuration modes:**
1. **`junctionTargetFieldId`** - References a specific `RELATION` field
on the junction
2. **`junctionTargetMorphId`** - References a `morphId` group for
polymorphic targets (e.g., link to Person OR Company)
### GraphQL Query Generation
When a junction relation is detected, the GraphQL fields are generated
to fetch the nested target:
```graphql
query GetPetWithRockets {
pet(id: "...") {
rockets { # ONE_TO_MANY to junction
id
rocket { # Target field on junction
id
name
__typename
}
}
}
}
```
For polymorphic junction targets:
```graphql
caretakerPerson { id, name }
caretakerCompany { id, name }
```
## Frontend Architecture
### Display Flow
1. **Detection**: `hasJunctionConfig()` checks if field has junction
settings
2. **Config Resolution**: `getJunctionConfig()` resolves junction object
metadata and target fields
3. **Record Extraction**: `extractTargetRecordsFromJunction()` extracts
target records from junction records
4. **Rendering**: Target records displayed as chips (not junction
records)
### Edit Flow
1. **Picker Opening**: Initializes the multi-record picker with:
- Searchable object types (derived from junction target fields)
- Pre-selected items (extracted from existing junction records)
2. **Selection Handling**: Manages create/delete of junction records:
- **Select**: Creates new junction record with source + target IDs
- **Deselect**: Finds and deletes the junction record
- **Optimistic Updates**: Manually updates Recoil store before API call
### Key Trade-offs
| Decision | Trade-off |
|----------|-----------|
| Junction records managed manually | More control over optimistic
updates, but requires manual cache management |
| Settings stored per-field | Flexible (same junction can power
different views), but requires UI to configure |
| Polymorphic via morphId groups | Supports N target types, but adds
query complexity |
| Feature flag gated | Safe rollout, but requires flag management |
## Backend Changes
- **Validation**: Junction target field must exist and be a valid
`MANY_TO_ONE` relation
- **Settings**: Extended `FieldMetadataRelationSettings` type with
junction fields
- **Dev Seeder**: Added sample junction objects (PetRocket,
EmploymentHistory, PetCareAgreement) for testing
## How to Test
1. Enable the `IS_JUNCTION_RELATIONS_ENABLED` feature flag
2. Create objects with junction pattern (Pet → PetRocket → Rocket)
3. Configure the junction target in field settings (advanced mode)
4. Verify:
- Display shows target objects (Rockets), not junction records
(PetRockets)
- Picker allows selecting/deselecting targets
- Changes persist correctly
https://github.com/user-attachments/assets/d04f057a-228c-4de8-af48-76bb2d72cac1
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
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, ...
## Context
- Add RLS entitlement to billing
- Check value in the backend (for RLS predicate entity
queries/mutations)
- Expose billingEntitlements to the API inside currentWorkspace to check
available features to the workspace and display the role components
accordingly
- Cleanup RLS when plan changes back to one without RLS.
This should cover almost everything, imho we don't need to check in the
ORM because => We can't create RLS without the correct PLAN and
switching back to a PLAN without RLS deletes existing RLS through
stripes webhooks
## Context
buildValueFromFilter was not handling composite filters which was needed
for RLS. This PR implements that and fix some issues with RLS
Tested with a few composite + relation fields
<img width="559" height="206" alt="Screenshot 2026-01-19 at 15 38 42"
src="https://github.com/user-attachments/assets/d64afa6e-3e12-4843-a215-a693665112c5"
/>
- 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.
This PR implements SSE update events across the main components of the
application : tables, boards, calendars, show pages.
There is still work to do on other event type and on making sure
everything works fine, but this first implementation should be robust
enough to start with.
Some problems encountered along the way :
- Events are returning raw Postgres output, because they are not
normalized by the GraphQL layer, so we ended up with amountMicros as
string values, which the frontend does not like, so I implemented a
small util in our `formatResult` generic pipeline to turn amountMicros
to a number if it's a string value. We could implement other formatters
for composite fields if we see problems with events.
-`action` property was missing in SSE events, which is required by the
frontend to know what kind of event it is.
# QA
https://github.com/user-attachments/assets/393b45ac-59d2-48b0-855b-2ce9c4b8ae57https://github.com/user-attachments/assets/cb214e7a-1595-4b85-bdb6-87630993d2e2
## 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.)
## Context
Now that RLS predicates are applied, creating a record through the FE
(which is empty by default) is failing if your role has predicates and
your input does not respect them (which will always be true since, as
said above, input will be pretty much empty)
## Implementation
- Moved isMatching* filters to twenty-shared
- Implemented isMatchingRlsPredicates utils in the backend (ORM) to
check before insertion/update if the record is matching the current user
role Rls predicates, reusing the isMatching* filters utils moved to
twenty-shared
- Frontend now applies RLS predicates before creating a new record
(similarly to what we do with view filters)
Note:
It seems composite were not properly handled with view-filter insertion
logic, since I'm reusing the util for now, the issue remains for RLS and
will need to be addressed
This PR refactors object record operation dispatch through a browser
event instead of a state that was registering all operations.
This is a cleaner pattern as it is a synchronous event code path instead
of relying on a useEffect to watch state change, which is not ideal.
We introduce this change first to then rely on this new pattern to
dispatch SSE events in a following PR.
This PR fixes two critical bugs with the DATE type, following up the
recent refactor around dates and time zones :
https://github.com/twentyhq/twenty/pull/16544
There are a lot of related bugs in Sentry, this PR should fix all bugs
that are of the form :
`Cannot parse: 2026-02-04T00:00:00.000Z`
# `node-postgres` date type
The package `node-postgres`, used by TypeORM, returns by default a Date
object for the date OID type.
But we can change this behavior without patching anything.
See the documentation for `pg-types` :
https://github.com/brianc/node-pg-types
Our fix is to call `setTypeParser` from `pg` to have the postgres "date"
type returned as a string always.
```ts
export const setPgDateTypeParser = () => {
types.setTypeParser(PG_DATE_TYPE_OID, (val: string) => val);
};
```
Then in server's main.ts :
```ts
const bootstrap = async () => {
setPgDateTypeParser();
```
## Are we safe with this very low-level fix ?
Since TypeORM bypasses a string value returned by `pg` we are safe with
this modification at a very low-level.
```ts
else if (columnMetadata.type === "date") {
value = DateUtils.mixedDateToDateString(value)
}
```
https://github.com/typeorm/typeorm/blob/0ec4079cd3760dc49247a02c54415f16a2a51766/src/driver/postgres/PostgresDriver.ts#L838
```ts
/**
* Converts given value into date string in a "YYYY-MM-DD" format.
*/
static mixedDateToDateString(value: string | Date): string {
if (value instanceof Date) {
return (
this.formatZerolessValue(value.getFullYear(), 4) +
"-" +
this.formatZerolessValue(value.getMonth() + 1) +
"-" +
this.formatZerolessValue(value.getDate())
)
}
return value
}
```
https://github.com/typeorm/typeorm/blob/6f3788b83730463e3b787b2a98bb41695e13caf8/src/util/DateUtils.ts#L28
For reference the original type parser seems to be configured in
node-pg-types, on the 1182 OID, which is an array of date / 1082 OID,
this type parser calls another small library `postgres-date` which
creates a JS Date. I couldn't find a type parser explicitly on 1082 in
the stack TypeORM -> node-postgres -> node-pg-types -> postgres-date
```ts
register(1182, parseStringArray) // date[]
```
https://github.com/brianc/node-pg-types/blob/26bfe645a8ddc0c73830b1b8c63f2c4f8265b24f/lib/textParsers.js#L168C19-L168C45
```ts
static parse (dateString) {
return new PGDateParser(dateString).getJSDate()
}
```
https://github.com/bendrucker/postgres-date/blob/b3060560ed62c250f7800d29e4b68a9d5a0622bd/index.js#L285
# Calendar view mixing DATE and DATE_TIME
At the time of the refactor, DATE type wasn't properly tested with
calendar view, thus the drag and drop of a card with a calendar view on
a DATE field and not a DATE_TIME field, was not working, this is now
fixed.
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
This PR is a follow-up of https://github.com/twentyhq/twenty/pull/16966
and implements a new mechanism to handle SSE events.
It creates only one event stream per browser tab, then use mutations to
tell the backend which query to listen to, without re-mounting the event
stream connexion.
Then each event that comes from this unique subscription is then
dispatched in a new JavaScript CustomEvent, per queryId, on which
specific hooks add an event listener.
This PR introduces the generic tooling as well as the handling of update
events on table.
## Summary
This PR introduces row-level security (RLS) permissions for roles in the
frontend, allowing fine-grained access control at the record level.
Users can now define permission rules that determine which specific
records a role can access based on dynamic conditions and filters.
## What's Changed
Implemented UI for configuring record-level permissions on object
permissions screens
Added support for defining permission predicates using filter conditions
(similar to advanced filters)
Introduced variable picker for dynamic permission rules (e.g., "me"
context for user-specific access)
Built predicate conversion layer to sync UI state with backend
permission structure
Extended GraphQL schema with mutations for upserting row-level
permission predicates
Fixed handling of orphaned RLS groups to prevent data inconsistencies
Added enterprise key validation for RLS features
The implementation enables scenarios like "users can only see their own
records" or "users can access records associated with their team."
<img width="687" height="702" alt="Screenshot 2026-01-09 at 23 07 02"
src="https://github.com/user-attachments/assets/33fe736e-6cbf-40bd-b2eb-c8a90c8d21bc"
/>
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
This PR refactors the table virtualization real index main state that
was creating performance problems.
The problem was that we kept track of all the real rows even if we only
print 200 at a time in the application.
This caused a crash when some users with 10k+ rows on a table tried to
iterate over this state 10.000 times or more.
The trade-off is that now we keep all real indices in a Map, and each
virtual row uses a selector to go to this Map.
This way if we want to reset the full Map, we just have to overwrite the
base state that contains the map, and the 200 selectors will recompute.
This happens for example when we delete a row. This now has a limited
and negligible performance impact.
**Description**
Fixes the "Add new" button appearing in relation picker dropdowns for
workspace member relations (e.g., Account Owner on companies). Workspace
members cannot be created from relation pickers, so this button should
not appear.
**Changes Made**
- Modified RelationManyToOneFieldInput.tsx to conditionally pass the
onCreate prop to SingleRecordPicker only when
createNewRecordAndOpenRightDrawer is defined.
- Moved the conditional check to the JSX level for clarity.
**Technical Details**
The useAddNewRecordAndOpenRightDrawer hook already returns undefined for
workspace member relations, but the component was still passing a
handler function to SingleRecordPicker, causing the "Add new" button to
appear. The fix ensures that when createNewRecordAndOpenRightDrawer is
undefined, onCreate is also undefined, which prevents
SingleRecordPickerMenuItemsWithSearch from rendering the button.
Testing
- Verified that the "Add new" button no longer appears in the Account
Owner dropdown for companies.
- Confirmed that the "Add new" button still appears for other relation
fields where it's appropriate (e.g., People, Opportunities).
- Tested that existing functionality for selecting workspace members
from the dropdown still works correctly.
Fixes#17060
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
Closes#17032
Summary
When a sort is already applied to a column, clicking "Sort" from the
column header dropdown now toggles the sort direction (ASC → DESC or
DESC → ASC) instead of doing nothing.
Problem
Previously, clicking "Sort" on a column that already had a sort applied
would always try to create a new sort with ASC direction. Since the
upsertRecordSort function updates an existing sort with the same field,
this effectively did nothing when the sort was already ASC—which felt
unintuitive.
Solution
Modified useHandleToggleColumnSort to:
- Check if a sort already exists for the clicked field
- If it exists, toggle the direction
- If it doesn't exist, create a new sort with ASC direction (existing
behavior)
---
Contribution by Gittensor, see my contribution statistics at
https://gittensor.io/miners/details?githubId=132382032
## Summary
This PR enables sorting records by fields of related objects. For
example, sorting **People by their Company's name**.
### Before
Only scalar and composite fields could be sorted. Relation fields showed
in the sort dropdown but produced errors.
### After
Many-to-One relation fields can now be sorted using the related object's
**label identifier field** (e.g., Company's `name`).
---
## Changes
### Frontend
- Added `RELATION` to sortable field types (restricted to `MANY_TO_ONE`
relations)
- New `getOrderByForRelationField()` generates nested orderBy structures
using the related object's label identifier
- Updated `turnSortsIntoOrderBy()` to handle relation fields by looking
up related object metadata
### Backend
- Extended `GraphqlQueryOrderFieldParser.parse()` to detect nested
relation ordering like `{ company: { name: 'AscNullsLast' } }`
- Returns `ParseOrderByResult` containing both `orderBy` conditions and
`relationJoins` info
- Added LEFT JOINs for relation ordering in `applyOrderToBuilder()`
- Added `addRelationOrderColumnsToBuilder()` for TypeORM DISTINCT
compatibility
### Tests
- Added unit tests for `filterSortableFieldMetadataItems`,
`getOrderByForRelationField`, and `turnSortsIntoOrderBy`
- Added integration tests covering ascending/descending order and
composite label identifiers
---
## TypeORM Bug Workaround
We encountered a significant TypeORM limitation when implementing this
feature. When using `getMany()` with `ORDER BY` on joined relation
columns, TypeORM generates a DISTINCT subquery that has specific
requirements:
### Issue 1: Alias Parsing
TypeORM's `orderBy()` method fails with **"alias not found"** when using
quoted SQL identifiers like `"company"."name"`. TypeORM internally
expects unquoted property paths (e.g., `company.name`) for its alias
resolution mechanism.
### Issue 2: setFindOptions Clears addSelect
`setFindOptions({ select })` **clears any previously added `addSelect()`
columns**. This caused `"column distinctAlias.company_name does not
exist"` errors because the relation columns needed for ORDER BY were
being removed.
### Solution
We split the logic into two methods:
1. `applyOrderToBuilder()` - adds JOINs and ORDER BY (before
`setFindOptions`)
2. `addRelationOrderColumnsToBuilder()` - adds relation columns for
SELECT (AFTER `setFindOptions`)
This ensures the relation columns are present in the final SQL query's
SELECT clause with the proper underscore aliases (`company_name`) that
TypeORM's DISTINCT subquery expects.
**Related TypeORM issue**:
https://github.com/typeorm/typeorm/issues/9921
---
## Screenshots/Demo
_Add screenshots if applicable_
### Problem:
If the command menu page is kept opened while navigating to record show
page then the app crashes.
### Root cause
When navigatiing to a record show page while the command menu page is
kept opened, it tries to open the record show page with command menu
open, as its state were set to true , before navigating to a new page
along with the instance id of previous context which are used in the
current context while rendering the page. Hence, leading to an error
page.
### Changes
while navigation to a record show page the command menu open state is
set to false. This is to handle the navigation gracefully
Fixes: #16577
**Bug Fixed**
Creating a new record in the People table view caused a browser memory
crash ("Paused before potential out of memory crash").
**Root Cause**
In useResetVirtualizationBecauseDataChanged.ts, when creating a record,
a loop iterated from 0 to totalNumberOfRecordsToVirtualize (the total
database count).
**Fix Applied**
Changed the loop to only iterate through indices from actually loaded
pages
Fixes https://github.com/twentyhq/twenty/issues/16980
## Summary
This PR introduces a Skills system for AI agents, inspired by the [Agent
Skills specification](https://agentskills.io/specification).
## Changes
### Backend
- **SkillEntity**: New database entity with migration for storing skills
- **V2 Sync Mechanism**: Implemented FlatSkill, builders, validators,
and action handlers following the v2 flat entity pattern
- **Standard Skills**: Pre-defined skills (workflow-building,
data-manipulation, dashboard-building, metadata-building, research,
code-interpreter, xlsx, pdf, docx, pptx)
- **GraphQL API**: CRUD operations for skills with proper guards and
permissions
- **Workspace Cache**: Integrated skills into the workspace cache system
### Frontend
- **Skills Table**: Searchable table in AI settings showing all skills
- **Skill Form**: Create/edit page with Label (primary), Description,
and Content (markdown editor)
- **API Name**: Following existing patterns, name is derived from label
with advanced settings toggle for custom API names
- **Standard vs Custom**: Standard skills are read-only, custom skills
can be edited/deleted
## Key Design Decisions
- Skills are stored in the database (Salesforce-like approach) rather
than files
- Name is derived from Label by default (isLabelSyncedWithName pattern)
- Skills reference functions/files via @ mentions in markdown content
rather than explicit relations
- Standard skills are synced from code, custom skills are created via UI
## Screenshots
Skills table and form UI follow existing settings patterns.
## Testing
- [x] Lint passes
- [x] Typecheck passes
- [ ] CI tests
#16383
## Scope & Context
**Context**:
Added a **Bulk Update** feature to allow users to modify multiple
records simultaneously. This addresses the need for efficient data
management when dealing with large datasets.
**Scope**:
Enabling a "Bulk Update" flow that can be triggered for a filtered list
of records (e.g., current view). The feature guides the user through
selecting fields to modify, inputting new values, and executing the
update with real-time progress feedback.
## Current behavior
Currently, users must open and update records one by one.
To change the "Status" of 10 different opportunities, the user has to
navigate to each record detail page or use the inline cell editor 10
separate times. This is repetitive and time-consuming.
## Expected behavior
Users can trigger "Update Records" from the command menu or action bar.
1. **Choose Fields**: A step where users select which properties they
want to modify (e.g., check "Status" and "Assignee").
2. **Input Values**: Users provide the new values for the selected
fields.
3. **Execution**: Upon confirmation, the system updates all matching
records in the background.
4. **Feedback**: A progress indicator shows the number of processed
records, and a toast notification confirms completion.
*Key interactions:*
- Users can clear a field's value (set to null) by selecting the field
but leaving the input empty.
- The operation supports cancelling midway.
https://github.com/user-attachments/assets/c87366a3-246e-4615-9941-0bf63d70df86
## Technical inputs
**Core Functionality**:
- **Incremental Batch Processing**: Updates are performed in small
batches (using `useIncrementalUpdateManyRecords` to handle large
datasets without timing out or freezing the UI.
- **Global Feedback**: Integrated with the Snackbar system to notify
users of success or errors across the application.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Introduces localization keys and generated messages to support the new
bulk update workflow.
>
> - New strings for command menu selection info: `{totalCount} selected`
> - Footer actions for bulk update: `Apply`, `Cancel` in
`UpdateMultipleRecordsFooter`
> - Toasts in `UpdateMultipleRecordsContainer`: `Successfully updated
{count} records`, `Failed to update records. Please try again.`
> - Action labels: `Update`, `Update records` in
`DefaultRecordActionsConfig` and command menu hook
> - Adds/updates entries across multiple locale `.po` files and
regenerates `af-ZA` messages
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b1dce4c599b85fa2947ca63b265fc750e80d9bb6. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Samuel Arbibe <samuelarbibe@Samuels-MacBook-Pro.local>
Co-authored-by: Félix Malfait <felix@twenty.com>
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>
# Introduction
This has been fixed on main already 1 hour ago, this PR now only passes
the field application id instead of the object applicationId that could
be different for example when creating a custom field on a standard
object
For the moment not introducing any logic around application integrity
directly and still relying on the isCustom and standardId definition
This will have to be refactored once we deprecate the standardId
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> Implements last-modifier tracking and unified actor injection.
>
> - New `ActorFromAuthContextService` replaces createdBy-only logic;
pre-query hooks now inject both `createdBy` and `updatedBy` on create
and `updatedBy` on update
> - Add `updatedBy` standard field to core/custom objects (e.g.,
`person`, `company`, `task`, `note`, `attachment`, `dashboard`,
`workflow`, `workflowRun`) with IDs, metadata builders, and ORM entity
fields
> - Record CRUD: `create` now sets both `createdBy` and `updatedBy`;
`update` sets `updatedBy`; workflow actions use a shared workflow actor
builder; REST base handler uses the new actor service
> - Seeder data and snapshots updated to include `updatedBy`; GraphQL
result formatting adds defaults/handling for empty composite fields
(incl. actor)
> - Frontend `useUpdateOneRecord` upserts returned record into the local
store after mutation
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1f283778e9cb28a35bf84632a1a2997250bb3131. 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: Félix Malfait <felix@twenty.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>