Commit Graph

5496 Commits

Author SHA1 Message Date
Etienne 56056b885d refacto - remove hello-pangea/dnd from navigation-menu-item module (#19033)
Removed all @hello-pangea/dnd imports from the navigation-menu-item/
module by replacing the type bridge layer with a native
NavigationMenuItemDropResult type. The dnd-kit events were already
powering all DnD — hello-pangea was only used as a type contract and one
dead <Droppable> component.
3 files deleted (dead <Droppable> wrapper, DROP_RESULT_OPTIONS shim,
toDropResult bridge), 4 files edited (handler signatures simplified,
bridge removed from orchestrator, utility type narrowed), 1 type
created. Zero behavioral changes, typecheck and tests pass.
2026-03-27 11:23:25 +00:00
BugIsGod 68f5e70ade fix: sign file URLs in timeline and file loss on click outside (#19001)
Fixes: #18943
## Problem

  Two bugs related to the Files field:

1. **File loss on click outside**: When `MultiItemFieldInput` was not in
edit mode (input hidden), clicking outside would still call
`validateInputAndComputeUpdatedItems()` with an empty `inputValue` and
`itemToEditIndex = 0`, causing the first file to be silently deleted.
<img width="624" height="332" alt="image"
src="https://github.com/user-attachments/assets/657aff2c-e497-4685-b8b7-fa22aba772f8"
/>


2. **Unsigned file URLs in timeline activity**: FileId, FileName stored
in `timelineActivity.properties.diff` (before/after values) were not
being signed (timeActivity.properties is stored as json in database),
making them inaccessible from the
frontend.

<img width="1092" height="103" alt="image"
src="https://github.com/user-attachments/assets/23d83ea3-4eb9-41ef-a99c-c4515d1cfb35"
/>


 ## Reproduction


https://github.com/user-attachments/assets/e75b842b-5cbb-46e8-a923-ac9df62deb98

## Changes
- `MultiItemFieldInput.tsx`: Wrap the validate + onChange logic in `if
(isInputDisplayed)` so it only runs when the input is actually open.
- `timeline-activity-query-result-getter.handler.ts`: New handler that
iterates over `properties.diff` fields and signs any file arrays found
in `before`/`after` values (call same method:
`fileUrlService.signFileByIdUrl` as table field view
`FilesFieldQueryResultGetterHandler`.
- `common-result-getters.service.ts`: Register the new handler for
`timelineActivity`.


## After 


https://github.com/user-attachments/assets/368c7be1-3101-43a2-ac93-fe1b0d8a7a37
2026-03-27 08:28:30 +00:00
Félix Malfait 08077476f3 fix: remove remaining direct cookie writes that make tokenPair a session cookie on renewal (#19031)
## Summary

- Removes two remaining direct `cookieStorage.setItem('tokenPair', ...)`
calls that were overwriting the Jotai-managed cookie (180-day expiry)
with a session cookie (no expiry) during **token renewal**
- Followup to #18795 which fixed the same issue in `handleSetAuthTokens`
but missed the renewal code paths

## Root cause

Two token renewal paths still had direct cookie writes without
`expires`:

1. **`apollo.factory.ts`** — `attemptTokenRenewal()` fires on every
`UNAUTHENTICATED` GraphQL error after a successful token refresh
2. **`useAgentChat.ts`** — `retryFetchWithRenewedToken()` fires on 401
from the AI chat endpoint

Both called `cookieStorage.setItem('tokenPair', JSON.stringify(tokens))`
without an `expires` attribute, creating a session cookie that overwrote
the Jotai-managed one. This is why the bug was **intermittent after
#18795**: it only appeared after a token renewal, not on fresh login.

The `onTokenPairChange` / `setTokenPair` calls already write through
Jotai's `atomWithStorage` → `createJotaiCookieStorage`, which always
sets `expires: 180 days`.

## Test plan

- Log in to the app
- Wait for a token renewal to occur (or force one by letting the access
token expire)
- Inspect the `tokenPair` cookie in DevTools → Application → Cookies
- Verify the cookie retains an expiration date ~180 days from now (not
"Session")
- Close and reopen the browser — confirm you remain logged in


Made with [Cursor](https://cursor.com)
2026-03-27 08:21:26 +00:00
Rayan Salhab 6f0ac88e20 fix: batch viewGroup mutations sequentially to prevent race conditions (#19027)
## Bug Description

When reordering stages in the Kanban board, the frontend fires all
viewGroup update mutations concurrently via Promise.all, causing race
conditions in the workspace migration runner's cache invalidation,
database contention, and a thundering herd effect that stalls the
server.

## Changes

Changed `usePerformViewGroupAPIPersist` to execute viewGroup update
mutations sequentially instead of concurrently. The `Promise.all`
pattern fired all N mutations simultaneously, each triggering a full
workspace migration runner pipeline (transaction + cache invalidation).
The sequential `for...of` loop ensures each mutation completes
(including its cache invalidation) before the next begins, eliminating
the race condition.

## Related Issue

Fixes #18865

## Testing

This fix addresses the root cause identified in the Sonarly analysis on
the issue. The concurrent mutation pattern was causing:
- PostgreSQL row-level lock contention on viewGroup rows
- Cache thundering herd from repeated invalidation/recomputation cycles
- Server stalls requiring container restarts

The sequential approach ensures proper ordering and prevents these race
conditions.

---------

Co-authored-by: Rayan <rayan@example.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-27 08:13:51 +00:00
Charles Bochet 17424320e3 fix: gate command-menu-items query behind feature flag (#19029)
## Summary
- Gate the `FindManyCommandMenuItems` GraphQL query behind the
`IS_COMMAND_MENU_ITEM_ENABLED` feature flag on the frontend, preventing
an uncaught error when the flag is not enabled for a workspace
- Remove `IS_CONNECTED_ACCOUNT_MIGRATED` from the default feature flags
list
2026-03-27 08:11:26 +01:00
nitin 31718d163c [Dashboards] fix rich text widget AGAIN (#19017) 2026-03-26 17:50:10 +01:00
Abdul Rahman f47608de07 Clear navbar edit selection when closing the side panel (#18940)
In navbar edit mode, selecting an item for edit stored
selectedNavigationMenuItemIdInEditModeState (and related
pending-insertion state). Closing the side panel did not reset those
atoms, so the nav item stayed visually selected. Reset both when the
panel closes so the highlight matches the closed panel; reopening in
edit mode then starts from the generic “new item” entry unless the user
picks an item again.
2026-03-26 17:49:50 +01:00
Thomas des Francs 695518a15e Fix not shared chip height + hard coded border radius (#19020)
## Summary
- align the forbidden field "Not shared" chip with small chip dimensions
in the object table
- use the shared small border radius token instead of a hardcoded value
- add `overflow: hidden` and `user-select: none` to match chip behavior
more closely

## Testing
- Not run (not requested)
2026-03-26 17:48:20 +01:00
github-actions[bot] 9eba54134d i18n - translations (#19019)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-26 16:50:18 +01:00
Harshit Singh 34832815e6 fix: update settings page for mobile viewport (#18991)
## Description

- This PR fixes
https://github.com/twentyhq/core-team-issues/issues/2313#issuecomment-4116380772
- Fixes Admin Panel Apps table, Data model table and Roles table
- made data model table scrollable for mobile viewport keeping name
column fixed.


## Visual Appearance

## Before

- Roles Table

<img width="636" height="1039" alt="Screenshot 2026-03-26 at 1 28 30 PM"
src="https://github.com/user-attachments/assets/fa7532bd-aeb0-4c8f-a40e-08cc976cd2c2"
/>



- Admin Apps table

<img width="610" height="998" alt="Screenshot 2026-03-26 at 1 28 12 PM"
src="https://github.com/user-attachments/assets/8712473c-350f-46f8-9e68-9cb0a5fa9d80"
/>





- Data Model table



https://github.com/user-attachments/assets/c48075c5-56dd-4b76-acd4-76330d6dab94






## After

- Roles Table

<img width="761" height="1045" alt="Screenshot 2026-03-26 at 1 23 19 PM"
src="https://github.com/user-attachments/assets/17099956-816a-4d41-b987-563f6931a995"
/>

- Admin Apps table

<img width="794" height="1044" alt="Screenshot 2026-03-26 at 1 23 34 PM"
src="https://github.com/user-attachments/assets/0463a087-2363-4192-9adb-7d27076f303a"
/>


- Data model Table


https://github.com/user-attachments/assets/fd44e353-cc7c-44cf-bda0-d2f3cd531f2e

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2026-03-26 16:47:40 +01:00
Félix Malfait 7a341c6475 feat: support authType on AI providers for IAM role authentication (#19016)
## Summary
- Adds an `authType` field (`'api_key' | 'access_key' | 'iam_role'`) to
AI provider config
- Providers like Amazon Bedrock that authenticate via IAM role (instance
profile) can now be registered without explicit API keys or access keys
- Backend: `isProviderConfigured()` checks `apiKey || accessKeyId ||
authType`
- Frontend admin panel: shows green "Configured" badge and "IAM role"
description for providers with `authType: "iam_role"`
- Provider detail page shows "IAM role (instance profile)" in the
credentials row

## Companion PR
- twentyhq/twenty-infra#528 — patches `authType: "iam_role"` into
dev/staging Bedrock catalogs

## Changed files
- **Backend**: new `AiProviderAuthType` type, `isProviderConfigured`
util, updated registry + resolver
- **Frontend**: new `AiProviderAuthType` type, updated provider list
card + detail page

## Test plan
- [ ] Deploy with a Bedrock catalog that includes `"authType":
"iam_role"` — verify Bedrock shows "Configured" in admin AI panel
- [ ] Verify OpenAI/Anthropic with `apiKey` still show "Configured"
- [ ] Verify a provider with no credentials and no `authType` still
shows "No credentials"

Made with [Cursor](https://cursor.com)
2026-03-26 16:43:09 +01:00
Lucas Bordeau 8ef99671e2 Fix board drag and drop issues (#19005)
This PR solves multiple problems : 
- An infinite loop that was happening on board with initial and fetch
more queries
- Warning messages that get triggered when we drag and drop multiple
times in a row
- An attempt to fix an existing error in Sentry, that couldn't be
reproduced for now.
2026-03-26 15:06:20 +00:00
Lucas Bordeau e63a23ea00 Fix create new with filters (#18969)
Fixes https://github.com/twentyhq/twenty/issues/18949

## Problem
- Creating a record from filters with DATE_TIME fields produced empty
objects instead of dates — `isPlainObject` from `twenty-shared` treats
`Date` instances as plain objects, so `mergeCompositeValues` spread them
into `{}`
- `buildRecordInputFromFilter` applied composite merge logic to all
field types indiscriminately, including primitives, dates, and strings
- DATE_TIME "is before" filters produced exact boundary values instead
of subtracting a minute

## Fix
- Composite and non-composite fields are now handled in separate
branches — `buildRecordInputFromFilter` uses `isCompositeFieldType` to
decide whether to merge or assign directly
- `mergeCompositeValues` extracted to its own file with a properly typed
signature (`Record<string, unknown>`) — no more runtime type guessing
- DATE_TIME fields with `IS_BEFORE` operand subtract one minute using
`subMinutes` from `date-fns`
- 13 unit tests for `mergeCompositeValues` covering all composite field
types (currency, address, full name, links, emails, phones) and
successive sub-field accumulation
2026-03-26 15:05:35 +00:00
github-actions[bot] 39b9ae6a76 i18n - translations (#19014)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-26 16:11:40 +01:00
nitin afeef8e04a feat: add command menu item to layout customization (#18764)
figma
https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=93630-365000&t=QesAkY3JOyI9D3UU-0


https://github.com/user-attachments/assets/cf3b354d-0b08-41ae-91ae-386054b5cb2e



https://github.com/user-attachments/assets/73bfd676-f823-44c9-a70d-107c20523664

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-26 16:04:03 +01:00
nitin b171a23216 [AI] replace custom styles with MenuItem and SidePanelGroup in AI agent persmissions tab (#19003)
closes
https://discord.com/channels/1130383047699738754/1486666682553598032
2026-03-26 14:33:07 +00:00
github-actions[bot] f771b13a20 i18n - translations (#19011)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-26 15:35:49 +01:00
nitin 92635b960b [AI] Navigation panel scroll refactor + Non chat placeholder (#18999)
<img width="374" height="1319" alt="CleanShot 2026-03-26 at 16 19 34"
src="https://github.com/user-attachments/assets/bebc7f67-3e9d-47c2-8e13-b57dab83d923"
/>

<img width="438" height="1320" alt="CleanShot 2026-03-26 at 16 19 20"
src="https://github.com/user-attachments/assets/e48ae2d2-066e-402f-a04b-aba7bae776bb"
/>


https://github.com/user-attachments/assets/64a8ad9b-0030-414c-a67d-3817ac3dd6b0
2026-03-26 14:20:48 +00:00
Thomas Trompette 2e015ee68d Add missing row lvl permission check on Kanban view (#19002)
The Kanban view builds a query in two layers:
- Inner query — selects actual records from the table (has all the
permission context)
- Outer query — wraps the inner query's raw SQL string to do
grouping/pagination
The problem: the inner query's SQL is copied out as a plain string
before RLS predicates are added to it. RLS predicates are normally added
lazily when you execute the query, but here the execution happens on the
outer query — which doesn't know about the entity or its RLS rules.

So RLS predicates are never applied anywhere.

The fix: explicitly apply RLS predicates to the inner query before its
SQL is extracted.

Additonnaly, fixed a temporal issue in Datetime pickers.
2026-03-26 14:20:05 +00:00
Abdul Rahman 36c7c99e34 fix: hide objects with no addable views in sidebar view picker (#18993)
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-26 13:13:49 +00:00
Abdul Rahman b651a74b1f fix: hide "Move to folder" when no destination folder is available (#18992) 2026-03-26 10:41:00 +00:00
nitin 29979f535d [Ai] fix overflow on new chat button (#18996)
before - 

<img width="368" height="220" alt="CleanShot 2026-03-26 at 15 38 38"
src="https://github.com/user-attachments/assets/c3a87ffd-bd84-408e-b2fb-fd4ce3ce8d61"
/>


after - 

<img width="418" height="274" alt="CleanShot 2026-03-26 at 15 37 59"
src="https://github.com/user-attachments/assets/04d462c4-3cbc-4f9f-9ed5-8b90ae1f112c"
/>
2026-03-26 10:35:33 +00:00
nitin b1e449d764 fix dashboard widget edit mode content (#18965) 2026-03-26 10:18:23 +00:00
Félix Malfait c28e637ea7 fix: prevent empty array id.in filter in single record picker (#18995)
## Summary

- Fixes intermittent `INVALID_QUERY_INPUT` errors (152 occurrences / 2
users in Sentry) caused by the single record picker sending `{ id: { in:
[] } }` to the Search query when no records are selected
- The `skip` guard is logically correct but Apollo Client v4 can briefly
fire queries during React 18 render transitions before processing the
skip flag
- Makes `selectedIdsFilter` conditional on `hasSelectedIds`, so
variables contain a safe empty filter `{}` regardless of skip behavior —
matching the existing defensive pattern used by the third query's
`notFilter`

## Test plan

- [ ] Open a relation field picker (e.g., Person on an Opportunity) with
no existing value — search should load without errors
- [ ] Open a relation field picker with an existing value — selected
record should appear and search should work
- [ ] Clear a selected relation and reopen the picker — no console
errors


Made with [Cursor](https://cursor.com)
2026-03-26 10:17:59 +00:00
Baptiste Devessier cfefe9273b Use record page layouts in the merge records feature (#18961)
There was a design issue. It revealed that we weren't using record page
layouts in the merge records feature.

## Before

<img width="802" height="1283" alt="image"
src="https://github.com/user-attachments/assets/e4238144-e10e-47a6-83e6-7cc03ca89b15"
/>

## After


https://github.com/user-attachments/assets/c0fa9cf5-2b28-4696-bf20-8271dce9e62c
2026-03-26 09:54:33 +00:00
github-actions[bot] 5041b3e14b i18n - translations (#18990)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-26 08:41:15 +01:00
BugIsGod 1e2b31b040 fix: prevent saving API key with empty name (#18970)
Fixes: #18959 
Generally, users need to type name before they create the api key.


https://github.com/user-attachments/assets/bb3ec0ec-d05f-48a8-b762-a35288a9e111

<img width="656" height="559" alt="image"
src="https://github.com/user-attachments/assets/cb8fd461-98f0-4475-b3f0-0de1e62624e2"
/>
2026-03-26 07:25:57 +00:00
github-actions[bot] 340a01567a i18n - translations (#18988)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-26 07:18:15 +01:00
nitin 4985270e69 [AI] Refactor settings search to use SearchInput and restructure Skills tab (#18876)
closes -
https://discord.com/channels/1130383047699738754/1480980523315626178
2026-03-26 05:57:41 +00:00
github-actions[bot] b87762b1c2 i18n - translations (#18987)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-26 07:02:45 +01:00
nitin 578d990b9c [AI] Match ai chat composer to figma (#18874)
https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=93653-368288&t=obTG32NRidXid4lN-0

closes
https://discord.com/channels/1130383047699738754/1480990726442582086

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2026-03-26 05:43:54 +00:00
Lucas Bordeau 441a9464d1 Fixed board no value column not fetching more (#18977)
Fixes https://github.com/twentyhq/twenty/issues/18148

## Problem
- Scrolling down in the "no value" kanban column never loads additional
records when it's the only column that needs pagination
- `useTriggerRecordBoardFetchMore` — `.filter(isDefined)` removes `null`
from the list of group values to fetch, and `null` is the value used by
"no value" columns, causing early exit
- `useTriggerRecordBoardFetchMore` — the inline `{ in: [...values] }`
filter cannot express a NULL match, so even without the early exit the
query would be wrong

## Fix
- "No value" column now triggers pagination correctly —
`useTriggerRecordBoardFetchMore` (removed `.filter(isDefined)` so `null`
values pass through)
- Query filter correctly matches NULL field values —
`useTriggerRecordBoardFetchMore` (replaced inline `{ in: [...] }` with
`computeRecordGroupOptionsFilter` which generates `{ is: 'NULL' }` for
null values and `{ in: [...] }` for non-null values)
2026-03-25 17:41:37 +00:00
Charles Bochet 72dd3af155 fix SVG icon sizing broken in Chrome 142 (#18974)
## Summary
- Chrome 142 rejects `var()` references in SVG `width`/`height`
attributes, causing icons to fall back to `100%` size
- Replaced `themeCssVariables.icon.size.*` /
`themeCssVariables.icon.stroke.*` (raw `var()` strings) with resolved
`theme.icon.size.*` / `theme.icon.stroke.*` (numeric values from
`ThemeContext`) when passed as icon component props
- Affects 3 navigation menu item components: `NavigationMenuItemFolder`,
`NavigationMenuItemLinkDisplay`, `LinkIconWithLinkOverlay`

## Test plan
- [ ] Verify navigation drawer folder chevron icons render at correct
size
- [ ] Verify navigation drawer link arrow icons render at correct size
- [ ] Verify link overlay icons render at correct size
- [ ] Test in Chrome 142+ to confirm the fix
- [ ] Test in Firefox/Safari to confirm no regression
2026-03-25 17:37:16 +00:00
Thomas Trompette 511d1bd7ab Fix SSE event stream errors when impersonating users with limited permissions (#18966)
Clear the SSE client before swapping auth tokens during impersonation,
preventing a userWorkspaceId mismatch between the existing event stream
(created under the admin's identity) and the new impersonation token.
Treat NOT_AUTHORIZED event stream errors as recoverable (destroy +
recreate), matching the existing behavior for
EVENT_STREAM_DOES_NOT_EXIST and EVENT_STREAM_ALREADY_EXISTS.
2026-03-25 17:35:54 +00:00
Thomas Trompette d47ddad4c5 Add multiselect option to form step (#18979)
<img width="415" height="462" alt="Capture d’écran 2026-03-25 à 18 10
06"
src="https://github.com/user-attachments/assets/e5157636-25ea-41b9-ae13-8f64a24cbd5e"
/>
<img width="415" height="462" alt="Capture d’écran 2026-03-25 à 18 10
12"
src="https://github.com/user-attachments/assets/5811848f-1ce6-4c09-9563-61f906968888"
/>
2026-03-25 17:35:50 +00:00
Lucas Bordeau 33a474c8e6 Add record table widget feature flag (#18960)
Introduce a new feature flag for the record table widget, enabling
conditional rendering and state management based on its status. Update
related components and tests accordingly.

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2026-03-25 16:47:20 +00:00
Charles Bochet a8dad6c882 Fix: default to allow-read when object permissions are not yet loaded (#18971)
## Summary

Fixes the merge-queue E2E failures that started after #18912 landed.

`filterReadableActiveObjectMetadataItems` (introduced in #18912) treats
a **missing** entry in the permissions map as "deny read". The previous
helper (`getObjectPermissionsFromMapByObjectMetadataId`) treated it as
**"allow read"** via a `?? { canReadObjectRecords: true, … }` fallback.

Because the permissions map is empty on initial render (before the API
round-trip completes), **every object was temporarily filtered out**.
This made `useDefaultHomePagePath` return the settings-profile path,
triggering a redirect race that broke Settings navigation in three E2E
tests:

- `signup_invite_email.spec.ts` — timed out waiting for
`getByRole('link', { name: 'Members' })`
- `create-kanban-view.spec.ts` — timed out waiting for
`getByRole('link', { name: 'Data model' })`
- `workflow-creation.spec.ts` — timed out waiting for sidebar Workflows
button

**Evidence from CI:**
- Last passing merge-queue run: base `13ea3ec75c` (before #18912)
- First failing merge-queue run: base `5f0d6553f6` (#18912)
- All individual PR CI runs continued to pass (they don't rebase on
latest main)
- Screenshot artifact shows the app stuck on the main page — the
Settings drawer never opened

## Fix

When an object has no entry in the permissions map, default to allowing
read (matching the old behavior). An empty map means permissions haven't
loaded yet, not that the user lacks access.

```diff
  objectMetadataItems.filter((objectMetadataItem) => {
+   if (!objectMetadataItem.isActive) {
+     return false;
+   }
+
    const objectPermissions =
      objectPermissionsByObjectMetadataId[objectMetadataItem.id];
-
-   return (
-     isDefined(objectPermissions) &&
-     objectPermissions.canReadObjectRecords &&
-     objectMetadataItem.isActive
-   );
+
+   if (!isDefined(objectPermissions)) {
+     return true;
+   }
+
+   return objectPermissions.canReadObjectRecords;
  });
```
2026-03-25 17:41:15 +01:00
github-actions[bot] 4fbe0a92ae i18n - translations (#18967)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-25 16:09:33 +01:00
Félix Malfait 5f0d6553f6 Add object type filter dropdown to side panel record searches (#18912)
## Summary

- Adds an object type filter dropdown to the side panel, allowing users
to scope record search results to a specific object type (e.g. People,
Companies, Opportunities)
- The filter appears as a funnel icon next to the search input in both
the global search (SearchRecords page) and the "Pick a record" sidebar
flow
- Replaces the AI sparkles button on the search records page with the
filter icon
- Uses colored icons matching the navigation menu style
(NavigationMenuItemStyleIcon + getStandardObjectIconColor)

## Test plan

- [ ] Open the side panel, type something to enter SearchRecords mode,
verify the filter icon appears
- [ ] Click the filter icon, verify the dropdown opens with "Object"
header, search input, "All Objects" and individual object types with
colored icons
- [ ] Select an object type, verify only records of that type appear in
search results
- [ ] Select "All Objects", verify all record types appear again
- [ ] Verify the filter icon turns blue when a filter is active
- [ ] In layout customization mode, add a new sidebar item > Record,
verify the filter dropdown also works there
- [ ] Close and reopen the side panel, verify the filter resets to "All
Objects"
- [ ] Verify the AI sparkles button no longer appears on the command
menu root page
- [ ] Verify the AI edit icon still appears on Ask AI pages


Made with [Cursor](https://cursor.com)
2026-03-25 16:02:24 +01:00
github-actions[bot] e25ea6069d i18n - translations (#18958)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-25 14:31:01 +01:00
github-actions[bot] f7ef41959b i18n - translations (#18956)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-25 14:23:30 +01:00
Thomas Trompette e1374e34a7 Fix object permission override (#18948)
Issue: https://www.loom.com/share/dd48cd509f614e51829f6a5b58d41b6b

Bug: Unsetting a revoked object permission keeps it revoked
When a role has a global permission enabled (e.g.
canReadAllObjectRecords: true) but an object-level override revokes it
(canReadObjectRecords: false), clicking to remove that override had no
effect — the permission stayed revoked after save.

Root cause:
Backend (object-permission.service.ts): The nullish coalescing operator
(??) was used to fall back to the current DB value when the input didn't
provide a value. Since ?? treats both null and undefined as nullish,
sending canReadObjectRecords: null (meaning "remove override") was
coalesced to the current value (false), silently discarding the reset.

Fix:
- Backend: Replaced ?? with explicit !== undefined checks, so null is
preserved as a meaningful value (meaning "no override / inherit from
global") while undefined (field not provided) still falls back to the
current value. This also fixes the "Reset all permissions" flow which
sends null for all permission fields.

Additional frontend fix: Changed !value to value === false so that only
an explicit false cascades revocation to write permissions. Setting null
(reset to inherit) now only affects the read permission itself.
2026-03-25 10:49:05 +00:00
Abdul Rahman 2bfd2f6b85 fix navigation menu item overflow issue (#18937)
Before
<img width="231" height="99" alt="Screenshot 2026-03-25 at 5 34 59 AM"
src="https://github.com/user-attachments/assets/47980a03-b2ec-47db-be16-db0a48b122dd"
/>


After
<img width="220" height="96" alt="Screenshot 2026-03-25 at 5 32 44 AM"
src="https://github.com/user-attachments/assets/5ad405c2-a697-407d-ac1e-450c4a0b9f62"
/>
2026-03-25 07:35:49 +00:00
github-actions[bot] 42d9e25bf2 i18n - translations (#18936)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-25 00:58:32 +01:00
Abdul Rahman b642d1b114 Animate the sidebar when opening/closing an element in the "opened" section (#18867)
https://github.com/user-attachments/assets/38f64750-7bca-4b10-8489-11d386a10298

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-24 23:44:01 +00:00
Abdul Rahman 766f956a15 Set system object icon colors to gray in New menu item flows (#18862)
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-25 00:17:23 +01:00
github-actions[bot] 981e83ecb1 i18n - translations (#18935)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-03-24 23:34:15 +01:00
Abdul Rahman 4a099ed097 Use side panel sub-pages for add-to-folder flow (#18872)
https://github.com/user-attachments/assets/a31b8523-2d0a-4a02-bcd9-5548c906b7cc

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-24 23:28:40 +01:00
Abdul Rahman 37640521d5 Batch create, update, and delete navigation menu items (#18882)
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-24 20:24:47 +00:00
Paul Rastoin 4ea2e32366 Refactor twenty client sdk provisioning for logic function and front-component (#18544)
## 1. The `twenty-client-sdk` Package (Source of Truth)

The monorepo package at `packages/twenty-client-sdk` ships with:
- A **pre-built metadata client** (static, generated from a fixed
schema)
- A **stub core client** that throws at runtime (`CoreApiClient was not
generated...`)
- Both ESM (`.mjs`) and CJS (`.cjs`) bundles in `dist/`
- A `package.json` with proper `exports` map for
`twenty-client-sdk/core`, `twenty-client-sdk/metadata`, and
`twenty-client-sdk/generate`

## 2. Generation & Upload (Server-Side, at Migration Time)

**When**: `WorkspaceMigrationRunnerService.run()` executes after a
metadata schema change.

**What happens in `SdkClientGenerationService.generateAndStore()`**:
1. Copies the stub `twenty-client-sdk` package from the server's assets
(resolved via `SDK_CLIENT_PACKAGE_DIRNAME` — from
`dist/assets/twenty-client-sdk/` in production, or from `node_modules`
in dev)
2. Filters out `node_modules/` and `src/` during copy — only
`package.json` + `dist/` are kept (like an npm publish)
3. Calls `replaceCoreClient()` which uses `@genql/cli` to introspect the
**application-scoped** GraphQL schema and generates a real
`CoreApiClient`, then compiles it to ESM+CJS and overwrites
`dist/core.mjs` and `dist/core.cjs`
4. Archives the **entire package** (with `package.json` + `dist/`) into
`twenty-client-sdk.zip`
5. Uploads the single archive to S3 under
`FileFolder.GeneratedSdkClient`
6. Sets `isSdkLayerStale = true` on the `ApplicationEntity` in the
database

## 3. Invalidation Signal

The `isSdkLayerStale` boolean column on `ApplicationEntity` is the
invalidation mechanism:
- **Set to `true`** by `generateAndStore()` after uploading a new client
archive
- **Checked** by both logic function drivers before execution — if
`true`, they rebuild their local layer
- **Set back to `false`** by `markSdkLayerFresh()` after the driver has
successfully consumed the new archive

Default is `false` so existing applications without a generated client
aren't affected.

## 4a. Logic Functions — Local Driver

**`ensureSdkLayer()`** is called before every execution:
1. Checks if the local SDK layer directory exists AND `isSdkLayerStale`
is `false` → early return
2. Otherwise, cleans the local layer directory
3. Calls `downloadAndExtractToPackage()` which streams the zip from S3
directly to disk and extracts the full package into
`<tmpdir>/sdk/<workspaceId>-<appId>/node_modules/twenty-client-sdk/`
4. Calls `markSdkLayerFresh()` to set `isSdkLayerStale = false`

**At execution time**, `assembleNodeModules()` symlinks everything from
the deps layer's `node_modules/` **except** `twenty-client-sdk`, which
is symlinked from the SDK layer instead. This ensures the logic
function's `import ... from 'twenty-client-sdk/core'` resolves to the
generated client.

## 4b. Logic Functions — Lambda Driver

**`ensureSdkLayer()`** is called during `build()`:
1. Checks if `isSdkLayerStale` is `false` and an existing Lambda layer
ARN exists → early return
2. Otherwise, deletes all existing layer versions for this SDK layer
name
3. Calls `downloadArchiveBuffer()` to get the raw zip from S3 (no disk
extraction)
4. Calls `reprefixZipEntries()` which streams the zip entries into a
**new zip** with the path prefix
`nodejs/node_modules/twenty-client-sdk/` — this is the Lambda layer
convention path. All done in memory, no disk round-trip
5. Publishes the re-prefixed zip as a new Lambda layer via
`publishLayer()`
6. Calls `markSdkLayerFresh()`

**At function creation**, the Lambda is created with **two layers**:
`[depsLayerArn, sdkLayerArn]`. The SDK layer is listed last so it
overwrites the stub `twenty-client-sdk` from the deps layer (later
layers take precedence in Lambda's `/opt` merge).

## 5. Front Components

Front components are built by `app:build` with `twenty-client-sdk/core`
and `twenty-client-sdk/metadata` as **esbuild externals**. The stored
`.mjs` in S3 has unresolved bare import specifiers like `import {
CoreApiClient } from 'twenty-client-sdk/core'`.

SDK import resolution is split between the **frontend host** (fetching &
caching SDK modules) and the **Web Worker** (rewriting imports):

**Server endpoints**:
- `GET /rest/front-components/:id` —
`FrontComponentService.getBuiltComponentStream()` returns the **raw
`.mjs`** directly from file storage. No bundling, no SDK injection.
- `GET /rest/sdk-client/:applicationId/:moduleName` —
`SdkClientController` reads a single file (e.g. `dist/core.mjs`) from
the generated SDK archive via
`SdkClientGenerationService.readFileFromArchive()` and serves it as
JavaScript.

**Frontend host** (`FrontComponentRenderer` in `twenty-front`):
1. Queries `FindOneFrontComponent` which returns `applicationId`,
`builtComponentChecksum`, `usesSdkClient`, and `applicationTokenPair`
2. If `usesSdkClient` is `true`, renders
`FrontComponentRendererWithSdkClient` which calls the
`useApplicationSdkClient` hook
3. `useApplicationSdkClient({ applicationId, accessToken })` checks the
Jotai atom family cache for existing blob URLs. On cache miss, fetches
both SDK modules from `GET /rest/sdk-client/:applicationId/core` and
`/metadata`, creates **blob URLs** for each, and stores them in the atom
family
4. Once the blob URLs are cached, passes them as `sdkClientUrls`
(already blob URLs, not server URLs) to `SharedFrontComponentRenderer` →
`FrontComponentWorkerEffect` → worker's `render()` call via
`HostToWorkerRenderContext`

**Worker** (`remote-worker.ts` in `twenty-sdk`):
1. Fetches the raw component `.mjs` source as text
2. If `sdkClientUrls` are provided and the source contains SDK import
specifiers (`twenty-client-sdk/core`, `twenty-client-sdk/metadata`),
**rewrites** the bare specifiers to the blob URLs received from the host
(e.g. `'twenty-client-sdk/core'` → `'blob:...'`)
3. Creates a blob URL for the rewritten source and `import()`s it
4. Revokes only the component blob URL after the module is loaded — the
SDK blob URLs are owned and managed by the host's Jotai cache

This approach eliminates server-side esbuild bundling on every request,
caches SDK modules per application in the frontend, and keeps the
worker's job to a simple string rewrite.

## Summary Diagram

```
app:build (SDK)
  └─ twenty-client-sdk stub (metadata=real, core=stub)
       │
       ▼
WorkspaceMigrationRunnerService.run()
  └─ SdkClientGenerationService.generateAndStore()
       ├─ Copy stub package (package.json + dist/)
       ├─ replaceCoreClient() → regenerate core.mjs/core.cjs
       ├─ Zip entire package → upload to S3
       └─ Set isSdkLayerStale = true
              │
     ┌────────┴────────────────────┐
     ▼                             ▼
Logic Functions               Front Components
     │                             │
     ├─ Local Driver               ├─ GET /rest/sdk-client/:appId/core
     │   └─ downloadAndExtract     │    → core.mjs from archive
     │      → symlink into         │
     │        node_modules         ├─ Host (useApplicationSdkClient)
     │                             │    ├─ Fetch SDK modules
     └─ Lambda Driver              │    ├─ Create blob URLs
         └─ downloadArchiveBuffer  │    └─ Cache in Jotai atom family
            → reprefixZipEntries   │
            → publish as Lambda    ├─ GET /rest/front-components/:id
              layer                │    → raw .mjs (no bundling)
                                   │
                                   └─ Worker (browser)
                                        ├─ Fetch component .mjs
                                        ├─ Rewrite imports → blob URLs
                                        └─ import() rewritten source
```

## Next PR
- Estimate perf improvement by implementing a redis caching for front
component client storage ( we don't even cache front comp initially )
- Implem frontent blob invalidation sse event from server

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2026-03-24 18:10:25 +00:00