caed92b1d468b4f0f168db92fcfa9f3bf716d2f5
13587 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
caed92b1d4 |
i18n - translations (#22813)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
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. |
||
|
|
797acff5fa |
i18n - translations (#22812)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
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. -->
|
||
|
|
dcb67fbbe1 |
i18n - translations (#22810)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
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. --> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
39847c8574 |
i18n - translations (#22804)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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. -->
|
||
|
|
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> |
||
|
|
108e1654e7 |
Catch Cursor's "Made with" attribution footer in blocked contributors check (#22791)
## Summary
PR #22785 merged with a Cursor attribution (
|
||
|
|
27e101599a |
i18n - translations (#22796)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
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> |
||
|
|
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. |
||
|
|
48252d35e9 |
i18n - docs translations (#22794)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22794?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> |
||
|
|
5499688861 |
i18n - translations (#22793)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22793?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> |
||
|
|
b682162f31 |
fix(front): render +N button for right-edge-clipped chips in expandable list (#22748)
## Problem Fixes #22383. Overflowing relation & multi-select chip cells didn't show the "+N" overflow button, so hidden records/values were unreachable. There were several distinct causes behind this, addressed below. Before <img width="263" height="34" alt="Capture d’écran 2026-07-10 à 11 43 13" src="https://github.com/user-attachments/assets/1001d47b-0f54-4fbd-a5cb-83a5c32c35ac" /> After <img width="263" height="34" alt="Capture d’écran 2026-07-10 à 11 41 54" src="https://github.com/user-attachments/assets/d4f5cf2f-fda4-422e-b72d-2df3fc81a06f" /> ## Changes **1. Overflow detection missed right-edge-clipped chips** `isFirstOverflowingChildElement` used `childElement.offsetLeft > containerElement.clientWidth` (left edge past the container), which is false when a chip is only right-edge clipped. Switched to a right-edge check: `childElement.offsetLeft + childElement.offsetWidth > containerElement.clientWidth`. **2. Multi-select never used the overflow list when unfocused** `MultiSelectFieldDisplay` rendered a plain clipped `MultiSelectDisplay` when not focused, and only used `ExpandableList` on focus. That non-focused fallback painted over the focused "+N" and hid it. It now always renders through `ExpandableList` with `isChipCountDisplayed={isFocused}`, matching `RelationFromManyFieldDisplay`, so the count shows on focus only (not idle). **3. The hover portal was never actually focused** `FieldFocusContextProvider` silently ignored its `isFocused` prop (`({ children }: any)` + hard-coded `useState(false)`), so `RecordInlineCellAnchoredPortal`'s `<FieldFocusContextProvider isFocused={true}>` had no effect and the hovered cell's display always saw `isFocused=false`. That's why "+N" never appeared on hover for any multi-value field. The portal now uses the existing `FieldFocusStaticFocusedProvider`, fulfilling the intent — so relations, multi-select, emails and phones all surface their "+N" on hover. **4. "+N" was hard to read on multi-select** The "+N" chip has a transparent background (shared component), and the hover portal let the base layer's colored option chips bleed through it. Gave the hover portal content an opaque `background.primary` so nothing bleeds through; the "+N" component itself is untouched, so relations/emails/phones keep their existing look. ## Test plan - Hover a relation or multi-select field whose values overflow the cell: the "+N" button appears (and is readable), and clicking it lists the hidden records. When not hovered, no "+N" shows. - Verify fully-overflowing rows still show the correct "+N" count. |
||
|
|
f58683e33c |
i18n - translations (#22789)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
c0b404b3d1 |
i18n - translations (#22788)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22788?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> |
||
|
|
a487ed043b |
i18n - translations (#22787)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22787?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> |
||
|
|
6baa5a6257 |
i18n - translations (#22784)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22784?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> |
||
|
|
05afc49ea6 |
Fix front-component cache read rejection bypassing network fallback (#22785)
## Summary Quick follow-up to #22672. `frontComponentCacheStorageService.read` returned `cachedResponse.text()` without awaiting it, so the promise escaped the surrounding `try/catch`. A cache entry with an unreadable body (corrupt or partially-evicted `CacheStorage` entry) would reject in `fetchComponentSource` and break component rendering entirely, instead of being treated as a cache miss with a network fallback. - `await` the body read inside the `try/catch` so decoding failures degrade to a network fetch - Add a regression test: cached body read rejects → source is still served from the network ## Test plan - `fetchComponentSource.spec.ts` — new test `falls back to the network when the cached response body is unreadable`; full renderer suite passes (14 tests) Made with [Cursor](https://cursor.com) <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22785?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: Cursor <cursoragent@cursor.com> |
||
|
|
4ab36a630b |
remove nestjs-query from app-token and drop unused createOneAppToken mutation (#22765)
## What Migrates `app-token` off `@ptc-org/nestjs-query` and removes the auto-generated `createOneAppToken` mutation, which was unused dead API surface. ## Why The `createOneAppToken` mutation was reachable only from the schema — no frontend query, SDK caller, or test used it. It was also non-functional (its input couldn't set the token `value`), a leftover from nestjs-query's default `create.one` being left enabled. Real app tokens (refresh, password-reset, email-verification, invitation, OAuth, enterprise) are all created directly via the repository in ~14 services, none of which touched this mutation. ## Notes - ⚠️ This removes `AppToken`, `createOneAppToken`, `CreateAppTokenInput`, and `CreateOneAppTokenInput` from the `/metadata` schema, so the **api-breaking-changes check will flag it** <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22765?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> |
||
|
|
9e20e2222a |
Fix front-component serving on Safari, kill stale presigned caching, and cache built bundles client-side (#22672)
## Context Built front-component bundles are served via `GET /rest/front-components/:id/:cacheKey`. On S3-backed storage (Twenty Cloud) the endpoint used to 302-redirect the worker's authenticated fetch to a presigned S3 URL. That redirect caused two bugs, and fixing it removed the caching the redirect was accidentally providing — so this PR also adds a proper client-side cache. Closes twentyhq/core-team-issues#2653. ### Bug 1 — Safari 403 (Authorization header forwarded across redirect) The renderer worker fetches the bundle with `Authorization: Bearer`. The controller answered with a 302 to a presigned S3 URL. Per the Fetch spec, browsers must strip `Authorization` on a cross-origin redirect. Chrome/Firefox do, but Safari/WebKit forwards it, so S3 receives both a query-string signature and an `Authorization` header and rejects with `InvalidArgument: Only one auth mechanism allowed`. Result: front components never load in Safari on S3-backed storage. ### Bug 2 — 302 cached publicly (browser-independent) The redirect branch set no `Cache-Control`, so a CDN could cache it far beyond the presigned URL's TTL (`STORAGE_S3_PRESIGNED_URL_EXPIRES_IN`, 900s). Consequences: any client re-served the cached 302 after 15 min hits an expired signature (403, also affects Chrome), and the cached redirect containing a live presigned URL is served to unauthenticated requests (short-lived auth bypass). ### Regression this introduces — warm-load caching lost Marking the handoff `no-store` (Bug 2 fix) is correct, but it means the built bundle is no longer cached anywhere on the S3 path. The browser HTTP cache cannot compensate: the presigned URL that actually returns the bytes carries a fresh `X-Amz-Date`/`X-Amz-Signature` on every request, so each download is a brand-new cache key and never hits. Net effect without mitigation: every worker mount re-downloads the full bundle. ## What changed - **Front components return a 200 JSON body instead of a 302.** The controller now responds `200 { url }` with `Cache-Control: private, no-store`. The worker parses the JSON and issues a separate header-less `fetch(url)` to S3. No redirect means the `Authorization` header is never forwarded, making it browser-independent, and the handoff carrying the presigned URL is never cached. The stream path (local storage) is unchanged. - **Client-side bundle cache in the renderer (restores warm loads).** `fetchComponentSource` wraps the fetch chain in a `CacheStorage` layer keyed by the **content-addressed** `/front-components/:id/:checksum.js` URL. A hit returns the stored bundle and skips **both** the `no-store` handoff to Twenty and the S3 download — restoring cross-session warm loads without ever persisting a presigned credential. Because `CacheStorage` is writable by any same-origin code (including the untrusted component code this cache feeds), cached content is verified against the sha-256 checksum embedded in the URL on every read, and evicted on mismatch. Caching degrades to a plain fetch where `CacheStorage` or WebCrypto is unavailable. - **sha-256 checksums for built front components.** The SDK build and workspace prefill now fingerprint built front-component bundles with sha-256 (WebCrypto has no md5), enabling the integrity check above. Other file folders keep md5. Legacy md5-fingerprinted URLs (32-hex) simply bypass the cache — already-synced components keep working and start benefiting from caching on their next build/sync. - **WebKit e2e coverage.** Added a `webkit` project to the postcard example's Playwright config mirroring `chrome` (shared setup + storageState), plus iframe/worker diagnostics logging so front-component failures surface in the test log. `TZ` is pinned to `Europe/Paris` because WebKit on Linux ignores Playwright's `timezoneId` emulation and rejects the runner's legacy `CET` alias, which crashed the record page before the component could render. ### Why we hand off to S3 instead of streaming through Twenty On S3-backed storage we deliberately **do not** proxy/stream the bundle bytes through the API. The controller returns the presigned URL and the worker fetches the content directly from S3, for two reasons: - **Server CPU/bandwidth.** Streaming every bundle on every cold load would put the API server on the hot path for all front-component content. Handing off to S3 keeps that load off the server. - **Domain isolation.** Front-component content is fetched from the object-storage domain (e.g. `s3.domain.com`), a different origin than the API and the front app. Serving untrusted/app-authored bundle content from a separate domain than `twenty.com` keeps it off the app's origin. The stream path is kept only as the local-storage fallback (no S3/presign available), where these concerns don't apply. ## Examples ### The JSON handoff (S3 path) ```http GET /rest/front-components/d3b07384-.../a1b2c3d4.js HTTP/1.1 Host: twenty.com Authorization: Bearer <worker-token> ``` ```http HTTP/1.1 200 OK Content-Type: application/json Cache-Control: private, no-store {"url":"https://s3.domain.com/bucket/.../checkout-widget.mjs?X-Amz-Date=20260709T091500Z&X-Amz-Expires=900&...&X-Amz-Signature=AAAA1111..."} ``` The worker then fetches that presigned URL **without** headers (the Safari fix) and gets the bundle bytes. ### Why the browser HTTP cache can't reuse it | | Load 1 (09:15) | Load 2 (09:30) | Same key? | |---|---|---|---| | Twenty handoff URL | `.../a1b2c3d4.js` | `.../a1b2c3d4.js` | ✅ but response is `no-store` | | Presigned `X-Amz-Signature` | `AAAA1111...` | `ZZZZ9999...` | ❌ | | Effective S3 URL (the HTTP cache key) | `...&X-Amz-Signature=AAAA1111...` | `...&X-Amz-Signature=ZZZZ9999...` | ❌ new key → miss | ### What the CacheStorage layer stores ``` key = https://twenty.com/rest/front-components/d3b07384-.../a1b2c3d4.js (stable, chosen by us) value = <bundle JS bytes> (NOT the presigned URL) ``` Keying by the stable logical URL (not the volatile URL the bytes arrived from) is the one thing the native HTTP cache can't express. The presigned URL is used once and discarded. ### Invalidation No TTL and no explicit delete — invalidation is by key change. A rebuild changes the checksum → changes the URL → guaranteed miss on the new key. The old entry is orphaned and reclaimed by normal browser eviction (quota/LRU; Safari ITP after 7 idle days). Global invalidation lever: bump the cache name suffix (`front-component-source-v1`). ## Deploy note — front/server release window Old frontend bundles (already-open tabs) hitting the new server receive the JSON handoff where they expect raw JS and fail to render until the tab is reloaded. The other direction is safe: the new worker against an old server follows the 302 transparently (the content-type check falls through to `response.text()`). Accepted as a short deploy-window trade-off. ## Follow-ups (not in this PR) - The client-side cache is a bridge for the `no-store` presigned handoff. If built components are later served from a stable, non-signed, public-by-URL path (they are already content-addressed by checksum, so `immutable` is safe), the browser + CDN cache natively and this custom layer can be removed. - `GET /file/:fileFolder/:id` presigned 302s still carry no `Cache-Control`. An explicit policy there (bounded `private, max-age` below the presigned TTL) was prototyped in this PR and deliberately dropped to keep the scope on front components — the file path authenticates via a query-param token (part of any cache key), so its exposure differs and deserves its own PR. ## Non-goals Per the issue, file serving keeps its query-param token + 302 model. Native browser loads (`<img>`, downloads) cannot do a two-step fetch and already work on Safari. The public-asset redirect is left untouched since its caching is intentional. ## Test plan - Renderer: `fetchComponentSource.spec.ts` covers cache miss + write, verified cache hit (no network), poisoned-entry eviction, checksum-mismatch (never cached), non-fingerprinted and legacy-md5 URL bypass, and the no-`CacheStorage` / no-WebCrypto fallbacks. `fetchComponentSourceFromNetwork.spec.ts` covers the direct JS response, the JSON handoff follow-through (header-less presigned fetch), and error mapping. - e2e: the postcard front-component spec now runs on both Chromium and WebKit against prod-parity storage (S3 + Lambda). - `oxlint` + `oxfmt` clean; typecheck passes on changed packages. ### Reproduction proof — Safari was always broken (e2e probe) We ran the prod-parity postcard e2e suite (S3 storage + Lambda) with WebKit against **`main` without this fix**, via a throwaway probe PR: twentyhq/twenty#22717. Result — [ci-privileged run 29015624468](https://github.com/twentyhq/ci-privileged/actions/runs/29015624468): ``` 1 failed [webkit] › card-front-component.spec.ts:61 › renders the postcard name and status badge in the record preview 2 passed (1.4m) ``` `[webkit]` times out waiting for `getByTestId('postcard-card')` to become visible (*element(s) not found*) while the Chromium run of the same spec passes. This confirms the front component **never rendered in Safari** on S3-backed storage prior to this PR — it is a genuine, browser-specific bug, not a flake. The fix in this PR is expected to turn that same `[webkit]` assertion green. Note: running the WebKit tests in CI requires the WebKit browser binary and its system dependencies in the e2e job (now installed via `npx playwright install --with-deps chromium webkit`). |
||
|
|
d87e762bf9 |
i18n - translations (#22781)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22781?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> |
||
|
|
1c8b8970fd | Allow CLI dev mode on catalog-synced apps without mutating the shared registration (#22756) | ||
|
|
f8d3555fe7 |
i18n - translations (#22779)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22779?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> |
||
|
|
09496a0f98 |
i18n - translations (#22745)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22745?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> |
||
|
|
bd349b1544 |
i18n - docs translations (#22777)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22777?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> |
||
|
|
f547da4ee9 |
Gate connected-account webhook subscriptions behind config (#22761)
Now gated behind IS_CONNECTED_ACCOUNT_WEBHOOK_SUBSCRIPTION_ENABLED (default false). Merge + deploy twentyhq/twenty-infra#780 first. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22761?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 <charlesBochet@users.noreply.github.com> |
||
|
|
a360f9bdec |
migrate 10 modules off NestjsQueryTypeOrmModule wiring (#22763)
## Summary Continues the incremental removal of `@ptc-org/nestjs-query`. Migrates ten modules from `NestjsQueryTypeOrmModule.forFeature` to the standard `TypeOrmModule.forFeature`. These modules only used `nestjs-query` for repository registration — none register `NestjsQueryGraphQLModule` / auto-generated resolvers — so this is a pure module-wiring swap with no behavior or schema change. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22763?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> |
||
|
|
23cae2040a |
Improve application asset management (#22564)
App manifests could point the logo and screenshots at either external
URLs or public folder paths, and that was handled inconsistently across
install, sync and the marketplace.
This makes assets always bundled files:
- Manifests now use `logo` and `galleryImages` (a `string[]` of public
folder paths) instead of `logoUrl` and `screenshots`. The old fields
still work but are deprecated. Gallery order comes from the array index.
Normalization (deprecated-field migration, and warning about + ignoring
external URLs) happens in `defineApplication`, so the warnings surface
at define time.
- Logo is stored as a File record (`logoFileId`).
- The registration gallery is configured via a `settings` jsonb column
on `applicationRegistration` (`{ galleryImages: string[] }`) — populated
from the manifest, read by the marketplace detail (falling back to the
legacy `screenshots` column, then the manifest). No dedicated gallery
table.
- The marketplace detail DTO and front now use `galleryImages`.
Verified against a local Postgres: the fast instance commands run with
no pending-migration diff, the schema is correct, and the server boots.
Typecheck, lint, codegen and the application unit tests pass.
Not included yet: rehosting assets into storage for npm catalog and
tarball registrations, versioned cache busting on the serving route, and
a backfill for existing installs.
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22564?utm_source=github"
rel="nofollow noreferrer noopener" target="_blank">``<img alt="Review
in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg">``</a>
|
||
|
|
4cce9b1544 | Fix integration test hang on unmocked requests + de-flake object metadata suite (#22758) | ||
|
|
b327ab09a7 |
feat(workflow): add universalIdentifier + applicationId to core workflowVersion (#22747)
## workflowVersion core: syncable columns Adds `universalIdentifier` + `applicationId` (nullable) to `core.workflowVersion`, plus the FK to `core.application` and the `(workspaceId, universalIdentifier)` unique index, via a 2.20 add-columns fast command gated with `@WasIntroducedInUpgrade`. Nullable for now: the already-merged Phase A backfill (#22663) inserts version rows without these columns, so `applicationId` can't be NOT NULL yet. Flipping to NOT NULL + `extends SyncableEntity` comes once they're populated (backfill + dual-write follow-ups). Schema captured and verified via `migrate:generate` (zero drift). Independent of the core-workflow PR, but both add 2.20 upgrade commands, so this one (ts `…480`) must merge **after** the core-workflow PR (ts `…479`), or it gets re-timestamped on rebase. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22747?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. --> |
||
|
|
0786f9e793 |
Seed CHANGELOG.md and SETUP.md in create-twenty-app scaffold (#22769)
Projects scaffolded with `create-twenty-app` now include two additional seed files: - `CHANGELOG.md` with an initial `0.1.0` entry matching the template's package version - `SETUP.md` with step-by-step local setup instructions (prerequisites, install, local server, dev sync, verification commands) Both files live in `src/constants/template/`, so they flow through the existing `fs.copy` scaffolding and the vite `copy-assets` build step with no code changes. Verified `dist/constants/template/` contains both files after `nx build create-twenty-app`. The scaffolded `README.md` was also simplified into a marketable front page for the app being built: a pitch placeholder, a features section, and links to `SETUP.md` for setup instructions and `CHANGELOG.md` for history, instead of duplicating dev commands. Also: - Adds a regression test asserting the template directory contains both seed files - Updates `project-structure.mdx` docs to list the new files in the scaffold directory tree --------- Co-authored-by: Martin <martin@twenty.com> |
||
|
|
1fe6156cf5 |
fix(twenty-sdk): restore require/__filename/__dirname in rolldown ESM CLI bundle (#22711)
## Problem Fixes #22708. `twenty-sdk` 2.19.0 CLI commands run through the ESM entrypoint (`node dist/cli.mjs dev --once`, `app:uninstall`, ...) crash on startup: ``` Error: Calling `require` for "fs" in an environment that doesn't expose the `require` function. ``` ## Root cause The 2.19.0 release switched bundling from esbuild to **rolldown** (Vite 7 → 8). Rolldown **inlines CommonJS dependencies** into the ESM output (`dist/cli.mjs` grows from ~6k to ~136k lines). Those third-party CJS modules — e.g. `typescript`, pulled in via `ts-morph` — call `require(...)` and read `__filename` / `__dirname` at load time. None of those exist in an ES module: - `require(...)` is routed through rolldown's interop shim, which **throws** when `require` is absent (i.e. in a `.mjs` file). - `__filename` / `__dirname` are simply `ReferenceError: … is not defined in ES module scope`. esbuild (2.18.0) injected these CJS globals for node-targeted ESM output; rolldown does not. Because the offending usage lives in **bundled third-party CJS**, prefixing our own imports could not fix it. ## Fix Add a banner to the **ESM output only** in `vite.config.node.ts` that recreates the CJS globals from `import.meta.url`: ```js import { createRequire as __twentyCreateRequire } from 'node:module'; import { fileURLToPath as __twentyFileURLToPath } from 'node:url'; import { dirname as __twentyDirname } from 'node:path'; const require = __twentyCreateRequire(import.meta.url); const __filename = __twentyFileURLToPath(import.meta.url); const __dirname = __twentyDirname(__filename); ``` This is the same `createRequire` pattern the repo already uses for the `twenty-oxlint-rules` ESM build. The CJS output already provides all three, so the banner is not applied there. This PR also prefixes the SDK CLI's own Node-builtin imports with `node:` (using native ESM imports instead of the interop shim for our own code) — good hygiene and guarded by a unit test, but note the **banner is the actual bug fix**. ## Verification (built and run locally) - Built the node bundle and reproduced the crash on the pre-fix build (`Calling require for "fs"`), then a follow-on `__filename is not defined` once `require` was restored. - With the banner, ran the previously-crashing commands against the built `dist/cli.mjs`: - `--help` → prints usage, exit 0 - `dev --once` → reaches "Checking server… Cannot reach Twenty server" (normal, no local server) - `app:uninstall` → reaches the interactive confirmation prompt - CJS bin (`dist/cli.cjs`) still works. ## Tests - `cli-esm-bundle-startup.integration.spec.ts` — runs the **built** `dist/cli.mjs --help` and asserts no require/ESM-scope crash. Demonstrated **red without the banner, green with it**. It runs in the `sdk-test` job (which builds the SDK before tests); it fails loudly in CI if the artifact is missing and skips locally when unbuilt, so it is never silently green in CI. - `node-builtin-import-protocol.test.ts` — guards the `node:`-prefix hygiene across the CLI source. Note: the existing `sdk-e2e-test` never caught this because it runs the CLI via `tsx` on the TypeScript **source**, which has no rolldown shim — only the bundled `.mjs` reproduces the crash. |
||
|
|
d430e38a9a |
Fix shard 12 integration test failure: regenerate stale object metadata snapshot from #22594 (#22768)
# Context Since #22594 merged (July 9, 16:59 CET), `server-integration-test (12)` fails deterministically on every main-based branch. The failure is easy to misread as flakiness because nx truncates the failed task's replayed output before jest's `FAIL`/summary lines reach the CI log; the visible `timelineActivity` FK-violation errors are pre-existing noise also present in green runs. Note: the flakiness PRs merged yesterday morning (#22699, #22701, #22702) are not the cause. The failure window starts with #22594. # Root cause #22594 moved `searchVector` provisioning out of the reserved-system-fields path into its own side-effect handler, registered after `ObjectSystemFieldsOnCreateSideEffectHandlerService` in `metadata-side-effect-handlers.module.ts`. The `searchVector` field entry therefore now appears after `updatedAt`/`updatedBy` in the create-object validation report. The snapshot for `failing-create-one-object-metadata-v2.integration-spec.ts` was rewritten in #22594 but kept the old `position -> searchVector -> updatedAt` ordering, so 15 of the 19 tests fail on a snapshot mismatch. Reproduced locally on latest main: 15/19 fail, diff is exactly the three reordered name lines. # Fix Regenerated the snapshot with `jest -u` against a freshly reset database. Suite is 19/19 green afterwards. The sibling snapshot suites in the same directory (`failing-update-one-object-metadata`, `failing-update-one-standard-object-metadata`, `successful-update-one-standard-object-metadata`) were re-run and pass unchanged. Pure block move: only the `searchVector`/`updatedAt`/`updatedBy` name lines relocate, 15 occurrences each (45+/45-), one file, no product code. # Related - #22757 is an earlier draft with the same snapshot regeneration. - #22758 fixes the same suite by replacing the snapshot with per-case contract assertions (plus an msw catch-all). If that one merges first, this PR becomes unnecessary. --- _Generated by [Claude Code](https://claude.ai/code/session_01NPSkHDBHNQw4c1iJzFTxoA)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22768?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: Martin <martin@twenty.com> |
||
|
|
25d7758049 |
chore: sync AI model catalog from models.dev (#22770)
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/22770?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> |
||
|
|
a35aa6ce7f |
i18n - docs translations (#22764)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22764?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> |
||
|
|
09a48f56c5 |
i18n - docs translations (#22762)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22762?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> |
||
|
|
751f18ee9d |
i18n - docs translations (#22759)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22759?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> |
||
|
|
d8082d86ca |
Add Last contact on Companies and Opportunities to last-contact app (#22720)
## After Example with google company, 2 contacts, 2 opportunities: <img width="1512" height="332" alt="image" src="https://github.com/user-attachments/assets/9dac3ac1-bbbb-41a4-b6d6-5f2180e33c13"/> <img width="1512" height="313" alt="image" src="https://github.com/user-attachments/assets/e6e326db-942f-45c2-a696-05fa4c32619b"/> <img width="1309" height="313" alt="image" src="https://github.com/user-attachments/assets/64ed84ab-f7f0-47e5-b48c-42572c7d2534"/> ## What Extends the `twenty-last-contact` app so **Last contact** is also surfaced on **Companies** and **Opportunities**, not just People. Feedback: Companies and Opportunities already show emails and meetings from their related Person records on their timeline, so they should expose the most recent touch as fields too. Terminology and mechanism intentionally mirror what the app already does on People (no email-specific fields). ## Changes - **New fields**, identical in name/label/semantics to the People headline columns: - `Company.lastContactAt` and `Opportunity.lastContactAt` (datetime, "Last contact") - `Company.lastContactItemMessage` / `lastContactItemCalendarEvent` and the same pair on Opportunity (morph relation, "Last contact item"), with inverse `lastContactForCompanies` / `lastContactForOpportunities` relations on Message and Calendar event - All app fields are read-only in the UI (`isUIEditable: false`) - **View fields** on the All Companies and All Opportunities views (Last contact + Last contact item, visible). - **Live updates**: the new `updateRelatedLastContact` util propagates a person's interaction to their company and their point-of-contact opportunities, guarded so an older interaction never overwrites a newer one. It is called from both the email handler (`on-email-interaction`) and the shared calendar path (`updatePersonLastContactFromCalendar`, used by `on-calendar-interaction` and `on-calendar-event-started`), so emails and meetings both count. - **Backfill** (`backfill-last-contact`): aggregates each person's last contact up to their company, and each opportunity's from its point of contact. Related-record scope: a company's last contact comes from its people; an opportunity's from its point of contact. ## Not included (deliberately) The directional fields (`lastInboundAt`, `lastOutboundAt`, `lastContactBy`) and the `lastEmail`/`lastMeeting` shortcuts are not mirrored: aggregated across many people they get semantically fuzzy, and they would double the write amplification on every synced email for little added signal. ## Tests - Unit tests for `updateRelatedLastContact` (email and meeting propagation, recency guard, no-company case). - Integration tests: a related person's email sets company + opportunity last contact; a later meeting supersedes an email; an older interaction does not overwrite a newer one. - Existing unit + integration tests updated and passing; typecheck and lint clean. |
||
|
|
6210389221 |
feat(workflow): add core workflow entity (syncable) + create-table (#22746)
## Core `workflow` entity (syncable) Part of the app-workflows work. Adds a core `WorkflowEntity extends SyncableEntity` (`name`, `lastPublishedVersionId`, plus `universalIdentifier`/`applicationId`/workspace from the base class) and its 2.20 create-table fast command, gated with `@WasIntroducedInUpgrade`. Schema was captured and verified via `migrate:generate` (zero drift, FK/index hashes correct), then run against a live DB. Backfill (populate from workspace `workflow` records) and the dual-write listener land in follow-ups. Independent of the version-syncable-columns PR, but note: both add 2.20 upgrade commands, so this one (ts `…479`) must merge **before** the version PR (ts `…480`) to satisfy the append-only guard. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22746?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. --> |
||
|
|
9c405384f3 |
Only propose configured apps during onboarding install step (#22712)
## What - Onboarding "Install your first apps" now proposes only apps that are actually installable: it intersects the onboarding list with `findManyMarketplaceApps`, which the backend already filters to listed + configured apps (all required server variables set). - If none are available, the step auto-skips. If the marketplace query fails, it shows an intentional fallback (heading + Skip) instead of silently skipping or rendering an empty install card. - `findManyMarketplaceApps` now accepts `universalIdentifiers`, so onboarding fetches and configuration-checks only its own apps instead of the entire catalog. ## Why Previously the step rendered all hardcoded apps regardless of configuration, only borrowing logos from the marketplace, so a user could be offered an app the admin never configured. This centralizes onboarding availability on the marketplace's existing logic and keeps the query bounded as the marketplace grows. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22712?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. --> |
||
|
|
b9cd62f396 |
i18n - docs translations (#22752)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22752?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> |
||
|
|
e9a030f762 |
fix(server): allow relabelling onto a field introduced in the same manifest sync (#22727)
## Context Closes twentyhq/core-team-issues#2655. `computeOrderedMigrationActions` runs `objectMetadata.update` **before** `fieldMetadata.create`. In a single manifest sync that both introduces a new field and relabels the object's `labelIdentifierFieldMetadataUniversalIdentifier` onto that field, the object update handler resolved the label identifier's universal identifier against the persisted `flatFieldMetadataMaps` only. Since the field's `fieldMetadata.create` runs later in the same migration, the field isn't in the maps yet and the sync failed with `ENTITY_NOT_FOUND`. The API metadata path is unaffected because create-field and update-object are separate requests (separate transactions), so the field is already persisted by the time the object update resolves. ## What this does `update-object-action-handler.service.ts` now resolves `labelIdentifierFieldMetadataId` and `imageIdentifierFieldMetadataId` against the deterministically preallocated field ids first, then falls back to the persisted flat maps for fields that already exist. The preallocated ids (`preallocatedIdByUniversalIdentifierByMetadataName`) are built from every create action before the migration loop starts (`buildPreallocatedIdByUniversalIdentifierFromActions`) and are the same ids `create-field-action-handler` persists the fields with. This is the same "preallocated-first, then flat maps" resolution that `resolveUniversalRelationIdentifiersToIds` already uses for modeled many-to-one relations, so no ordering change or new machinery is needed, and the single sync stays one atomic transaction. This does not touch the underlying action ordering or the hand-rolled label/image identifier handling flagged by the `#2172` TODO; generalizing those into the relation config remains the follow-up. ## Test Adds `relabel-onto-new-field-manifest-sync.integration-spec.ts` (used as the TDD reproduction, now green): - introducing a field and relabelling onto it in a single sync succeeds, and the object's `labelIdentifierFieldMetadataId` points at the new field; - the split path (introduce in one sync, relabel in the next) still succeeds and exposes the enriched `labelIdentifierFieldMetadataId` through the metadata API. Both cases pass against the fix. `oxlint`, `oxfmt`, and `typecheck` are clean. --- _Generated by [Claude Code](https://claude.ai/code/session_01KJewXMuWYUyrX3YJh2JBDE)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22727?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. --> |