6e00a122c671f4de1a8b88128a98edd043105a7b
12385 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6e00a122c6 |
fix(kanban): contain checkbox hover reveal within card bounds (#21100)
follow up to https://github.com/twentyhq/twenty/pull/20455 before - https://github.com/user-attachments/assets/e5a8a328-81ec-4dc4-8e54-1a54cf252135 after - https://github.com/user-attachments/assets/61cbb856-564c-487f-81e5-e27adc4a0d2d |
||
|
|
4dff30f676 |
Twenty server:Fix REST pagination issues (#20980)
Fixes #20109 The entry was repeating because in the database we store DateTime fields with microsecond precision (timestamptz), but when JS parses timestamptz into a Date object it only keeps millisecond precision. ### Example If previous cursor was: ``` { name: "Quick Lead", createdAt: "2026-05-21T15:33:00.708Z", } ``` The resulting query look something like: ``` ... WHERE ( "workflow"."name" > "Quick Lead" OR ( "workflow"."name" = "Quick Lead" AND "workflow"."createdAt" > "2026-05-21T15:33:00.708Z" ) OR ( "workflow"."name" = "Quick Lead" AND "workflow"."createdAt" = "2026-05-21T15:33:00.708Z" AND "workflow"."id" > "8b213cac-a68b-4ffe-817a-3ec994e9932d" ) ) ``` So, when comparing the 2nd condition the `"workflow"."createdAt" > "2026-05-21T15:33:00.708Z"` would always result to true because in db the data for createdAt is `2026-05-21 21:03:00.708 +0530` which will always be greater than `2026-05-21T15:33:00.708Z` The second condition `"workflow"."createdAt" > "2026-05-21T15:33:00.708Z"` always evaluates to true, because the value actually stored in the DB for createdAt is something like `2026-05-21 21:03:00.708264 +0530`, which is always greater than `2026-05-21T15:33:00.708Z` in the cursor. The row used to generate the cursor therefore reappears on the next page. ### My solution Truncate the column to milliseconds in the comparison so both sides have the same precision: `date_trunc('milliseconds', ${fieldReference})`. For the issue of nested sorting filters, when ordering by a composite field (e.g. `createdBy.name`), `encodeCursor` stored the entire composite object (`source`, `workspaceMemberId`, `name`, `context`). The where-condition builder later iterated those sub-keys and threw "Invalid cursor" because only name had an orderBy direction. P.S: Duplicate of #20867 because last fork got polluted. --------- Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> |
||
|
|
71c377484e |
fix(front): keep app variable cache in sync after update (#20861)
Updating an application variable in Workspace / Applications / <App> / Settings persisted server-side but the Apollo cache kept the old value. Switching tabs unmounted the settings tab, and remounting reseeded the input from the stale cache — only a full refresh showed the new value. Mutation now writes the new value into the ApplicationVariable entity via cache.modify, so FindOneApplication reflects the change immediately. Hook + table updated to pass the variable id through. Adds a hook test that pre-seeds the cache and asserts the cached value after the mutation. NOTE: saving the plain value in Apollo's cache might not be the best approach here --------- Co-authored-by: martmull <martmull@hotmail.fr> |
||
|
|
26906951b3 |
fix(twenty-sdk): minify front-component bundles & set NODE_ENV=production in deploy build (#20937)
## Summary
`twenty deploy` (and `twenty build`) currently ship front-component
`.mjs` bundles **unminified**, with `process.env.NODE_ENV` undefined at
build time. Two missing options in
`get-base-front-component-build-options.ts` — fix is two lines.
## Why this matters
Each front-component bundle includes React + ReactDOM + the design
system AOT (only `twenty-client-sdk/{core,metadata}` are listed in
`FRONT_COMPONENT_EXTERNAL_MODULES`). A trivial widget measures **~2 MB**
unminified. Because every widget mount spawns a fresh Web Worker that
re-fetches and re-parses the bundle (`FrontComponentWorkerEffect.tsx`),
that 2× size translates directly into 2× cold-start latency on **every
record-page navigation and every browser refresh**. On a CRM with even a
handful of custom widgets this dominates perceived UI latency.
## Why it bites every app, not one user
Reference apps in this repo
(`packages/twenty-apps/fixtures/{minimal,rich}-app`,
`community/github-connector`, `internal/twenty-for-twenty`) ship the
same way — verified by inspecting the released `twenty-sdk@2.5.0`
bundle. There's no CLI flag, env var, or config option to opt into a
production build. A search of issues/PRs for "minify", "bundle size",
"production build" surfaces nothing tracking this.
## Fix
Enable `minify: true` and `define: { 'process.env.NODE_ENV':
'"production"' }` in the base front-component build options. These flow
through `build-application.ts` (the orchestrator for `twenty deploy` and
`twenty build`).
**Watch mode (`twenty dev`) is intentionally untouched.**
`esbuild-watcher.ts` has its own configuration path that doesn't consume
`getBaseFrontComponentBuildOptions()`; it stays unminified so local
rebuilds remain fast and stack traces remain readable during
development.
## Measured impact
Two production extension apps using `twenty-sdk@2.5.0`:
| File | Before | After | Δ |
|---|---:|---:|---:|
| `oapps-deal-items` (single widget) | 2,114,953 B | 863,444 B |
**−59%** |
| `oapps-document-hub/documents-panel` | 2,120,341 B | 872,478 B |
**−59%** |
| `oapps-document-hub/hub-document-record` | 2,077,013 B | 852,544 B |
**−59%** |
| `oapps-document-hub/field-mapping-editor` | 1,168,941 B | 255,787 B |
**−78%** |
End-user effect: opening an Opportunity record with two custom-widget
tabs went from ~3-4s widget paint to under 1s on the same machine, same
browser, same record.
## Risk / scope
- **No behavior change.** Minification is a transparent transform;
`NODE_ENV=production` is the standard signal libraries already gate on.
No app code changes needed.
- **No effect on `twenty dev`** — separate code path.
- **No effect on logic functions** — they use their own build-options
object.
- One file touched.
## Test plan
- [ ] `yarn twenty deploy` on
`packages/twenty-apps/fixtures/minimal-app` → output `.mjs` is mangled
and `process.env.NODE_ENV` no longer appears literally inside the
bundle.
- [ ] `yarn twenty dev` on the same app → output `.mjs` remains
readable.
- [ ] Existing CI green.
Happy to add a feature-flag (`TWENTY_BUILD_MODE` env var or `twenty
deploy --no-minify` escape hatch) if maintainers prefer that over
unconditional minification.
---------
Co-authored-by: 8Maverik8 <8maverik8@users.noreply.github.com>
Co-authored-by: martmull <martmull@hotmail.fr>
|
||
|
|
51202d5a32 |
fix(front): scroll long content in rich text editor (#20319)
## Summary Fixes scroll behavior in the rich text editor. Long content was unbounded — the popup grew off-screen, the expand-to-side-panel button became unreachable, and the side panel itself didn't scroll either. Closes #20309 ## Changes - `RichTextFieldInput`: cap the popup at `min(60vh, 500px)` and wrap the editor in a scrollable region. Collapse button stays at the top via `align-items: flex-start`. - `RecordInlineCellEditMode`: add `shift()` middleware to keep the popup inside the viewport after `flip()` triggers (previously the popup could extend above the viewport top with the collapse button out of reach). - `SidePanelContainer` / `SidePanelRouter`: add `min-height: 0` to the flex-column chain so the existing `overflow-y: auto` on the content area can actually clip and scroll long children. The previous attempt in #20310 added the same `min-height: 0` plus a nested overflow wrapper inside the rich-text page; the nested wrapper turned out to be the reason scrolling didn't work. ## Test plan - [x] Open a record with a long `RICH_TEXT` field - [x] Click the field — popup opens bounded; long content scrolls inside - [x] Click the expand button (top-right) — side panel opens - [x] Side panel: long content scrolls vertically - [x] Popup near the bottom of the viewport flips upward and stays fully inside the viewport (collapse button remains visible) - [x] `lint:diff-with-main`, `typecheck`, prettier — green https://github.com/user-attachments/assets/827be881-aadd-49ed-9ddc-7566c00cf4be --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f4380f89a8 |
fix: SSE event stream reconnection after idle connection death (#21061)
The SSE event stream could silently die from network partitions, NAT
table flushes, browser tab throttling, or server restarts. When this
happened:
1. The `error` callback only called `captureException` — no reconnection
was triggered
2. The `complete` callback was `() => {}` — a cleanly terminated stream
left the client permanently broken
3. No mechanism existed to detect a silently dead connection where no
FIN/RST was received
## Summary
- **Fix `error`/`complete` callbacks**: The `graphql-sse` subscription's
`error` callback only reported to Sentry, and `complete` was a no-op.
Both now set `shouldDestroyEventStreamState = true` to trigger the
destroy-recreate lifecycle, ensuring detected transport failures and
clean stream terminations lead to automatic reconnection.
- **Add server-side keepalive**: The existing heartbeat timer now runs
every 30s (instead of 6min) and publishes empty events through the Redis
pub/sub channel in addition to refreshing the Redis TTL (throttled to
~6min). Unlike GraphQL Yoga's opaque SSE comment pings, these are real
subscription events that flow through the client's `next`/`message`
handlers.
- **Add client-side keepalive monitor (`SSEKeepAliveEffect`)**: Tracks
the timestamp of the last received event. If no event arrives within 90
seconds (3x the keepalive interval), it clears query listeners and
triggers a stream destroy-recreate cycle.
## Test plan
- [x] Start the app, verify SSE events flow normally (workflow runs
update in real-time)
- [x] Leave the app idle for >90 seconds, then trigger a workflow run —
verify the stream auto-reconnects and events are delivered
- [x] Kill the server, restart it, verify the frontend recovers its
event stream
- [x] Verify keepalive events (empty
`objectRecordEventsWithQueryIds`/`metadataEvents`) appear in browser
network tab every ~30s
- [x] Verify no regressions in SSE-dependent features (record updates,
metadata changes, workflow run visualization)
|
||
|
|
e430e4ea0a |
fix(ai): route xAI search through Responses API as native tools (#21037)
xAI deprecated Live Search, so the `searchParameters` provider option now returns 410. This routes all xAI models through the Responses API and binds web/X search as native agent tools, matching how Anthropic/OpenAI expose search. - xAI provider now uses `provider.responses()` — its `webSearch()`/`xSearch()` tools only run against the Responses endpoint, not chat completions - web/X search migrated from the `provider-option` variant to `sdk-tool` (`web_search`/`x_search`); deleted the dead `searchParameters` path, the `provider-option` variant, and `providerOptions` on `NativeModelBinding` - dropped a dead `rolePermissionConfig` param on `getAgentRoleId`, left over from #20331 --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <FelixMalfait@users.noreply.github.com> |
||
|
|
b027e4bdb1 |
[Website] i18n module, page-local sections, translatable copy (#21082)
**i18n** — collapsed the ~22 scattered i18n files into a single module and turned on Spanish alongside French. **Sections** — dropped the old compound pattern (`Section.Root`, `Section.Heading`, …). Reusable layout shells moved to `src/templates/`, atomic bits stay in `design-system/`, and each page now owns its copy in local `_components` blocks instead of pulling it out of shared sections. Data files hold arrays only, no prose. **Copy → `<Trans>`** — A lot of headings were split across several `<HeadingPart>`s just for font styling, which meant each piece was a separate translation string. A translator got "Build your Enterprise CRM" and "at AI Speed" as two unrelated strings and had no way to reorder them for their language. Those are now single `<Trans>` units with placeholders. Same idea for the old `\n` + `white-space: pre-line` line-break trick: replaced with a small `ResponsiveLineBreak` element so the break is doesn't quietly rot, and did a dead-code pass. The de-fragmentation changes the message IDs, so around 60 strings will fall back to English in fr/es until Crowdin re-syncs. |
||
|
|
fc90b4ba8b |
i18n - docs translations (#21064)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
88b77cb699 |
feat(server): opt-in FRONT_AUTO_BASE_URL for hostname-relative API URL (#20504)
## Problem
`generateFrontConfig()` writes `window._env_.REACT_APP_SERVER_BASE_URL =
process.env.SERVER_URL` unconditionally. The frontend then pins to that
absolute URL. For self-hosted deployments reachable from multiple
hostnames (Tailscale IP, LAN IP, internal DNS, SSH tunnel to localhost,
public DNS), only the one matching `SERVER_URL` works — others hit CORS
errors or unreachable hosts because the frontend tries to call the API
at the configured URL, not the one the user came in via.
The frontend already supports the right fallback:
`packages/twenty-front/src/config/index.ts:20-21` reads
`window._env_?.REACT_APP_SERVER_BASE_URL` and falls back to
`getDefaultUrl()` (which uses `window.location`) when the env var is
absent. But the server-side `generateFrontConfig` always populates
`_env_`, so the fallback never runs.
## Fix
One file: `packages/twenty-server/src/utils/generate-front-config.ts`.
Add a `FRONT_AUTO_BASE_URL=true` opt-in (also triggered when
`SERVER_URL` is unset entirely). When the toggle is on, inject
`window._env_ = {}` so the frontend's existing `getDefaultUrl()`
fallback resolves the origin from `window.location` at runtime.
## Backwards compatibility
When `SERVER_URL` is set AND `FRONT_AUTO_BASE_URL` is unset (or anything
other than `'true'`): unchanged — `REACT_APP_SERVER_BASE_URL:
process.env.SERVER_URL` is injected exactly as before.
The toggle is strictly additive. Existing single-hostname deployments
are not affected.
## Use case
Self-hosted Twenty reachable via:
- `http://100.115.12.29` over Tailscale
- `http://localhost:4440` over SSH tunnel
- `http://twenty.internal` over LAN DNS
- `http://crm.example.com` public
With `FRONT_AUTO_BASE_URL=true`, all four paths work without rebuilds or
per-hostname server processes.
## Test plan
- [ ] `SERVER_URL=http://x.com` (toggle unset) → `<script>window._env_ =
{"REACT_APP_SERVER_BASE_URL":"http://x.com"};</script>` (unchanged from
main)
- [ ] `SERVER_URL` unset → `<script>window._env_ = {};</script>` (new
fallback path)
- [ ] `SERVER_URL=http://x.com FRONT_AUTO_BASE_URL=true` →
`<script>window._env_ = {};</script>` (toggle wins)
- [ ] `FRONT_AUTO_BASE_URL=false SERVER_URL=http://x.com` → unchanged
(only `'true'` triggers the toggle)
---------
Co-authored-by: martmull <martmull@hotmail.fr>
|
||
|
|
0ed2e9d82d |
Docs: clarify numberOfSelectedRecords usage for RECORD_SELECTION items (#21059)
Add a note to the command menu items docs explaining that RECORD_SELECTION already guarantees a non-empty selection, so numberOfSelectedRecords > 0 is redundant in conditionalAvailabilityExpression. |
||
|
|
643cfe9b13 |
i18n - docs translations (#21062)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
bc1b7f6fdf |
fix: resolve workflow form step auto-open race condition (#21053)
## Summary - Fix intermittent failure where the Quick Lead workflow form step did not auto-open - Root cause: race conditions between SSE events, Apollo cache writes, and the `runWorkflowVersion` mutation timing - Add generic monotonicity guard in the SSE handler that drops stale updates for all records (not just WorkflowRun) ## Changes - **`useTriggerOptimisticEffectFromSseUpdateEvents.ts`**: Compare incoming `updatedAt` with cached record before writing — skip if stale. Moved `upsertRecordsInStore` after the guard so neither Apollo cache nor Jotai store receive stale data. - **`useRunWorkflowVersion.tsx`**: Await mutation before opening side panel; register SSE listener eagerly before mutation - **`useWorkflowRun.ts`**: Simplified back to plain `useFindOneRecord` + schema parse (no extra state needed) - **`generateWorkflowRunDiagram.ts`**: `shouldOpenStep` matches both PENDING and RUNNING for form steps (backend RUNNING means "waiting for user input") - **`WorkflowRunVisualizerEffect.tsx`**: Pass `runStatus` directly without status mapping - **`WorkflowRunStepNodeDetail.tsx`**: Form is interactive when step is PENDING or RUNNING - **Deleted `latestWorkflowRunFamilyState.ts`**: No longer needed — the generic SSE guard replaces it ## Test plan - [x] Hard refresh, run Quick Lead workflow 10+ times — form should always auto-open - [x] Complete the form and verify all subsequent steps execute without getting stuck - [x] Verify the workflow diagram is always visible (never disappears) - [x] Verify other record types still update correctly via SSE (e.g. edit a person in another tab) |
||
|
|
57118a868f |
Docs update: Calling a logic function from a front component (#21057)
Documents how a headless front component calls a server-side logic function over HTTP via the /s/ route, so AI agents have a clear reference for implementing this pattern. |
||
|
|
93f848fd2f |
i18n - translations (#21055)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
667cb95730 |
fix(sso): accept HTTP-POST binding and surface descriptive parser errors (#21051)
Fixes https://github.com/twentyhq/twenty/issues/21044 ## Summary - Closes [#490](https://github.com/twentyhq/private-issues/issues/490) — JumpCloud customers (and anyone else whose IdP only advertises `HTTP-POST` for `SingleSignOnService`) could not upload their SAML metadata; the parser silently rejected them with a generic `Invalid file` toast. - The SAML IdP metadata parser now falls back to `HTTP-POST` when `HTTP-Redirect` is not advertised. Both are valid SAML 2.0 bindings. - The parser now returns a descriptive `reason` string (Zod issues + custom errors) instead of an opaque `error: unknown`, and the upload snack bar surfaces it so the customer can self-diagnose (e.g. `entityID: entityID is not a valid URL` if they forgot to fill in their IdP Entity ID). - Added unit tests for HTTP-POST-only metadata, HTTP-Redirect preference, and each descriptive-error path. ## Test plan - [x] `npx jest parseSAMLMetadataFromXMLFile --config=packages/twenty-front/jest.config.mjs` — 8/8 pass - [x] `npx oxlint -c packages/twenty-front/.oxlintrc.json` on changed files — clean - [x] `npx oxfmt --check` on changed files — clean - [ ] Manual: upload the customer's JumpCloud metadata (HTTP-POST only, placeholder `entityID`) and confirm the error now says `Invalid file: entityID: entityID is not a valid URL` instead of `Invalid file` - [ ] Manual: upload metadata with a real `entityID` and HTTP-POST-only binding, confirm the form populates correctly |
||
|
|
4e5d47168c |
2439 improve command menu item display in right panel (#21020)
## Before <img width="1512" height="389" alt="image" src="https://github.com/user-attachments/assets/33274356-fb99-4a02-baa7-c324e6d151c6" /> ## After <img width="1512" height="357" alt="image" src="https://github.com/user-attachments/assets/c0affb71-e920-4d64-b2f0-1bed53209ea5" /> |
||
|
|
10c0bed462 |
fix: harden email-group SES provisioning, cleanup, and inbound replay (#21046)
## Changes **Provisioning idempotent** (`aws-ses-register-domain.service.ts`) - Each SES create call (`CreateConfigurationSet`, event destination, contact list, tenant association) now swallow `AlreadyExistsException` via `.send().catch()`. - Retry after partial failure re-run every step, no blow up on "already exists". Before: one existing resource kill whole provision. **Workspace delete clean up cloud** (`workspace.service.ts`, `emailing-domain-workspace-cleanup.job.ts`, `emailing-domain.service.ts`) - On workspace delete, fetch domain list first, pass domains to cleanup job. - Cleanup now loop `driver.cleanupDomain(domain)` per domain + `deprovisionWorkspace`. Tear down SES identity/tenant/config-set, not just delete DB rows. - Before: DB rows gone, SES resources orphaned forever. Now: cloud match DB. **Inbound replay dedupe** (`ses-inbound-mail-handler.service.ts`) - Use `snsMessageId` as job id. SNS deliver same message twice → second is no-op. No duplicate inbound email import. |
||
|
|
08b1c5738d |
i18n - translations (#21050)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
c2df39405c |
Fix admin pannel server variable config tab (#21017)
## Before <img width="1046" height="490" alt="image" src="https://github.com/user-attachments/assets/450557de-fcf5-4b51-afdb-36c0c36e43d8" /> ## After <img width="1040" height="414" alt="image" src="https://github.com/user-attachments/assets/4a5fe2ab-85d6-4431-9397-6f81ae24055d" /> |
||
|
|
3e2c50c6cf |
i18n - translations (#21048)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
f67db8d8b2 |
i18n - translations (#21047)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
3cd2458fdf |
i18n - website translations (#21045)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
13f09d8946 |
[Dashboards] Remove gauge chart types and code (#20410)
Follow-up cleanup to #20172. |
||
|
|
961745e9ba |
Fix latency spike on application lookup (#21042)
Fixes https://discord.com/channels/1130383047699738754/1509645089062781058 Caching application entities to improve authentication latency |
||
|
|
41832c8d82 |
Fix workflow creation on view filtered by status (#21027)
Creating a workflow on a table with with a filter on status (eg: status is "active") failed because it added the status to createOneWorkflow (in order to have the record belonging to the view) - while createOneWorkflow throwed a 400 exception when attempting to create a workflow with a status (does not correpsond to a valid behaviour). Silently stripping status rom create workflow endpoints. |
||
|
|
3041ed3b6e |
chore: sync AI model catalog from models.dev (#21041)
Automated daily sync of `ai-providers.json` from [models.dev](https://models.dev). This PR updates pricing, context windows, and model availability based on the latest data. New models meeting inclusion criteria (tool calling, pricing data, context limits) are added automatically. Deprecated models are detected based on cost-efficiency within the same model family. **Please review before merging** — verify no critical models were incorrectly deprecated. Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com> |
||
|
|
6d550611d2 |
chore(deps): bump typescript from 5.9.2 to 5.9.3 (#20991)
Bumps [typescript](https://github.com/microsoft/TypeScript) from 5.9.2 to 5.9.3. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/microsoft/TypeScript/releases">typescript's releases</a>.</em></p> <blockquote> <h2>TypeScript 5.9.3</h2> <p>Note: this tag was recreated to point at the correct commit. The npm package contained the correct content.</p> <p>For release notes, check out the <a href="https://devblogs.microsoft.com/typescript/announcing-typescript-5-9/">release announcement</a></p> <ul> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.0%22+is%3Aclosed+">fixed issues query for Typescript 5.9.0 (Beta)</a>.</li> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.1%22+is%3Aclosed+">fixed issues query for Typescript 5.9.1 (RC)</a>.</li> <li><em>No specific changes for TypeScript 5.9.2 (Stable)</em></li> <li><a href="https://github.com/Microsoft/TypeScript/issues?utf8=%E2%9C%93&q=milestone%3A%22TypeScript+5.9.3%22+is%3Aclosed+">fixed issues query for Typescript 5.9.3 (Stable)</a>.</li> </ul> <p>Downloads are available on:</p> <ul> <li><a href="https://www.npmjs.com/package/typescript">npm</a></li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/microsoft/TypeScript/commit/c63de15a992d37f0d6cec03ac7631872838602cb"><code>c63de15</code></a> Bump version to 5.9.3 and LKG</li> <li><a href="https://github.com/microsoft/TypeScript/commit/8428ca4cc8a7ecc9ac18dd0258016228814f5eaf"><code>8428ca4</code></a> 🤖 Pick PR <a href="https://redirect.github.com/microsoft/TypeScript/issues/62438">#62438</a> (Fix incorrectly ignored dts file fr...) into release-5.9 (#...</li> <li><a href="https://github.com/microsoft/TypeScript/commit/a131cac6831aa6532ea963d0cb3131b957cad980"><code>a131cac</code></a> 🤖 Pick PR <a href="https://redirect.github.com/microsoft/TypeScript/issues/62351">#62351</a> (Add missing Float16Array constructo...) into release-5.9 (#...</li> <li><a href="https://github.com/microsoft/TypeScript/commit/04243333584a5bfaeb3434c0982c6280fe87b8d5"><code>0424333</code></a> 🤖 Pick PR <a href="https://redirect.github.com/microsoft/TypeScript/issues/62423">#62423</a> (Revert PR 61928) into release-5.9 (<a href="https://redirect.github.com/microsoft/TypeScript/issues/62425">#62425</a>)</li> <li><a href="https://github.com/microsoft/TypeScript/commit/bdb641a4347af822916fb8cdb9894c9c2d2421dd"><code>bdb641a</code></a> 🤖 Pick PR <a href="https://redirect.github.com/microsoft/TypeScript/issues/62311">#62311</a> (Fix parenthesizer rules for manuall...) into release-5.9 (#...</li> <li><a href="https://github.com/microsoft/TypeScript/commit/0d9b9b92e2aca2f75c979a801abbc21bff473748"><code>0d9b9b9</code></a> 🤖 Pick PR <a href="https://redirect.github.com/microsoft/TypeScript/issues/61978">#61978</a> (Restructure CI to prepare for requi...) into release-5.9 (#...</li> <li><a href="https://github.com/microsoft/TypeScript/commit/2dce0c58af51cf9a9068365dc2f756c61b82b597"><code>2dce0c5</code></a> Intentionally regress one buggy declaration output to an older version (<a href="https://redirect.github.com/microsoft/TypeScript/issues/62163">#62163</a>)</li> <li>See full diff in <a href="https://github.com/microsoft/TypeScript/compare/v5.9.2...v5.9.3">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Weiko <corentin@twenty.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <FelixMalfait@users.noreply.github.com> |
||
|
|
3c97d9648b |
fix(address): show saved address in record detail when street1 is null (#21033)
## Fixes #20084 ### Problem A saved address is visible in the **table view** but shows **"Empty"** in the **record detail page** when `addressStreet1` is `null`. This reproduces with the default seed data out of the box — e.g. **Google** (city "Mountain View", no street), **Microsoft** (Redmond), **Meta** (Menlo Park) — which is why several users reported hitting it immediately. ### Root cause The frontend zod schema required `addressStreet1` to be a **non-null** string: ```ts // isFieldAddressValue.ts export const addressSchema = z.object({ addressStreet1: z.string(), // ← required non-null addressStreet2: z.string().nullable(), ... }); ``` …but the backend composite type marks it `isRequired: false` (`address.composite-type.ts`), and the DB column is nullable. So the API legitimately returns `addressStreet1: null` when only other subfields are filled. The two views diverge on how they render: - **Record detail** gates the value behind `useIsFieldEmpty()` → `isFieldValueEmpty()`, which for addresses calls `isFieldAddressValue()`. With `addressStreet1: null` the `safeParse` **fails**, so `isFieldValueEmpty` returns `true` and the `"Empty"` placeholder is shown (`RecordInlineCellDisplayMode`). - **Table view** (`RecordTableCellDisplayMode`) renders `AddressFieldDisplay` directly with **no** empty check, so the address stays visible. This was a latent mismatch since the address guard was introduced. ### Fix Make `addressStreet1` nullable to match the backend and the other subfields: - `addressSchema` → `addressStreet1: z.string().nullable()` - `FieldAddressValue.addressStreet1` → `string | null` - `FieldAddressDraftValue.addressStreet1` → `string | null` (keeps the input/draft type consistent; the text input already renders `?? ''`) The change is strictly more permissive — persisting and the settings default-value form still accept string values; they now also accept `null`. ### Tests - `isFieldAddressValue.test.ts` — guard returns `true` for `addressStreet1: null` with other subfields filled. - `isFieldValueEmpty.test.ts` — new address coverage: empty address is empty; **`street1: null` + city filled is NOT empty**; normal address is not empty. (Added an `addressFieldDefinition` mock.) Both new assertions were confirmed to **fail before the fix** and pass after. ### Verification - `npx jest isFieldValueEmpty isFieldAddressValue normalize-address-field-value-for-persist` → 17 passed - `npx nx typecheck twenty-front` → pass - `npx nx lint:diff-with-main twenty-front` → 0 warnings, 0 errors |
||
|
|
3afdabb93e |
fix(dashboards): isolate pie chart slice labels per widget (#21034)
## Summary Fixes [#21014](https://github.com/twentyhq/twenty/issues/21014). When two pie chart widgets shared the same group-by field (and therefore the same slice ids) but used different aggregation operators (e.g. `count` vs `sum`), the arc-link labels would mirror between the two charts — both ending up showing either the count or the sum values, depending on render order. Center metrics stayed correct. **Root cause.** Nivo's `ArcLinkLabelsLayer` and `ArcsLayer` (from `@nivo/arcs`) wire `react-spring`'s `useTransition` with `keys: e => e.id`. When two `<ResponsivePie>` instances render with overlapping ids, the transitioned data bleeds across charts. The center metric is unaffected because it's computed by a separate hook (`usePieChartCenterMetricData`). **Fix.** Namespace the Nivo-computed slice id per widget by passing an `id` accessor to `<ResponsivePie>`: ```tsx id={(datum) => `${id}:${String(datum.id)}`} ``` Lookups inside the widget switch to `datum.data.id` (the original, un-namespaced id stored on the raw datum), so value/percentage formatting, the custom tooltip, and the legend hover-dim behavior all keep working. Touched files: - `GraphWidgetPieChart.tsx` — add `id` accessor - `CustomArcsLayer.tsx` — compare legend highlight against `datum.data.id` - `getPieChartFormattedValue.ts`, `getPieChartTooltipData.ts` — match on `datum.data.id` - Tests for both utils get a regression case covering the namespaced computed id ## Test plan - [ ] `npx jest getPieChartFormattedValue` ✅ - [ ] `npx jest getPieChartTooltipData` ✅ - [ ] `npx tsc --noEmit` ✅ - [ ] Manual: dashboard with two pies on the same group-by field, one `count` and one `sum`, "Display data label" on for both — confirm each chart shows its own metric on the slices, and the central total is unchanged. - [ ] Manual: hover a legend item — the matching slice in that chart stays solid while the others dim, and the sibling chart is not affected. - [ ] Manual: clicking a slice still drills into the correctly filtered view. |
||
|
|
25b0e0d091 |
fix: correct typo occurence -> occurrence in metadata-event-emitter.ts (#21036)
## Summary Fixes a spelling typo in `packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-emitter.ts`: - Variable name `occurence` → `occurrence` (4 references on lines 101, 103, 114, 115) ## Changes - `packages/twenty-server/src/engine/subscriptions/metadata-event/metadata-event-emitter.ts` — rename misspelled variable Co-authored-by: james <li@jamesdeMacBook-Pro.local> |
||
|
|
a43e5c3fb3 |
i18n - translations (#21032)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
996cdaf3ff |
refactor(agents): split tool resolution into native and action rails (#20331)
## Summary
Splits AI agent tool resolution into two independent rails:
- **Native tools** — capabilities baked into the model SDK
(Anthropic/OpenAI `web_search`, xAI `web`/`x` provider options). Bound
by `NativeToolBinderService`, controlled by per-agent
`modelConfiguration` toggles. Opaque to Twenty — executed on the model
provider's servers.
- **Action tools** — registry-scoped tools from `ToolRegistryService`
(code interpreter, send email, record CRUD, etc.). Permission-gated via
the agent's role. Executed on Twenty's server.
Both rails merge into a single `ToolSet` at call time. When both
surfaces expose a search tool the model picks at runtime — coexistence
is intentional (relevant once Exa returns as an action, see below).
## Notable changes worth calling out
**Contract change: `AgentAsyncExecutorService.executeAgent` no longer
accepts `rolePermissionConfig`.** Workflow agents now scope exclusively
by the agent's own permission-tab role (`unionOf: [agentRoleId]`). The
previous role-merging path (caller role intersected with agent role) is
removed. No agent role → no registry tools (fail-closed by design).
**`NativeToolBinderService` relocated** from
`core-modules/tool-provider/native/` →
`metadata-modules/ai/ai-models/services/`. The binder needs SDK-package
knowledge, which lives in `ai-models`. Old location created a backwards
module dependency.
**`NATIVE_MODEL_TOOLS_BY_SDK_PACKAGE` is exhaustive over
`AiSdkPackage`** (`Record<>`, not `Partial<Record<>>`). Adding a new SDK
without thinking about native tools now fails the build. SDKs without
native tools (Bedrock, Google, Mistral, Azure, OpenAI-compatible) get
explicit `{}` entries.
**Discriminated union `kind: 'sdk-tool' | 'provider-option'`** lets one
registry describe both function tools (Anthropic/OpenAI) and runtime
sources (xAI). Follows the local `tool-provider` convention from #19321.
## Deferred to follow-ups
- **Exa web search is dropped from this PR** (along with its
`WEB_SEARCH_TOOL` permission flag and the Exa-specific gating). Exa
comes back as an **action/app tool** once apps can define permission
flags through the SDK — ongoing work in #20481.
- **xAI native search currently errors.** xAI deprecated its Live Search
API (the `web`/`x` provider-option sources this rail maps to), so xAI
returns `410` when native search is actually exercised. The code path
itself is clear — it's only hit if you test xAI native tools. Fixed
separately alongside the broader xAI model fixes.
## Conscious non-decisions
- **No "twenty-native" category.** `native` is reserved for
model/provider SDK features; everything Twenty-owned is just a
tool/action.
- **Coexistence over precedence.** No rule forcing an action search tool
to override native search (or vice-versa) — when both exist, it's the
user's choice in workflow agents and the model's choice in chat.
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
|
||
|
|
1d84695fb0 |
Fix: focus stack overwritten when auto-opening title cell on new record (#21029)
Fixes https://github.com/twentyhq/twenty/issues/20894 In `PageChangeEffect` `resetFocusStackToFocusItem` ran right after `openNewRecordTitleCell`, wiping the title cell entry. Typing in the auto-opened breadcrumb input (e.g. new workflow) triggered global shortcuts and ignored Enter / Escape / Tab. Reordered so the page reset runs first, then the title cell push lands on top. ## Before https://github.com/user-attachments/assets/d3c0c266-a493-46b8-b99b-32f4381b8664 ## After https://github.com/user-attachments/assets/3a886386-f438-46d2-a6ea-9ee6908d6df2 |
||
|
|
64e0b76d00 |
chore(deps): bump js-cookie from 3.0.5 to 3.0.7 (#20992)
Bumps [js-cookie](https://github.com/js-cookie/js-cookie) from 3.0.5 to 3.0.7. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/js-cookie/js-cookie/releases">js-cookie's releases</a>.</em></p> <blockquote> <h2>v3.0.7</h2> <ul> <li>Prevent cookie attribute injection: CVE-2026-46625 (eb3c40e)</li> <li>Add <code>Partitioned</code> attribute to readme (b994768)</li> <li>Publish to npm registry via trusted publisher exclusively (4dc71be)</li> <li>Ensure consistent behaviour for <code>get('name')</code> + <code>get()</code> (1953d30)</li> </ul> </blockquote> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/js-cookie/js-cookie/commit/17bacba0171dd022728d8fdeba3203c60791bf58"><code>17bacba</code></a> Craft v3.0.7 release</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/adb823cb7e95ead47f3af4d4951e589acbde2077"><code>adb823c</code></a> Fix release workflow halting at <code>git tag</code></li> <li><a href="https://github.com/js-cookie/js-cookie/commit/5f9e759b07d2752e8407a3a43fb5f879bf384c5e"><code>5f9e759</code></a> May remove Git user config from release workflow</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/6ac921184c7b3b7d9431c88707f56521acd72ab4"><code>6ac9211</code></a> Fix release workflow not able to push commit + tag</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/2278bc55e1804c4c2d9bd2110a9b449949a52751"><code>2278bc5</code></a> Fix missing package version bump</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/eb3c40e89731e99b8970faaf35ddad249c6c0020"><code>eb3c40e</code></a> Prevent cookie attribute injection</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/f6f157f430d707d2ffd0c9c9138227a6cea564e5"><code>f6f157f</code></a> Bump globals from 17.5.0 to 17.6.0</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/f409d022da50a0c6fa8724f087fbc50fab9a9533"><code>f409d02</code></a> Bump eslint from 10.2.0 to 10.3.0</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/a686883c03a754c04546cfc1653911a70a640b40"><code>a686883</code></a> Bump protobufjs in the npm_and_yarn group across 1 directory</li> <li><a href="https://github.com/js-cookie/js-cookie/commit/c6112d2d4f2881a12aaf89d9e2996ef6870eb6d0"><code>c6112d2</code></a> Bump <code>@protobufjs/utf8</code> in the npm_and_yarn group across 1 directory</li> <li>Additional commits viewable in <a href="https://github.com/js-cookie/js-cookie/compare/v3.0.5...v3.0.7">compare view</a></li> </ul> </details> <details> <summary>Maintainer changes</summary> <p>This version was pushed to npm by <a href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new releaser for js-cookie since your current version.</p> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Weiko <corentin@twenty.com> |
||
|
|
8a74ea8829 |
fix(contact-creation): enrich missing names on auto-created contacts (#21018)
## Summary
Three related fixes to the auto-creation of People records from calendar
events and email messages, all centred on the data-quality problem of
contacts being created with missing or malformed names.
### 1. Enrich names on existing contacts (commit 1)
Previously: when an email or calendar import matched an existing Person
by email, the existing record was left untouched — even if the new
source carried a better name.
This is the root cause of contacts like `"Félix"` (no last name)
sticking around forever: the `To:`/`Cc:` headers of outbound emails
rarely include a display name, and Google Calendar only returns
`displayName` for attendees already in the organizer's address book. So
the first sighting often creates a Person as `{firstName: "felix",
lastName: ""}`, and a later inbound `From: "Félix Malfait"
<felix@twenty.com>` — which would have produced the right name — gets
silently dropped because the Person already exists.
The new `computePeopleToEnrichNames` bucket and
`CreatePersonService.enrichPeopleNames` method fill in missing
`firstName`/`lastName` fields from the new parsed name, with
conservative rules:
- Only enrich when the existing Person's `createdBy.source` is
`CALENDAR` or `EMAIL` — `MANUAL`, `IMPORT`, `API`, `WORKFLOW`, etc. are
never touched.
- Only fill empty fields. Non-empty `firstName`/`lastName` are never
overwritten.
- Soft-deleted contacts continue to be handled by the existing restore
path.
### 2. Handle multi-comma "Last, First, Suffix" display names (commit 2)
The comma-inverted swap in the parser previously required *exactly* one
comma. Names like `"Smith, Jane, Jr."`, `"O'Brien, Mary, MD"` or `"Doe,
John, Patrick"` fell through to the space-split fallback, which stored
the comma in `firstName` (e.g. `"Smith,"`) and produced garbled records
(the avatar shows a single "B" and the name reads `"Barbey, Julien"`
because the entire string lives in `firstName`).
The regex now splits on the first comma and treats the remainder as the
first name, collapsing any further commas to spaces. Single-comma
behaviour is unchanged.
### 3. Perf: skip the parser when an existing record is already
populated (commit 3)
`computePeopleToEnrichNames` runs on every cron-driven email/calendar
import batch. The first version called the display-name parser for every
matched existing person, even when both `firstName` and `lastName` were
already set — i.e. the steady-state case after the initial enrichment
pass.
Reordered so the cheap "both fields populated" check short-circuits
before any parsing happens. Same behaviour, fewer parser calls on the
hot path.
## Test plan
- [x] 8 new unit tests for the enrichment bucket: empty `lastName`
enrichment, both `EMAIL` and `CALENDAR` sources, non-overwrite of
non-empty fields, skip on `MANUAL`/`IMPORT`, skip when the new source
also has no last name, skip for soft-deleted, fill `firstName` while
preserving `lastName`, handle null `name` field
- [x] 5 new parser tests for multi-comma forms: `"Last, First, Suffix"`,
credential suffixes (`MD`), three-token forms, whitespace around inner
commas, `:GROUP` tag interaction
- [x] 1 new parser test on the single-comma path covering multi-word
first names (`"Smith, Mary Jane"`)
- [x] All 15 existing parser tests still pass
- [x] All 116 tests in `contact-creation-manager` pass
- [x] `npx nx typecheck twenty-server`
- [x] `npx oxlint --type-aware` + `npx oxfmt --check` on changed files
- [ ] Manual: trigger a fresh contact creation from an outbound email
with no display name, then a subsequent inbound email from the same
address with a full display name, and confirm the Person's last name
gets populated
|
||
|
|
f4ead89956 |
refactor(twenty-orm): migrate 23 grandfathered entities to WorkspaceScopedRepository (#20987)
## Summary Follow-up to #20953. Migrates 23 of the 30 entities that were left in `WORKSPACE_SCOPED_EXEMPTIONS` last time, so the lint rule's workspaceId-enforcement default now covers most of the core/metadata schema. ### Migrated (23 entities, 88 files, 22 commits) | Family | Entities | |---|---| | Trivial caches | `NavigationMenuItem`, `Skill`, `DataSource`, `Webhook`, `CommandMenuItem`, `IndexMetadata` | | Views | `View`, `ViewField`, `ViewFieldGroup`, `ViewFilter`, `ViewFilterGroup`, `ViewGroup`, `ViewSort` | | Layouts | `PageLayout`, `PageLayoutTab`, `PageLayoutWidget` | | Roles & permissions | `Role`, `RoleTarget`, `PermissionFlag`, `ObjectPermission`, `FieldPermission`, `RowLevelPermissionPredicate`, `RowLevelPermissionPredicateGroup` | For each entity: swap `@InjectRepository(X)` → `@InjectWorkspaceScopedRepository(X)` (and the field type → `WorkspaceScopedRepository<X>`); rewrite every call site to pass `workspaceId` as the first arg (stripped from `where`/criteria — the wrapper throws if you include it now); register `provideWorkspaceScopedRepository(X)` in every owning NestJS module; update affected spec providers to `getWorkspaceScopedRepositoryToken(X)`. ### Rule update - `ApplicationRegistrationVariableEntity` was misclassified — moved to `STRUCTURAL_EXEMPTIONS` (no `workspaceId` column; it's keyed on `applicationRegistrationId` at the instance level). - 22 of the 23 migrated entities removed from `WORKSPACE_SCOPED_EXEMPTIONS` entirely (zero remaining raw `@InjectRepository` sites). - `RoleTargetEntity` also removed; one call site in `user-workspace.service.ts` keeps a raw injection with an `eslint-disable` + reason because `softRemove(...)` is not on the wrapper API yet (the migration would require threading `workspaceId` through `deleteUserWorkspace`'s three callers). ### Still exempted (7 entities, follow-up PRs) | Entity | Why deferred | |---|---| | `ApplicationEntity` | ~50 sites with several cross-workspace lookups by id (auth, OAuth, file-storage, cleanup) | | `CalendarChannelEntity` / `MessageChannelEntity` | Use `.increment(...)` (not on wrapper) and `repository.manager.transaction(...)` — wrapper needs to grow `.increment` + the transaction sites need `withManager` or dual-inject | | `FieldMetadataEntity` / `ObjectMetadataEntity` | The metadata services `extends TypeOrmQueryService<X>` and `super(rawRepo)` — requires dual-inject or reworking the inheritance | | `KeyValuePairEntity` | Allows `workspaceId: IsNull()` for instance-level config; wrapper rejects null | | `UpgradeMigrationEntity` | Same — instance-level + cross-workspace ledger | ## Test plan - [x] `npx nx typecheck twenty-server` — clean - [x] `npx nx lint twenty-server` — clean (0/0) - [x] All 10 affected unit specs pass (115 tests) — api-key, agent-role, permissions, workspace-roles-permissions-cache, view-filter-group, workflow-version-step-operations, two-factor-authentication (service + resolver), user-workspace, file - [ ] Server integration tests in CI |
||
|
|
865ca697ca |
Fix AI permission gating: use Ask AI for chat UI, AI Settings for admin endpoints (#21030)
## Summary Closes #20662. Two AI permission flags exist: - **`AI`** (label "Ask AI") — user-facing: chat with AI agents, use AI features - **`AI_SETTINGS`** (label "AI") — admin: create and configure AI agents After auditing every use of these flags I found: ### Frontend — chat UI gated by the admin permission (user-facing bug from the issue) A user granted only `Ask AI` could not see chat tabs, the "new chat" button (desktop & mobile), or the chat content pane; thread initialization was also skipped, leaving the chat in a half-initialized state and producing intermittent `THREAD_NOT_FOUND` errors. Switched these to `AI`: - `MainNavigationDrawerTabsRow.tsx` - `MainNavigationDrawer.tsx` - `MobileNavigationBar.tsx` - `AgentChatThreadInitializationEffect.tsx` ### Backend — admin-only resolvers gated by the user permission (privilege escalation) Two resolvers had a class-level guard of `AI`, letting any user with the user-facing flag reach admin endpoints (skill CRUD, eval runs). Switched the class-level guards to `AI_SETTINGS`: - `SkillResolver` — create/update/delete/activate/deactivate skills - `AgentTurnResolver` — read turns, run/grade evaluations ### Left as-is (already correct) - `AgentResolver` — class-level `AI` for reads (workflow editors and admin pages both need them), mutation-level `AI_SETTINGS` overrides for writes - `AgentChatResolver` & `AgentChatSubscriptionResolver` — already `AI` - `AiGenerateTextController` — already `AI` - Workspace AI config fields in `workspace.service.ts` — already `AI_SETTINGS` ## Test plan - [ ] As a user with `Ask AI` only (no `AI_SETTINGS`): chat tabs, "new chat" button, and chat history pane are visible on desktop + mobile; sending a message works; no `THREAD_NOT_FOUND` errors - [ ] As a user with `AI_SETTINGS` but no `Ask AI`: chat UI is hidden - [ ] As a user with `Ask AI` only: calling `skills` / `createSkill` / `agentTurns` / `runEvaluationInput` via GraphQL returns permission denied - [ ] As an admin (`AI_SETTINGS`): skill settings and agent eval pages still work |
||
|
|
ebfaca5b3d |
EncryptedString PlaintextString branded string types (#21001)
## Summary closes https://github.com/twentyhq/core-team-issues/issues/2464 Introduces compile-time branded types to distinguish encrypted ciphertext from plaintext strings, preventing mix-ups like the one fixed in #20819 — but at the type level rather in addition to the one existing at runtime. ### Branded string primitives - Created `EncryptedString` and `PlaintextString` as hard nominal brands using `z.string().brand(...)`, making them non-assignable to each other or to raw `string` - Created `isEncryptedString` type predicate to narrow `string` to `EncryptedString` based on the `enc:v2:` envelope prefix - Retyped `SecretEncryptionService`: `encryptVersioned` accepts `PlaintextString`, `decryptVersioned` returns `PlaintextString` ### Entity typing - Typed encrypted columns across entities: `SigningKeyEntity.privateKey`, `TwoFactorAuthenticationMethodEntity.secret`, `ApplicationRegistrationVariableEntity.encryptedValue`, `ApplicationVariableEntity.value` - Parameterized JSONB types for connected account connection parameters (`ImapSmtpCaldavParams<Pwd>`) with reusable aliases `EncryptedImapSmtpCaldavParams` / `DecryptedImapSmtpCaldavParams` - Typed DTOs (`CreateApplicationRegistrationVariableInput`, `UpdateApplicationRegistrationVariablePayload`, `UpdateApplicationVariableEntityInput`) with `PlaintextString` ### ApplicationVariable always-encrypt uniformization - Retyped `ApplicationVariableEntity.value` to `EncryptedString | ''` — all values are now encrypted regardless of `isSecret` - Updated `ApplicationVariableEntityService` to always encrypt on write and always decrypt on read - Simplified `UpdateApplicationVariableActionHandlerService` by removing conditional encrypt/decrypt-on-isSecret-toggle logic - Added slow instance command (`2.9.0`) to backfill-encrypt existing `isSecret=false` plaintext rows and tighten the `CHECK` constraint ### ConfigStorageService refactor - Split `convertAndSecureValue` (which used `any`) into two well-typed methods: `convertAndDecrypt` and `convertAndEncrypt` - Introduced `isSensitiveStringValue` type predicate to narrow values before encryption/decryption ### What's next - Typeorm entity derivation to strictly type sitemap configuration as code + handler logic for encryption rotation - https://github.com/twentyhq/core-team-issues/issues/2465 |
||
|
|
9b54200d8c |
Fix playwright CI (#21024)
## Context
The Install Playwright step ran npx playwright install with no
arguments, which downloads all browsers (Chromium + Firefox + WebKit +
ffmpeg, ~500MB+) on every run with no caching.
Fix:
- Install Chromium only — npx playwright install chromium instead of all
browsers.
- Cache the browser binaries — actions/cache on ~/.cache/ms-playwright,
keyed on the resolved Playwright version (v4-playwright-browsers-${{
runner.os }}-<version>). On a cache hit the install step is skipped
entirely; the cache invalidates automatically when the Playwright
version bumps.
|
||
|
|
bd6811d060 |
i18n - translations (#21022)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
b32249877a |
i18n - translations (#21021)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
6fb6ef4e7a |
Add darkmode for oAuth screen (#21005)
## after <img width="1512" height="828" alt="image" src="https://github.com/user-attachments/assets/71664eab-c921-45da-ac67-7a660c976d5c" /> |
||
|
|
bb4e28904f |
Support the "Me" filter for workspace members in dashboard widgets and add multi select (#20971)
Fixes https://github.com/twentyhq/twenty/issues/20225 The "Me" filter (current workspace member) worked in view filters but not in dashboard widget filters — the server never resolved the placeholder, and the widget side-panel UI had no "Me" option and only allowed single selection. Backend: `ChartDataQueryService` now forwards the current workspace member id (from authContext) into filterValueDependencies, so the shared filter logic resolves "Me" the same way it does for view filters. Added unit tests for the converter. Frontend: new multi-select picker for workspace member filters in the widget side panel, mirroring the view filter's actor select: search input, "Me" pinned item, and a multi-select workspace member list. ## Before <img width="3024" height="1488" alt="CleanShot 2026-05-27 at 17 16 36@2x" src="https://github.com/user-attachments/assets/b2cff46c-53e5-4e8a-a463-b106daf96c8c" /> ## After <img width="3024" height="1488" alt="CleanShot 2026-05-27 at 17 14 05@2x" src="https://github.com/user-attachments/assets/8b3b5f11-44b9-4ae5-a2f3-9c7a689f4bb2" /> |
||
|
|
c3d1af89ae |
i18n - docs translations (#21019)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
e6221c5f0e |
Fix twenty sdk billing exports (#21016)
Fix two omissions from #19973 that prevented `twenty-sdk/billing` from being a fully exported subpath: - `package.json`: add `billing` to `typesVersions` (every other subpath was listed; billing was the only one missing, breaking type resolution for consumers using classic TS moduleResolution). - `project.json`: add the billing vite build and `dist/billing` output to the `build:sdk` target |
||
|
|
6566a918af |
chore(deps): bump @apollo/client from 4.1.6 to 4.2.0 (#20993)
Bumps [@apollo/client](https://github.com/apollographql/apollo-client) from 4.1.6 to 4.2.0. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/apollographql/apollo-client/releases">@apollo/client's releases</a>.</em></p> <blockquote> <h2><code>@apollo/client</code><a href="https://github.com/4"><code>@4</code></a>.2.0</h2> <h3>Minor Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/apollographql/apollo-client/pull/13132">#13132</a> <a href="https://github.com/apollographql/apollo-client/commit/f3ce805425d10a9666218a8e109288a2d46dcab1"><code>f3ce805</code></a> Thanks <a href="https://github.com/phryneas"><code>@phryneas</code></a>! - Introduce "classic" and "modern" method and hook signatures.</p> <p>Apollo Client 4.2 introduces two signature styles for methods and hooks. All signatures previously present are now "classic" signatures, and a new set of "modern" signatures are added alongside them.</p> <p><strong>Classic signatures</strong> are the default and are identical to the signatures before Apollo Client 4.2, preserving backward compatibility. Classic signatures still work with manually specified TypeScript generics (e.g., <code>useSuspenseQuery<MyData>(...)</code>). However, manually specifying generics has been discouraged for a long time—instead, we recommend using <code>TypedDocumentNode</code> to automatically infer types, which provides more accurate results without any manual annotations.</p> <p><strong>Modern signatures</strong> automatically incorporate your declared <code>defaultOptions</code> into return types, providing more accurate types. Modern signatures infer types from the document node and do not support manually passing generic type arguments; TypeScript will produce a type error if you attempt to do so.</p> <p>Methods and hooks automatically switch to modern signatures the moment any non-optional property is declared in <code>DeclareDefaultOptions</code>. The switch happens across all methods and hooks globally:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { namespace ApolloClient { namespace DeclareDefaultOptions { interface WatchQuery { errorPolicy: "all"; // non-optional → modern signatures activated automatically } } } } </code></pre> <p>Users can also manually switch to modern signatures without declaring any <code>defaultOptions</code>, for example when wanting accurate type inference without relying on global <code>defaultOptions</code>:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { export interface TypeOverrides { signatureStyle: "modern"; } } </code></pre> <p>Users can do a global <code>DeclareDefaultOptions</code> type augmentation and then manually switch back to "classic" for migration purposes:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { export interface TypeOverrides { signatureStyle: "classic"; } } </code></pre> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/apollographql/apollo-client/blob/main/CHANGELOG.md">@apollo/client's changelog</a>.</em></p> <blockquote> <h2>4.2.0</h2> <h3>Minor Changes</h3> <ul> <li> <p><a href="https://redirect.github.com/apollographql/apollo-client/pull/13132">#13132</a> <a href="https://github.com/apollographql/apollo-client/commit/f3ce805425d10a9666218a8e109288a2d46dcab1"><code>f3ce805</code></a> Thanks <a href="https://github.com/phryneas"><code>@phryneas</code></a>! - Introduce "classic" and "modern" method and hook signatures.</p> <p>Apollo Client 4.2 introduces two signature styles for methods and hooks. All signatures previously present are now "classic" signatures, and a new set of "modern" signatures are added alongside them.</p> <p><strong>Classic signatures</strong> are the default and are identical to the signatures before Apollo Client 4.2, preserving backward compatibility. Classic signatures still work with manually specified TypeScript generics (e.g., <code>useSuspenseQuery<MyData>(...)</code>). However, manually specifying generics has been discouraged for a long time—instead, we recommend using <code>TypedDocumentNode</code> to automatically infer types, which provides more accurate results without any manual annotations.</p> <p><strong>Modern signatures</strong> automatically incorporate your declared <code>defaultOptions</code> into return types, providing more accurate types. Modern signatures infer types from the document node and do not support manually passing generic type arguments; TypeScript will produce a type error if you attempt to do so.</p> <p>Methods and hooks automatically switch to modern signatures the moment any non-optional property is declared in <code>DeclareDefaultOptions</code>. The switch happens across all methods and hooks globally:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { namespace ApolloClient { namespace DeclareDefaultOptions { interface WatchQuery { errorPolicy: "all"; // non-optional → modern signatures activated automatically } } } } </code></pre> <p>Users can also manually switch to modern signatures without declaring any <code>defaultOptions</code>, for example when wanting accurate type inference without relying on global <code>defaultOptions</code>:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { export interface TypeOverrides { signatureStyle: "modern"; } } </code></pre> <p>Users can do a global <code>DeclareDefaultOptions</code> type augmentation and then manually switch back to "classic" for migration purposes:</p> <pre lang="ts"><code>// apollo.d.ts import "@apollo/client"; declare module "@apollo/client" { export interface TypeOverrides { signatureStyle: "classic"; } } </code></pre> </li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/apollographql/apollo-client/commit/e010bdd239b5c10415d4b70ca791467cde12fc88"><code>e010bdd</code></a> Version Packages (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13241">#13241</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/9c4c01a640b43bfb47bd52b25d5881c4ad7bec71"><code>9c4c01a</code></a> Release 4.2 (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13129">#13129</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/222838e99bc6054120cc1f881bb225b1ef049de9"><code>222838e</code></a> Exit prerelease mode</li> <li><a href="https://github.com/apollographql/apollo-client/commit/7d3a533c811a8423536ceeebccc06413ded5b6a3"><code>7d3a533</code></a> Merge branch 'main' into release-4.2</li> <li><a href="https://github.com/apollographql/apollo-client/commit/f20d591bbf74cb4f0d87ec9a14b93a59fe46b039"><code>f20d591</code></a> chore(deps): update actions/create-github-app-token digest to d72941d (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13239">#13239</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/d4a28b6142e47164c8a24bd8c05a8aa3f1ce4eee"><code>d4a28b6</code></a> chore(deps): pin dependencies (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13237">#13237</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/c1f39cf5402b052ab92886a1857840a745aee02b"><code>c1f39cf</code></a> ci: pin Actions@SHA and disable cache on workflows with elevated OIDC permiss...</li> <li><a href="https://github.com/apollographql/apollo-client/commit/511048b7bd6253a38a6b7ebe58e9674a39c74273"><code>511048b</code></a> Event-based refetching docs (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13228">#13228</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/d1f68f1a5fdb7c6915a72b2426cad373a0526c06"><code>d1f68f1</code></a> Version Packages (rc) (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13234">#13234</a>)</li> <li><a href="https://github.com/apollographql/apollo-client/commit/f1b541fed4111028b6842727178288156582e669"><code>f1b541f</code></a> Prepare for rc release (<a href="https://redirect.github.com/apollographql/apollo-client/issues/13232">#13232</a>)</li> <li>Additional commits viewable in <a href="https://github.com/apollographql/apollo-client/compare/@apollo/client@4.1.6...@apollo/client@4.2.0">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
6ea637d6c5 |
Export STANDARD_PAGE_LAYOUT_UNIVERSAL_IDENTIFIERS (#21010)
fix https://discord.com/channels/1130383047699738754/1509086323464474705 |
||
|
|
531410f64a |
fix(ai): expose MORPH_RELATION join columns in AI/MCP tool schemas (#21012)
## Summary
- Fixes a bug where `noteTarget` (and any other morph-relation join
object) created via AI/MCP would land with `targetCompanyId` /
`targetPersonId` / `targetOpportunityId` left null, even though the tool
reported success.
- Root cause: the Zod schema generators for the AI tools only branched
on `FieldMetadataType.RELATION`. MORPH_RELATION fields fell through to
the default case — for `create_*` they were exposed as `targetCompany:
string` instead of `targetCompanyId: uuid`, and for `group_by_*` they
were silently skipped entirely. Downstream
(`data-arg-processor.service.ts` and the group-by arg processor) already
accept the join-column form for both kinds of relations via
`computeMorphOrRelationFieldJoinColumnName` and
`isMorphOrRelationFlatFieldMetadata`, so the fix is purely in the schema
generators.
## Changes
- `record-properties.zod-schema.ts` — extend the existing RELATION
MANY_TO_ONE / ONE_TO_MANY branches to also match MORPH_RELATION.
- `group-by-tool.zod-schema.ts` — replace the silent MORPH_RELATION skip
with the same treatment as RELATION MANY_TO_ONE (exposes `${name}Id` as
a groupBy option).
- `test/integration/ai/suites/mcp-tool-execution.integration-spec.ts` —
new file. First integration test for tool execution end-to-end. Drives
the real MCP JSON-RPC endpoint with the seeded API key (`learn_tools`
for schema introspection, `execute_tool` for invocation):
- asserts `create_note_target`'s schema exposes `targetCompanyId` /
`targetPersonId` / `targetOpportunityId` as UUIDs and does **not**
expose `targetCompany` / `targetPerson` / `targetOpportunity`.
- creates a company + note + noteTarget via MCP, then queries the
workspace schema to confirm `targetCompanyId` is actually persisted in
the FK column.
- asserts `group_by_note_targets` schema accepts `targetCompanyId` as a
groupBy key.
- sets up 3 noteTargets (2 → company A, 1 → company B), calls
`group_by_note_targets` by `targetCompanyId`, and asserts the counts.
Out of scope: `record-filter.zod-schema.ts` has the same pattern (only
RELATION) — left for a follow-up so this PR stays focused on what was
reported.
## Test plan
- [x] `npx nx typecheck twenty-server`
- [x] `npx oxlint --type-aware` on changed files — clean
- [x] `npx oxfmt --check` on changed files — clean
- [x] Integration tests pass (4/4) after `database:reset`:
- `should expose the morph-relation join columns as \`${name}Id\` UUID
parameters`
- `should persist targetCompanyId when create_note_target is invoked via
MCP`
- `should expose targetCompanyId as a valid groupBy option`
- `should group noteTargets by targetCompanyId via MCP`
|
||
|
|
182960051f |
i18n - translations (#21013)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |