Commit Graph

13619 Commits

Author SHA1 Message Date
Paul Rastoin de75be16e2 harden(server): backfill and enforce workspace.databaseSchema invariant with a check constraint (#22855)
## What & why

`core.workspace.databaseSchema` is meant to be set for every workspace
past the creation phase. It only started being written at creation time
in 2.x (dual-write since 2026-03-28, direct write since 2026-04-10);
older workspaces relied on the `1-21 backfill-datasource-to-workspace`
instance command, which never effectively ran on some instances. On
affected rows the column could be left `NULL`.

A null value on a post-creation workspace is a real integrity problem —
several paths trust the column:

- **REST API**: `hydrateRestRequest` throws `No data sources found` for
authenticated requests.
- **GraphQL API**: `getOrComputeSchemaSDL` returns `null`, so
`WorkspaceSchemaFactory` hands back an empty schema.
- **GraphQL introspection** (direct execution) returns `null`.

This PR makes the invariant impossible to silently violate, and repairs
any instance still lagging.

### On the original "No data source, skipping" logs

This investigation started from `BackfillActorSourceEnumValuesCommand`
logging `No data source for workspace <id>, skipping` at high volume.
**That symptom is not explained by this change, and this PR is not a fix
for it.** Findings:

- The workspace iterator only processes `ACTIVE` + `SUSPENDED`
workspaces, and on the affected instance all of those already have
`databaseSchema` set (only `PENDING_CREATION` rows are null, and those
are never iterated).
- `getGlobalWorkspaceDataSource()` never resolves to `undefined` (it
returns a value or throws), so a defined-schema workspace should never
hit the skip branch.
- The upgrade-aware repository proxy was investigated as a possible
cause (it can short-circuit `findOne` to `null` for entities marked
unavailable during an upgrade) and **exonerated**: `WorkspaceEntity` and
its `databaseSchema` column carry no
`@WasIntroducedInUpgrade`/`@WasRemovedInUpgrade` decorators, so
`resolveEntityShapeAtUpgradeCursor` always reports the entity available
and the column visible at every cursor.

In other words, current code should emit zero such skips for that
instance's data, so the root cause of the observed logs remains
undetermined and is tracked separately. See
twentyhq/core-team-issues#2666.

## Changes

- **Check constraint `workspace_requires_database_schema`** (the core of
this PR): enforces `databaseSchema IS NOT NULL` for any workspace past
creation (`activationStatus NOT IN ('PENDING_CREATION',
'ONGOING_CREATION')`). Declared on `WorkspaceEntity` and applied in the
slow instance command's `up()`. Safe against the creation flow:
`databaseSchema` is written in `WorkspaceManagerService.init` (right
after schema creation) long before a workspace becomes `ACTIVE`.
- **Defensive backfill** (`2-21` slow instance command): repopulates
`databaseSchema` where it is `NULL`/empty, deriving the schema name
deterministically from the workspace id (`getWorkspaceSchemaName`) and
only setting it for workspaces whose schema actually exists in
`information_schema.schemata` (so `PENDING_CREATION` rows without a
provisioned schema are left untouched, and stay exempt via the
constraint). No-op on instances already backfilled.
- `runDataMigration` runs before `up()`, so the backfill repairs legacy
rows before the constraint is enforced. Keeping both in the same slow
command (rather than a standalone fast command) guarantees the
constraint is never added ahead of the repair.
- `checkSchemaExists` gets an explicit `: Promise<boolean>` return type.

## Notes

- Backfill + constraint live in a **slow** instance command, so they
only apply on upgrades run with `--include-slow`.
- The constraint is added **`NOT VALID`**: the backfill repairs every
workspace whose Postgres schema exists, but some legacy active/suspended
workspaces (e.g. carried over from very old versions, as reproduced by
the cross-version upgrade from v1.22) have a null `databaseSchema` with
no schema to point at and are unrepairable. `NOT VALID` enforces the
invariant on all future inserts/updates without failing the upgrade on
that pre-existing corruption.
- No production request path was changed — the iterator and
`checkSchemaExists` keep trusting the (now backfilled + constrained)
column.

## Test plan

- [ ] Run `database:migrate:prod --include-slow` on an instance with
null `databaseSchema` rows; verify rows whose schema exists get
backfilled and `PENDING_CREATION` rows are left null.
- [ ] Verify the `workspace_requires_database_schema` constraint exists
on `core.workspace` and rejects nulling `databaseSchema` on an active
workspace.
- [ ] Verify a fresh workspace creation still succeeds (constraint does
not fight the `PENDING_CREATION` → `ACTIVE` transition).
2026-07-13 13:12:43 +00:00
Weiko 8e022d3c49 Fix stale relation table in field widget when switching records in side panel (#22829)
The relation table rendered by a FIELD widget in TABLE display mode kept
its jotai component states (loaded rows, virtualization maps, loading
guards, query identifiers) in instances keyed only by widget id and view
id. Since the side panel record pages share those instances across
records, switching to another record kept rendering the previous
record's related rows until an asynchronous catch-up reload landed, and
any race or error in that catch-up left the previous record's data on
screen permanently.

Scope the record-table widget's context store instance and record index
instance by target record id (and side panel surface), the same way
FieldsWidget already scopes its field list instances. Each record now
gets its own table state, so a record's rows can never appear under
another record, and loads that land after a record switch write into
their own instance instead of the visible one.

loadRecordIndexStates and setRecordGroupsFromViewGroups accept an
optional recordIndexId override so the widget view load effect can
populate the record-scoped instance instead of deriving the shared one
from object name and view id.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22829?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-13 14:50:42 +02:00
Paul Rastoin b2a4bb0e0c docs(apps): add Targeting System Fields page (#22856)
## What

Adds a docs page teaching app developers how to reference auto-created
**system fields** (`createdAt`, `updatedAt`, `id`, …) from views and
other entities, and makes the API it documents real by exporting
`generateDefaultFieldUniversalIdentifier` from the SDK.

## Why

System fields are provisioned by the server, so they're never declared
with `defineField()` and have no importable `universalIdentifier`
constant. Since 2.19 their universal identifier is derived
deterministically from the application id, the object id and the field
name. Hardcoding an invented id fails sync with `INVALID_VIEW_DATA:
Field metadata not found` (this is exactly what broke the
twenty-partners `createdAt` view column).

The twenty-partners app already imports
`generateDefaultFieldUniversalIdentifier` from `twenty-sdk/define`, but
the function was never exported from the SDK. This PR adds the export
and documents the pattern.

## Changes

- **New page** `data/system-fields.mdx` — "Targeting System Fields":
- Lists the 8 system fields (`id`, `createdAt`, `updatedAt`,
`deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`).
- Explains the deterministic derivation and the sync error from
hardcoding ids.
- Documents `generateDefaultFieldUniversalIdentifier({
applicationUniversalIdentifier, objectUniversalIdentifier, fieldName })`
with a full `defineView` example.
- Contrasts with standard objects (use
`STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.<object>.fields.<field>.universalIdentifier`)
and notes that `name` is a default, not system, field.
- **SDK export** — new `generate-default-field-universal-identifier.ts`
wrapping the existing `getFieldUniversalIdentifier` from
`twenty-shared/application` (`name` → `fieldName`), exported from
`define/index.ts`.
- Registered the page in `docs.json` (Data group) and cross-linked it
from the Views doc.

## Notes

`node_modules` isn't installed in this environment, so `nx typecheck`
wasn't run. The wrapper is a signature-matched pass-through and the
`twenty-shared/application` subpath + `getFieldUniversalIdentifier`
barrel export were both verified to exist.

---
_Generated by [Claude
Code](https://claude.ai/code/session_017B7VivHcqZYjn3ukestY9U)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22856?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-13 12:39:06 +00:00
Félix Malfait 6201d06141 Preload Stripe.js before the onboarding payment step (#22858)
## Context

On the plan-required onboarding step, the card form was slow to appear
because Stripe.js is loaded lazily (`@stripe/stripe-js/pure`): the
script download only started once the payment page rendered, and the
PaymentElement iframe could only boot after that.

## What this does

- Adds `usePreloadStripeForPlanRequiredStep`, called once from
`OnboardingStepLayout` (the shared layout for the authenticated
onboarding step routes), so Stripe.js is already loaded by the time the
user reaches the payment step. The hook only triggers when billing is
enabled, the workspace has no subscription yet, and a publishable key is
configured, so self-hosted instances still never contact Stripe.
- Moves the memoized loader to
`settings/billing/utils/getStripePromise.ts`, shared by
`useStripePromise` and the preload hook.
- Stops caching failed script loads: previously a rejected `loadStripe`
promise stayed in the cache forever, which would have made a failed
preload permanently break the payment form. Now a later call retries
(stripe-js re-injects the script tag on retry).
- Extracts the plan-required predicate into
`onboarding/utils/getIsPlanRequired.ts`, now shared with
`useSetNextOnboardingStatus`.

The in-app add-credit-card modal is intentionally left untouched: it has
no preceding step to preload from.

## Tests

- `getStripePromise.test.ts`: dedup per publishable key, retry after a
failed load.
- `usePreloadStripeForPlanRequiredStep.test.ts`: preloads when billing
is enabled and no subscription exists; skips when billing is disabled, a
subscription exists, or the key is missing.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01NtUN99tHPZ6bPWwpYKMbpE)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22858?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-13 14:30:38 +02:00
neo773 9f5d17d1f0 Add receipt metrics and logs to connected account sync webhooks (#22853)
Webhook deliveries from Google and Microsoft were invisible at the app
level: successful notifications produced no logs and no metrics, so
webhook-triggered syncs could not be told apart from cron polling.

Add two counters, connected-account-sync-webhook/received/messaging and
/received/calendar, mirroring the sync-job metric umbrellas, and log a
line whenever a notification triggers a sync. Unmatched subscriptions
keep their existing warn logs.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22853?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-13 14:28:21 +02:00
Paul Rastoin 1b168ac1f7 fix(server): gate workspaceDiscoverability behind upgrade decorator (#22818)
## Context

Needs to be patched on 2.20, will craft a 2.19 equivalent with fallback
asap ( be it won't be merged unlike this one )

Fixes #22662. Follow-up to #22423, which introduced
`workspaceDiscoverability`.

A clean 2.18.x to 2.19 upgrade breaks on login with:

```
QueryFailedError: column workspaceDiscoverability does not exist
```

`workspaceDiscoverability` was added to `WorkspaceEntity` (in #22423) as
a plain, always-selected, non-nullable column, so any workspace query
(including the auth-path `findAvailableWorkspacesByEmail` lookup) fails
as soon as the ORM selects it, before the `2.19` upgrade command that
creates the column has run. Because the Docker entrypoint is fail-open,
the API starts even if the upgrade is delayed, and users hit this on
their first login.

## Changes

- Add `@WasIntroducedInUpgrade` to `workspaceDiscoverability`,
referencing the existing `2.19.0` fast instance command that creates the
column. The upgrade-aware ORM then skips the column until the command
has actually added it, keeping login working during the upgrade.
- Keep the GraphQL `@Field` non-nullable and add a
`workspaceDiscoverability` `@ResolveField` that falls back to
`WorkspaceDiscoverability.PUBLIC` while the column is hidden, so the
resolver never returns `null` for the non-nullable field during the
upgrade window.

This mirrors the existing pattern already applied to `FileEntity.status`
and `FileEntity.applicationRegistrationId`, and the resolver-default
pattern already used for `fastModel` / `smartModel` / `logo`.

## Cherry-pick

This fix needs to be cherry-picked onto both the **2.19** and **2.20**
release branches, since affected instances are upgrading into those
versions.

## Test

- `validate-upgrade-aware-entity-decorators` and
`resolve-entity-shape-at-upgrade-cursor` unit tests pass (the referenced
upgrade command name resolves correctly).
- `upgrade-aware-repository.proxy` and
`upgrade-aware-entity-metadata.adapter` specs pass.
- `typecheck` and lint pass for `twenty-server` and `twenty-front`.
- Regenerating the GraphQL schemas produces no diff (the field stays
non-nullable).
2026-07-13 12:20:28 +00:00
github-actions[bot] 8e66411203 i18n - translations (#22861)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22861?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-13 14:15:41 +02:00
martmull 7381038452 Paginate admin panel app registrations list (#22734)
## Context

The `findAllApplicationRegistrations` query on the admin panel Apps page
(`/settings/admin-panel#apps`) loaded every application registration at
once, with search and filtering done client-side.

## Changes

**Server**
- `findAllApplicationRegistrations` now takes `limit` / `offset` /
`searchTerm` / `isPreInstalledOnly` args and returns a
`PaginatedApplicationRegistrations` object (`registrations`,
`totalCount`, `hasMore`), following the same pattern as `getQueueJobs`.
- `ApplicationRegistrationService.findAll` uses `findAndCount` with
`take`/`skip`, and moves the search (name, source package, universal
identifier via `ILIKE`) and the pre-installed filter into the SQL query,
mirroring how `getInstalledWorkspacesGlobal` filters installed
workspaces.

**Frontend**
- `SettingsAdminApps` passes the page, the debounced search term (300ms,
like the installed workspaces table), and the pre-installed toggle as
query variables instead of filtering client-side.
- Adds a Previous / Next pagination footer (25 per page) matching the
queue jobs table, shown only when there is more than one page.
- The "unconfigured first" ordering is kept within each page
(`isConfigured` is a dataloader-resolved field, so it can't be sorted in
SQL).

## Notes
- Regenerated `generated-admin/graphql.ts` follows in a subsequent
commit.

---
_Generated by [Claude
Code](https://claude.ai/code/session_015erumgPozkbNA3zPeKrrFW)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22734?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Weiko <corentin@twenty.com>
2026-07-13 14:07:11 +02:00
Abdullah. b06f3c7f8f fix(website): query isVetted so the apps marketplace renders again (#22859)
## What

Production `/apps` renders no apps. The marketplace queries still
request `isFeatured`, but #22674 renamed that flag to `isVetted` across
the server schema (2-20 instance command renames the DB column — same
flag, same data, trust-signal semantics). Production (2.21) rejects the
query:

```
Cannot query field "isFeatured" on type "MarketplaceApp"
```

The transport throws on GraphQL errors, `fetchMarketplaceApps` catches
and falls back to `[]`, so the page renders the empty state.
`/apps/[slug]` detail pages degrade the same way.

## Fix

Rename `isFeatured` -> `isVetted` across the website marketplace module:
both queries, the API response types, the `MarketplaceApp` domain type,
and the vetted-first sort. 4 files, no behavior change beyond restoring
the data (same column, same values).

The catch-all `[]` fallback is intentionally left in place.

## Verification

- Corrected query run against `https://api.twenty.com/metadata`: returns
the 3 live apps (People Data Labs, Last contact, Call Recorder),
`isVetted: true`.
- Rename provenance confirmed: #22674 is a pure rename (paired diff,
symmetric column rename, `previousName: 'isFeatured'` marker on the
entity).
- `nx typecheck twenty-website` + `nx lint twenty-website` green.

Takes effect on the next website deploy (`force-dynamic` route, 300s
revalidate).
2026-07-13 13:56:35 +02:00
Paul Rastoin a6af730353 chore: upgrade call-recorder, last-contact, people-data-labs to twenty-sdk 2.20 (#22852)
## What

Upgrades three public apps to `twenty-sdk` 2.20.

For each app, bumped `twenty-sdk` and `twenty-client-sdk` to `2.20.0`,
raised the `engines.twenty` floor to `>=2.20.0`, and regenerated
`yarn.lock`:

- **call-recorder**: `2.19.0` -> `2.20.0`
- **last-contact** (`@twentyhq/last-contact`): `2.19.0-alpha.1` ->
`2.20.0`
- **people-data-labs**: `2.19.0-alpha.1` -> `2.20.0`

## Verification

- Lockfile diffs are version/checksum-only; the SDK's transitive
dependency set is unchanged between 2.19 and 2.20, so no new packages
were introduced.
- `yarn typecheck` passes cleanly for all three apps against 2.20,
confirming no breaking API changes to adapt to.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01YWiC3qAbBcE1kvBMWvaxba)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22852?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-13 12:06:16 +02:00
martmull 6185c74786 Bypass corrupted cached front-component responses with a cache-bust query parameter (#22854)
## Context

Follow-up to #22672. Users' browsers hold corrupted cached responses for
front-component request URLs from before the fix. #22672 fixed serving
and caching for newly built front components, but the corrupted entries
already sitting in browsers keep being served and need to be bypassed
programmatically.

## What changed

- `fetchComponentSourceFromNetwork` appends a constant `cacheBust=v2`
query parameter to the component request (`GET
/rest/front-components/:id/:cacheKey`). This changes the cache key, so
any corrupted response cached under the old URL is never served again
and the bundle is refetched.
- Existing query parameters on the URL are preserved; if the URL cannot
be parsed, the request falls back to the original URL unchanged.
- The presigned S3 URL from the JSON handoff is left untouched: adding a
query parameter there would invalidate its SigV4 signature.
- The `CacheStorage` layer keeps using the logical component URL as its
key, so its checksum-verified entries and hit behavior are unchanged.

## Test plan

- `fetchComponentSourceFromNetwork.spec.ts`: assertions updated to
expect the cache-busted component URL, plus a new test that existing
query parameters are preserved and one that the presigned fetch stays
unmodified; full renderer suite passes (226 tests).
- `npx nx typecheck twenty-front-component-renderer` and `npx nx lint
twenty-front-component-renderer` pass.
2026-07-13 11:34:53 +02:00
Paul Rastoin 652adc3c03 fix(server): backfill isSystemSideEffect on system fields provisioned before 2.15 (#22850)
## Context

The `isSystemSideEffect` column was introduced in **2.15** via a fast
instance command that added it with `DEFAULT false`. That stamped
`false` onto every pre-existing `fieldMetadata` row — including the 8
engine-owned system fields (`id`, `createdAt`, `updatedAt`, `deletedAt`,
`createdBy`, `updatedBy`, `position`, `searchVector`) of every object
provisioned before 2.15, regardless of the creation path (API metadata
**and** manifest sync).

The per-workspace backfill that should have re-flagged those existing
rows was explicitly deferred as out of scope in #21673 ("PR 2") and
never shipped for `fieldMetadata`. Because `isSystemSideEffect` is
configured with `toCompare: false`, no later sync ever repaired the
stale value either.

Since **2.20** the SDK no longer declares system fields in manifests. On
an up-to-date instance, `twenty plan` against an unchanged app therefore
diffs those stale-`false` system fields as **missing from the
manifest**, and they fall through the `isSystemSideEffectFlatEntity`
exclusion in
`buildAllFlatEntityOperationRecordByMetadataNameFromFromTo`. Deletion
inference then emits them as deletes, which the validator rejects:

```
Sync failed with 144 errors
fieldMetadata: 144 errors
  1..144. FIELD_MUTATION_NOT_ALLOWED: System fields cannot be deleted
```

(144 = 8 system fields × 18 custom objects, as reported on a production
2.20 instance.)

## What this PR does

Adds a **2.21 workspace command**
(`upgrade:2-21:backfill-system-field-is-system-side-effect`) that
iterates active/suspended workspaces and flags the 8 system fields as
`isSystemSideEffect: true`.

- **Resolution by deterministic universal identifier**: for each object
× reserved system field name it recomputes
`getFieldUniversalIdentifier(applicationUID, objectUID, name)` and looks
the row up in the flat maps. This is safe (and preferable to matching by
`name`) because the 2.19 backfill already took over system field UIDs
for every application, so an author-declared field reusing a reserved
name keeps its own identifier and is never touched. An extra `isSystem`
guard warn-and-skips any mismatch.
- **All applications** are covered (installed apps, workspace custom
app, twenty-standard): the stale flag is a function of *when* a row was
provisioned, not *how*. Installed/custom apps are the acute `twenty
plan` delete trap; twenty-standard has no trap today but flagging is a
zero-diff no-op (`toCompare: false`) and a prerequisite for the
end-state ownership invariant.
- **`name` is intentionally excluded**: the 2.20 slow instance command
deliberately flipped it to `false` (caller-provided default, not
engine-owned); re-flagging it would undo that migration.
- Supports `--dry-run`, updates only the collected rows, and invalidates
the `flatFieldMetadataMaps` workspace cache after the write (a raw
repository update does not invalidate it).

## Related

- Resolves the pre-2.15 regression tail of
twentyhq/core-team-issues#2635
- Follow-up to twentyhq/core-team-issues#2642 (system field side-effect
engine migration)

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22850?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Weiko <corentin@twenty.com>
2026-07-13 08:55:54 +00:00
Paul Rastoin b94e38c2d9 fix(server): stabilize flaky app-install workspace-version gate test (#22851)
## What

The integration test
`failing-app-installation-workspace-version.integration-spec.ts` is
flaky depending on the shape of the upgrade sequence, especially right
after a version bump. It intermittently fails at upload time with:

```
App requires Twenty server >=2.21.0 but this server is 2.20.0.
(SERVER_VERSION_INCOMPATIBLE)
```

## Why

The test mixed two different version sources:

- The upload-time check (`validateServerCompatibility`) compares the
app's required version against the **instance** inferred version, i.e.
the last attempted instance command (`workspaceId IS NULL`, via
`getInferredVersion`).
- The test's `beforeAll` instead derived the required version from the
**workspace** cursor.

These agree most of the time but diverge right after a version bump
whose newest upgrade segment ends in workspace-scoped commands and adds
no new instance command. In that state the seeded workspace cursor sits
at the new version while the instance is still at the previous one. The
test then uploads an app requiring `>=newVersion`, which fails the
instance gate at upload time before the workspace gate under test is
ever reached.

## How

Derive the gate version in `beforeAll` from the last attempted instance
command, mirroring exactly what `getInferredVersion()` uses. The
required version is then always `>=` the instance's own version, so the
upload passes; injecting that same command as a failed workspace attempt
drops the workspace to the previous completed version, so the install
reliably hits the workspace gate and returns
`WORKSPACE_VERSION_INCOMPATIBLE` as the snapshot expects. This holds
regardless of whether the newest version's segment ends in an instance
or workspace command.

No production code changed; the fix is confined to test setup logic.


---
_Generated by [Claude
Code](https://claude.ai/code/session_01UtEpU7fF4q6pydGRaawfve)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22851?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-13 10:33:45 +02:00
github-actions[bot] bbe9886274 chore: sync AI model catalog from models.dev (#22842)
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.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22842?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com>
2026-07-11 08:42:36 +02:00
github-actions[bot] 983f03adbe i18n - translations (#22833)
Created by Github action

---------

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 20:36:28 +02:00
neo773 b0dc637dbd Throw proper error on duplicate emailing domain (#22790)
Adding an emailing domain that already exists blew up with a raw
QueryFailedError and the client just saw a generic "An error occurred".
The unique index on domain is global, so the workspace-scoped existence
check never caught rows owned by another workspace.

Now the check is unscoped and throws an EmailingDomainException mapped
to CONFLICT with a proper user-facing message, in both the
createEmailingDomain mutation and the email group channel flow. Also
dropped the hardcoded catch-all snackbar on the new channel page so
server messages actually reach the user.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22790?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-10 20:28:34 +02:00
Paul Rastoin cb95410a51 ci: test twenty-apps install against latest dockerhub and local server + new trigger (#22636)
## Why

App installability can silently regress from two directions, and today
CI only covers one of them:

1. A **server** change (about to merge from the monorepo) breaks the
ability to install the **current public apps** — a
backward-compatibility regression users would hit on upgrade.
2. An **app** change breaks against a server **built from the current
monorepo files** (not just the last published image), so the app and the
upcoming server drift apart before either ships.

Both are compatibility guarantees between the server and the app
catalog. Today they are only tested from the app side, against the
latest published image. This PR makes CI enforce the contract from both
sides:

- Any server PR must keep **every** current public app installable.
- Any app PR is exercised against both the **released** server (its
integration suite — what users run today) and the **upcoming**
(monorepo) server (integration plus deploy + install).

## What

Shared building blocks so both CIs exercise the same paths instead of
duplicating them:

- **`spawn-twenty-server`** (composite action) — returns a running
server (`server-url` + `api-key`) from either the latest published
Docker Hub image or a server built from the monorepo. Both sources
expose the same contract, so callers never branch on how the server came
up.
- **`test-twenty-app`** (composite action) — exercises one app against a
given server, delegating deploy + install to the shared
`deploy-twenty-app` / `install-twenty-app` actions.
- **`discover-apps`** (reusable workflow) — the single source of truth
for the app matrix. Parameterized by `scope` (`public` vs
`internal-and-public`) and `changed-only`, so both CIs derive their
matrix from the filesystem instead of a hand-maintained list. Discovery
stays automatic: a newly added public app is picked up with no CI edit,
which is what keeps the "every public app" guarantee honest.

Wired in:

- **CI Server** gains a `server-apps-install-smoke` matrix that installs
every public app (`discover-apps` with `scope: public, changed-only:
false`) against the about-to-merge server, gated in
`ci-server-status-check` so a regression blocks merge.
- **CI Twenty Apps** discovers changed apps (`scope:
internal-and-public, changed-only: true`) and runs each against both
server sources — the released image and the monorepo build.

## Why the coverage differs per side (not "always everything")

`test-twenty-app` has three explicit modes —
`installation-and-integration-test` (integration + deploy + install),
`integration-test-only` (suite only), `installation-only` (deploy +
install only) — because the useful signal depends on what actually
changed:

- **App PR against the monorepo server →
`installation-and-integration-test`.** The app changed, so run its whole
suite against the upcoming server, install included.
- **App PR against the released server → `integration-test-only`.**
Checks the app's own suite against what users run today; install against
the released image is left to the SDK e2e path.
- **Server PR → `installation-only`, across all apps.** The apps did not
change; the only question is "can each one still be installed." Running
every app's full integration suite on every server PR would be far
slower and largely redundant. Installation-only keeps this broad (the
whole catalog) and cheap enough to always run and block merge.

The tradeoff is deliberate: broad but shallow where nothing in the app
changed, deep where it did.

## Notes / trade-offs

- On app-only PRs the `local` source pays a full server build per app
(the `server-build` cache is only warm on server PRs). Could be
optimized later with a shared warm-up job.
- SDK-local (Verdaccio) install testing stays in
`ci-create-app-e2e-minimal`; this PR's `local` source targets the server
build.

<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22636?utm_source=github"
rel="nofollow noreferrer noopener" target="_blank">``&lt;img alt="Review
in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"&gt;``</a>
2026-07-10 19:18:53 +02:00
Paul Rastoin 3614183200 fix(server): stabilize app install version-gate integration test (#22826)
## Why

The `failing-app-installation-workspace-version` integration suite fails
on CI (e.g. [this
run](https://github.com/twentyhq/twenty/actions/runs/29105697459/job/86405891168)):

```
App requires Twenty server >=2.21.0 but this server is 2.20.0.
subCode: SERVER_VERSION_INCOMPATIBLE
```

The test uploads an app requiring `>=${TWENTY_CURRENT_VERSION}` and
expects the install to be rejected by the **workspace** version gate.
But after the `2.21.0` version bump, `TWENTY_CURRENT_VERSION` (`2.21.0`)
moved ahead of the latest instance upgrade command (`2-20`, so
`getInferredVersion()` returns `2.20.0`). The tarball upload runs the
**instance** server-compat check first, which rejects `>=2.21.0` against
a `2.20.0` server before the workspace gate under test is ever reached.

The sibling sync test is unaffected because sync only validates
workspace compatibility, not the upload-time instance check.

## What

Derive the required version range from the version the instance actually
reached (the workspace upgrade cursor via
`extractVersionFromCommandName`) instead of the drifting
`TWENTY_CURRENT_VERSION` constant. This way:

- The upload passes the instance server-compat check (server satisfies
`>=<current version>`).
- The workspace, which resolves one version behind after the injected
failed cursor, still fails the workspace gate, producing the expected
`WORKSPACE_VERSION_INCOMPATIBLE` error.

The error assertion keeps using the normalized snapshot
(`scrubSemverVersions`), so the concrete version numbers do not leak
into the snapshot and future version bumps won't churn it.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XqhqQ8VGJZWBuznR8nRviX)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22826?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-10 16:33:25 +00:00
Raphaël Bosi c9d84ba7f0 Disable install button in app install onboarding when no app is selected (#22822)
In the app installation onboarding step, the Install button was always
clickable, even with no app selected. Clicking it with an empty
selection ran the completion flow with zero apps, which is equivalent to
skipping.

Now the button is disabled until at least one app is selected (in
addition to staying disabled while completing). The Skip button remains
available for users who don't want to install anything.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22822?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-10 17:55:00 +02:00
github-actions[bot] c0f5a28d5d i18n - docs translations (#22824)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22824?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 17:23:57 +02:00
Thomas Trompette cb2e325c4b fix(workflow): make prefilled workflow ids unique per workspace (#22800)
## Problem

\`prefillWorkflows\` (run for every workspace on \`activateWorkspace\`)
inserts workflows and versions with **hardcoded ids**
(\`QUICK_LEAD_WORKFLOW_ID = 8b213cac...\`, etc.). So every workspace
carries the same workflow/version record ids. Within a workspace schema
that's harmless, but it means workspace record ids are **not unique
across workspaces**, which:
- breaks the workflowVersion backfill on the shared core table (surfaced
as the \`IDX_WORKFLOW_VERSION_ONE_ACTIVE_PER_WORKFLOW\` duplicate-key
error, since multiple workspaces claim the same active \`workflowId\`),
and
- collides on \`core.workflow\`/\`core.workflowVersion\` PKs once
workflows migrate to core (the core row reuses the workspace record id),
causing cross-workspace clobbering.

## Fix

Derive the prefill ids **deterministically per workspace**:
\`getWorkflowPrefillIds(workspaceId)\` returns \`v5(label:workspaceId,
namespace)\` for each of the workflow/version/trigger ids. Deterministic
(stable across the idempotent \`orIgnore\` re-runs) but unique per
workspace. The command-menu-item prefill uses the same helper so its
\`workflowVersionId\` reference stays consistent.

Only affects **new** workspaces; existing workspaces keep their current
ids (prefill is skipped on re-activation).

## Test

Reset seeds two workspaces; both now get a Quick Lead workflow with a
**distinct** v5-derived id (not the old \`8b213cac\`), and internal
references stay consistent (\`version.workflowId == workflow.id\`,
\`lastPublishedVersionId == version.id\`). Typecheck + lint clean.

Companion to #22795 (which scopes the active index to workspace).
Together they fix the backfill duplicate-id failures.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22800?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-10 16:21:04 +02:00
github-actions[bot] 165d47a9f0 i18n - translations (#22817)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22817?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-07-10 15:50:53 +02:00
Priyanshu Bartwal 7babc049f5 [Twenty-Front]: Record board column drag and drop functionality (#22323)
Closes #22321 

- Used `@dnd-kit` library for core drag and drop logic.
- Tried to keep as much similar to #21304 as possible.


https://github.com/user-attachments/assets/bff100cf-d727-4281-a5fa-b010a373f189



<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22323?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
2026-07-10 13:47:59 +00:00
twenty-pr[bot] ab9e6f30b8 chore: bump version to 2.21.0 (#22820)
## Summary

- Moves current version to previous versions array
- Sets TWENTY_CURRENT_VERSION to the new version
- Updates TWENTY_NEXT_VERSIONS with the next minor version
- Bumps twenty-client-sdk, twenty-sdk, and create-twenty-app to the same
version

## Checklist

- [ ] Verify version constants are correct
- [ ] Verify npm package versions match

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22820?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: Github Action Deploy <github-action-deploy@twenty.com>
2026-07-10 15:46:26 +02:00
martmull 7f8a1da27a Fix Cloudflare rate limiting during last-contact backfill (#22811)
## Context

Upgrading `twenty-last-contact` on a production workspace failed with a
Cloudflare error 1015 ("You are being rate limited"). The
`backfill-last-contact` post-install function runs on every version
upgrade and fired 20 concurrent update mutations per batch with no pause
between batches, on top of paginated full-collection reads. On a
workspace with real email/calendar history that burst trips Cloudflare's
rate limit, and since the client SDK throws on any non-2xx response, a
single 429 killed the whole install/upgrade hook mid-backfill.

## Changes

- New `executeWithRetry` util: retries rate-limit (429 / Cloudflare
1015) and transient gateway/network errors (502/503/504, timeouts,
connection resets) with exponential backoff and jitter, capped at 5
attempts. Honors a `retry_after` hint when present in the response body.
Non-retryable errors still throw immediately.
- All backfill queries and mutations are wrapped with it.
- Update batch concurrency reduced from 20 to 10 to keep bursts under
the rate limit in the first place.
- Bumped app version to 1.1.1 with a changelog entry.

## Test

- Added unit tests for `executeWithRetry` (success passthrough,
retry-then-succeed, non-retryable passthrough, retry exhaustion,
`retry_after` handling).
- `yarn test:unit` (28 passed), `yarn typecheck`, `yarn lint` all green
in the app package.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01AtnkEfbpFhLp5qCJbSmZpE)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22811?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-10 15:26:07 +02:00
github-actions[bot] 7861ada589 i18n - translations (#22815)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22815?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-07-10 15:23:27 +02:00
github-actions[bot] 5f70beb2d2 i18n - docs translations (#22816)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22816?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 15:18:53 +02:00
Charles Bochet 4fc3650b2d fix(emails): deterministic i18n catalog order to stop recurring i18n PR conflicts (#22803)
## Problem

The automated `i18n - translations` PR (branch `i18n`) conflicts on
`twenty-emails` `.po` files on **every** cycle. Each time, the msgid
*set* is identical to main — only the entry **order** differs.

## Root cause

`twenty-emails` uses explicit message ids (`js-lingui-explicit-id`).
With lingui's default ordering, **`lingui extract` is non-idempotent for
this catalog** — two consecutive runs on identical source produce
different `.po` orderings:

```
# no source change between runs
lingui extract  # run 1
lingui extract  # run 2  -> ~170 lines reordered vs run 1
```

So the order produced by the `i18n-push` extract on main, the order
stored in Crowdin, and the order the `i18n-pull` bot downloads never
agree, and the translation PR re-conflicts perpetually.
`twenty-front`/`twenty-server` use hashed ids and are already idempotent
— this is isolated to emails.

## Fix

Set `orderBy: 'messageId'` in `twenty-emails/lingui.config.ts`. Verified
this makes extraction idempotent — two consecutive extracts now produce
byte-identical output.

This commit includes the one-time reorder of the existing catalogs into
the stable order. Generated `.ts` output is unchanged (already
order-independent). After merge, one push cycle syncs Crowdin to the
stable order, after which the recurring conflicts stop.

## Test plan

- `nx run twenty-emails:lingui:extract` twice → no diff on the second
run.
- `nx run twenty-emails:lingui:compile` → succeeds, generated `.ts`
unchanged.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22803?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-10 15:16:33 +02:00
martmull 945724e016 accepts any valid expression, not just an identifier (#22778)
Fixing and issue with front component definition

look at the unit test to see the actual fix
2026-07-10 13:15:26 +00:00
Weiko 666ceb41d5 Fix SDK plan on non installed apps (#22805)
# Context

`yarn twenty plan` fails when the app has never been installed in the
target workspace:

```
Sync failed with error: Application "f5ce204f-..." is not installed in workspace "10f39a9d-...". Install it first.
Hint: run `yarn twenty dev --once` to register the app in this workspace, then retry.
```

This forces developers to apply before they can plan, which defeats the
purpose of `plan`. Planning a not-yet-installed app is well defined: the
from-state is empty, so the plan is simply "create everything".

## Why it failed

The dry-run sync required the application row to exist in two places:

1. `ApplicationSyncService.synchronizeFromManifest` threw
`APP_NOT_INSTALLED` when the app row was missing, because the dry-run
needs an owner `FlatApplication` to anchor the from → to metadata diff.
2.
`WorkspaceMigrationFlatEntityMapsService.computeAllInvolvedApplicationIds`
threw when the owner app id was absent from `flatApplicationMaps`, even
though the only hard dependency of a build is the twenty standard
application.

`apply` never hit this because it registers the app row as a side effect
before syncing (and even swallows this exact error on its pre-apply
plan).

# What this PR does

Keeps `plan` strictly read-only, no registration or app row is created:

- **`application-sync.service.ts`**: on dry-run, resolve the owner to
the installed application when it exists (unchanged behavior), otherwise
build a virtual, non-persisted `FlatApplication` from the manifest. Its
freshly generated id matches no existing metadata, so the from-state
slice resolves to empty and every manifest entity shows up as a create.
- **`workspace-migration-flat-entity-maps.service.ts`**: relax the guard
so only the twenty standard application is required. A missing owner app
just contributes an empty from-slice instead of throwing. Installed apps
take the exact same path as before (`applicationId` defined → identical
behavior).
2026-07-10 15:11:46 +02:00
Raphaël Bosi 60f5964c64 Run front components in a sandboxed opaque-origin iframe (#22588)
Front components run untrusted third-party React in a Web Worker. That
worker previously shared the host origin, so it could reach
origin-scoped storage (the metadata-store IndexedDB, the
`twenty-sign-out` BroadcastChannel), cookies, and same-origin resources.

This runs the worker inside a `sandbox="allow-scripts"` (no
`allow-same-origin`) iframe, giving it an opaque origin where the
browser denies localStorage, cookies, IndexedDB, and BroadcastChannel
outright. The worker is kept inside the iframe (rather than a bare
iframe) so untrusted code always runs off the main thread; the
remote-dom render path is unchanged.

- **Transport:** host ↔ iframe ↔ worker over a re-transferred
`MessagePort` (`ThreadMessagePort`); a small bootstrap script is inlined
into the iframe via `srcdoc` (bundled at build time by a prebuild step)
and relays the port to the worker it spawns. Messages across the
boundary use a typed discriminated union with a single parse/guard.
- **Network:** under the opaque origin, direct fetches to the Twenty API
would be `Origin: null`, so the component source and SDK modules are
fetched through an allowlisted, credential-omitting `hostFetch` bridge
and blobbed inside the worker. The allowlist is single-sourced on the
host (http(s) origins only) and carried in the render context. The
bridge is mandatory (rendering fails closed if it is missing), refuses
redirects except for GET/HEAD to the known file-storage URLs, and caps
response body size.
- **SDK loading:** SDK client modules now load inside the worker through
the bridge, replacing the host-side SDK-blob state/effect/provider with
a pure `getSdkClientUrls` URL builder.
- **Isolation tests:** a unit test locks the sandbox attribute
(`allow-scripts`, never `allow-same-origin`); a browser test asserts the
worker actually gets an opaque origin with storage denied, probing
cookies by writing one rather than reading an empty jar.

Also adds a "List Companies" seed front component that queries workspace
data via the SDK client (exercising the bridge end-to-end),
single-sources the command-menu confirmation-modal result event name and
detail type in `twenty-shared` (previously a hand-synced duplicate), and
decomposes the renderer (bridge, sandbox, worker orchestration) into
small single-purpose utils with unit tests.

## How it works

```mermaid
sequenceDiagram
    autonumber
    participant Host as Host window (twenty-front · host origin)
    participant Frame as Sandboxed iframe (allow-scripts · opaque origin)
    participant Worker as Worker (untrusted component · opaque origin)
    participant API as Twenty API (host origin)

    rect rgb(238,242,248)
    Note over Host,Worker: 1 — Boot handshake
    Host->>Frame: create iframe sandbox="allow-scripts", srcdoc = inlined bootstrap script
    Host->>Host: MessageChannel + ThreadMessagePort(port1)<br/>exports = host API + hostFetch
    Frame-->>Host: READY
    Host->>Frame: INIT + transfer port2
    Frame->>Worker: spawn inlined Worker + re-transfer port2
    Worker->>Worker: ThreadMessagePort(port)<br/>exports = render / updateContext
    Note over Host,Worker: Port now entangles Host ↔ Worker directly
    end

    rect rgb(246,240,248)
    Note over Host,Worker: 2 — Render
    Host->>Worker: render(connection, { componentUrl, sdkClientUrls, hostFetchOrigins, token })
    Worker->>Worker: override globalThis.fetch<br/>(Twenty origins → hostFetch)
    end

    rect rgb(248,244,238)
    Note over Worker,API: 3 — Network via hostFetch bridge (opaque Origin:null cannot reach the API directly)
    Worker->>Host: hostFetch(componentUrl, Bearer)
    Host->>Host: origin allowlist + credentials:'omit'
    Host->>API: fetch(componentUrl)
    API-->>Host: source
    Host-->>Worker: { status, headers, body }
    Worker->>Host: hostFetch(sdkClientUrls.core / .metadata)
    Host-->>Worker: SDK module sources
    Worker->>Worker: blob each source in its own opaque origin → import() → run untrusted React
    end

    rect rgb(238,248,242)
    Note over Worker,Host: 4 — Render mirror
    Worker->>Host: remote-dom mutations (RemoteConnection)
    Host->>Host: RemoteReceiver → RemoteRootRenderer → host DOM
    end

    Note over Worker: Opaque origin ⇒ browser denies localStorage,<br/>cookies, IndexedDB, BroadcastChannel
```
2026-07-10 13:10:30 +00:00
github-actions[bot] 95b672e61e i18n - translations (#22814)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22814?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 15:09:01 +02:00
github-actions[bot] caed92b1d4 i18n - translations (#22813)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 15:02:17 +02:00
Marie f667ba500c fix(front): clean stale morph relations from metadata store on object deletion (#22681)
## Problem

After deleting a custom object (e.g. `meeting`), the app crashes with
"Sorry, something went wrong" on pages that load records referencing
that object through a morph relation. The console shows:

```
Target object metadata item not found for target (morph target meeting)
```

It reproduces on the machine that used the object before deletion but
not on a fresh machine, which points at a stale client metadata store
rather than a server issue.

## Root cause

Every field carries its own server-provided `morphRelations` array; a
morph relation field (note/task/timeline targets, etc.) lists every
object it can point to, including the deleted one. When an object is
deleted, `useDeleteOneObjectMetadataItem` and the SSE `delete` handler
only remove the deleted **object** and its own fields from the metadata
store. The sibling morph fields on other objects keep their now-dangling
`morphRelations` entry pointing at the deleted object.

Those stale entries were only meant to be cleaned up later by a
collection-hash-triggered `network-only` refetch. When that
reconciliation does not win, `generateDepthRecordGqlFieldsFromFields`
can't resolve the deleted morph target in `objectMetadataItems` and
throws, crashing the page.

## Fix

Clean `morphRelations` entries referencing the deleted object from the
field metadata store at deletion time, so the store stays
self-consistent immediately instead of relying on an async refetch.
Applied in both paths that handle object deletion:

- `useDeleteOneObjectMetadataItem` (the client performing the deletion)
- `MetadataStoreSSEEffect` delete handler (other tabs/clients receiving
the event)

The throw in `generateDepthRecordGqlFieldsFromFields` is intentionally
left in place so any genuine future metadata inconsistency still
surfaces rather than being silently swallowed.

## Test

Added a unit test for the cleaning util covering: morph relations
targeting the deleted object are removed, only changed fields are
returned, non-morph fields are untouched, and nothing is returned when
no relation targets the deleted object.
2026-07-10 14:55:25 +02:00
github-actions[bot] 797acff5fa i18n - translations (#22812)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 14:54:18 +02:00
Thomas Trompette c1b62334b7 fix(workflow): scope one-active-per-workflow index to workspace (#22795)
## Problem

The Phase 0 core index \`IDX_WORKFLOW_VERSION_ONE_ACTIVE_PER_WORKFLOW\`
is on \`(workflowId) WHERE status='ACTIVE'\`, with **no
\`workspaceId\`**. But \`core.workflowVersion\` is a shared multi-tenant
table, so this enforces "one active version per workflowId **globally
across all workspaces**" instead of per workspace.

The version backfill fails on staging with:
\`\`\`
duplicate key value violates unique constraint
"IDX_WORKFLOW_VERSION_ONE_ACTIVE_PER_WORKFLOW"
Detail: Key ("workflowId")=(8b213cac-...) already exists.
\`\`\`
across several different workspaces that share the same workflowId
(seeded/cloned data): workspace A's active version claims the
workflowId, and every other workspace's insert collides. Every other
index on this table includes \`workspaceId\`; this one dropped it when
copied from the per-tenant workspace entity.

## Fix

Index becomes \`(workspaceId, workflowId) WHERE status='ACTIVE'\` — one
active version per workflow **per workspace**, matching the table's
multi-tenant design and the intended invariant. New 2-20 fast instance
command drops and recreates the index (Phase 0's command is
merged/append-only).

## Test

Reset + reproduce the exact scenario against the fixed index:
- two workspaces with the same workflowId, both ACTIVE → **insert
succeeds** (previously collided)
- a second ACTIVE version for the same workflow within one workspace →
**still blocked** (invariant preserved)

Zero \`migrate:generate\` drift, typecheck + lint clean. After this
deploys, re-run \`upgrade:2-20:backfill-workflow-version-to-core\`.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22795?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-10 12:48:33 +00:00
github-actions[bot] dcb67fbbe1 i18n - translations (#22810)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 14:46:54 +02:00
Rashad Karanouh bf1220883f docs: partner CTAs on high-traffic pages (workflows, data-model, docker-compose) (#22808)
## Summary

Follow-up to #22719 (merged), which added partner-marketplace CTAs to
four **high-intent, low-traffic** docs pages (SSO, both migration
guides, implementation services).

Reviewing the docs' **top-visited pages** showed none of those four rank
in the top ~20 — they're the high-intent tail, which is correct, but
small reach. This PR extends the same pattern to three **high-traffic
pages that also carry buying intent**, without touching the pure
top-of-funnel intros/quickstarts (volume without intent → a CTA there is
just noise).

Same conventions as #22719: Mintlify-native `<Tip>` callouts,
partner-first with `contact@twenty.com` secondary, directory deep-linked
via `?categories=<scope>` and tagged with `?ref=docs-*`. No new snippet;
no `docs.json`, navigation, or translation (`l/`) changes.

## Pages changed — screenshots (one per page)

> Preview locally with `npx mintlify dev` from `packages/twenty-docs`,
or use the Mintlify PR preview once it posts. Paths below are under
`docs.twenty.com`.

### 1. `/user-guide/workflows/overview` (~593 views)
New `## Need Help?` two-bullet `<Tip>` → **Done for you** (Solutioning
partner) / **Onboarding pack** (Workflow Creation). Maps 1:1 to the
named onboarding service.

_screenshot:_
<img width="1440" height="818" alt="Screenshot 2026-07-10 at 14 34 32"
src="https://github.com/user-attachments/assets/231f681d-b5be-4b1e-9b6a-a4947a9fca37"
/>


### 2. `/user-guide/data-model/overview` (~954 views)
Replaced the plain "Need Help?" line with a two-bullet `<Tip>` → **Done
for you** (Solutioning partner) / **Onboarding pack** (Data Model
Design). Keeps the existing Implementation Services link.

_screenshot:_
<img width="1436" height="817" alt="Screenshot 2026-07-10 at 14 34 14"
src="https://github.com/user-attachments/assets/c0fe1064-1030-4062-91c7-24644ac31654"
/>


### 3. `/developers/self-host/capabilities/docker-compose` (~2421 views)
New `## Managed Hosting` single-line `<Tip>` → *find a certified Twenty
hosting partner* (Hosting), contact fallback. Framed as a lighter
"prefer not to run it yourself?" alternative — deliberately low-pressure
for the DIY self-host audience.

_screenshot:_
<img width="1437" height="815" alt="Screenshot 2026-07-10 at 14 33 39"
src="https://github.com/user-attachments/assets/a37207cd-2aaa-4aba-848d-cbf06a1e1321"
/>

## Notes for reviewers

- Page-selection rationale: intent × volume. Kept the four intent-tail
pages from #22719; added the highest-traffic pages that also carry a
natural partner-buying moment (self-host → Hosting; workflows /
data-model → Solutioning). Intros/quickstarts/contribute pages
intentionally left untouched.
- **Attribution caveat (unchanged from #22719):** twenty.com's analytics
(Cloudflare Web Analytics) is path-based, so `?ref=` is not measurable
yet. Per-page measurement via a `/go/*` redirect Worker remains a
planned, separate follow-up (out of scope here).
- `mintlify validate` passes.

Opened as a draft.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22808?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-10 14:39:31 +02:00
github-actions[bot] 55768cfe81 i18n - translations (#22809)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22809?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 14:36:06 +02:00
github-actions[bot] e9dc4d8e89 i18n - translations (#22807)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22807?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 14:29:33 +02:00
github-actions[bot] 07c88aed45 i18n - translations (#22806)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22806?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 14:22:05 +02:00
github-actions[bot] 39847c8574 i18n - translations (#22804)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 14:14:54 +02:00
github-actions[bot] a8a7356a42 i18n - translations (#22802)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22802?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 14:07:53 +02:00
github-actions[bot] 75ea4fb027 i18n - translations (#22799)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22799?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-07-10 14:00:49 +02:00
Abdul Rahman ffdda50afc remove nestjs-query auto-resolver from index-metadata (#22775)
## Summary

Removes the `NestjsQueryGraphQLModule.forFeature` block from
`IndexMetadataModule`, continuing the incremental migration off
`@ptc-org/nestjs-query`.

- Drops the dead auto-generated read surface: `index` and
`indexMetadatas` queries, the `IndexConnection` /
`IndexObjectMetadataConnection` types, and the `Index.objectMetadata`
field. No client consumes these — the frontend reads indexes via
`ObjectMetadata.indexMetadatas`.
- Keeps `IndexMetadataDTO` nestjs-query-compatible (`@Authorize`,
`@FilterableField`, `@QueryOptions`, `@IDField`) because
`ObjectMetadataDTO` still references it via
`@CursorConnection('indexMetadatas')` until object-metadata is migrated.
- Hand-written `createOneIndex` / `deleteOneIndex` mutations and the
`indexFieldMetadataList` resolve-field are unchanged.
- Deletes the now-obsolete `index-metadatas` integration test and
regenerates the GraphQL schema artifacts (frontend + client-sdk).

## Breaking change

This is an intentional GraphQL schema breaking change
(`api-breaking-changes` CI will flag it)

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22775?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-10 14:00:23 +02:00
github-actions[bot] 04c195a54e i18n - translations (#22798)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22798?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-07-10 13:44:11 +02:00
Paul Rastoin 108e1654e7 Catch Cursor's "Made with" attribution footer in blocked contributors check (#22791)
## Summary

PR #22785 merged with a Cursor attribution (05afc49e) even though the
`check-blocked-contributors` job ran and passed. The PR's commits were
clean, but the description ended with Cursor's attribution footer, which
starts with "Made with" followed by a markdown link to cursor.com, while
the script only matched the "Generated with" wording. Since the repo
squash-merges, the PR description became the merged commit message,
carrying the footer (plus the co-author trailer GitHub appends at squash
time) into main's history.

This broadens the signature pattern in
`scripts/check-blocked-contributors.ts` to also cover the "Made"/"Built"
wordings.

Note: this description intentionally avoids quoting the banned strings
verbatim, since the check scans it too (and it would become the squash
commit message).

## Test plan

Verified the new pattern against:
- The exact footer from the PR #22785 description - now caught
- The co-author trailer from the merged squash commit - caught by
existing patterns
- The "Generated with" and "Built with" wordings, with and without the
"Agent" suffix in the link label - caught
- Negative cases: prose mentioning the word cursor and a plain markdown
link to cursor.com - not flagged
2026-07-10 11:42:59 +00:00
github-actions[bot] 27e101599a i18n - translations (#22796)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-07-10 13:30:12 +02:00
Abdul Rahman d23511ea5e remove nestjs-query from user and workspace resolvers (#22766)
## What

Migrates the `user` and `workspace` core modules off
`@ptc-org/nestjs-query`. Both used `NestjsQueryGraphQLModule` only as
scaffolding — all CRUD was disabled and the real GraphQL API is already
served by the hand-written `UserResolver` / `WorkspaceResolver`.



<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22766?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->

---------

Co-authored-by: Weiko <corentin@twenty.com>
2026-07-10 16:59:58 +05:30
Paul Rastoin ace16add6c fix(twenty-partners): align createdAt view field with 2.19 deterministic ids and bump to 1.2.10 (#22782)
## What

- Bump `twenty-partners` from `1.2.0` to `1.2.10`.
- Fix the red integration tests by pointing the "Partner Applications"
view `createdAt` column at the real field metadata id.

## Why the integration tests were red

The `twenty-partners` CI job spins up `twentycrm/twenty-app-dev:latest`
and runs the integration suite. The suite's global setup does a dev sync
of the app, which failed:

```
Dev sync failed: viewField: INVALID_VIEW_DATA: Field metadata not found
(universalIdentifier: 835c9a7e-72ec-46c5-8d90-39a02998f561)
```

The `partner-applications.view.ts` `createdAt` column referenced
`PARTNER_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER = 421cbcea-...`, an
invented id. `createdAt` is a reserved system field auto-created on the
custom `partner` object, and since 2.19 its universal identifier is
derived deterministically by the server from the application id, the
object id and the field name. The invented id matched nothing, so the
sync rejected the dangling view field and the app never registered,
failing every integration test.

## Fix

Set `PARTNER_CREATED_AT_FIELD_UNIVERSAL_IDENTIFIER` to the
deterministically derived value `746e2944-28d0-545e-9832-a46516e1d9a0`
(application id `e662fc1f-...` + partner object id `39101b39-...` +
field name `createdAt`). This matches the same derivation the server
uses for standard object system fields, verified against the
`twenty-shared` opportunity `createdAt` snapshot.
2026-07-10 13:27:21 +02:00