de610bc4e7d30326aa94dfa33e66ec4dfcce2a22
13024 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
de610bc4e7 |
Rename CI New UI workflow to CI UI (#22030)
Renames the `CI New UI` workflow to `CI UI`, dropping the "new ui" terminology everywhere it appeared. - Renamed `.github/workflows/ci-new-ui.yaml` → `ci-ui.yaml` - Updated the workflow `name`, job names (`ui-task`, `ui-sb-build`, `ui-sb-test`, `ci-ui-status-check`), and internal `needs`/`if` references - Updated `visual-regression-dispatch.yaml` which keys off the workflow name (`CI UI`) Note: the required status check in branch protection settings (workflow name / `ci-ui-status-check`) lives in repo settings and will need updating by an admin so PRs don't wait on the old check name. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22030?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. --> |
||
|
|
855664daa2 |
feat(timeline): activity kind registry (Layer A) (#21950)
## What & why
The timeline-activity system's contract is a magic `name` string
(`"company.updated"`, `"linked-note.created"`, `"message.linked"`)
decoded by `String.split('.')` in **four** different frontend spots and
produced by a hardcoded `if`-ladder + two listeners. It is not
extensible and it already harbored a latent bug.
This PR replaces that stringly-typed protocol with an explicit,
persisted **`kind`** contract consumed through registries on both ends.
Adding a new timeline activity type becomes: add a producer + register a
presenter — no edits to a central switch.
This is **Layer A** of a larger plan (see
`packages/twenty-server/docs/TIMELINE_ACTIVITIES_REFACTOR.md` and
`TIMELINE_ACTIVITIES_PR_A.md`). Layer B (timeline projection /
"inheritance") and Layer C (user-defined aggregation rules) are
intentionally **out of scope** here.
## 🐛 Bug fixed along the way
`calendar-event-participant.listener.ts` was writing calendar-event
timeline rows with `name: 'message.linked'` (copy-paste from the message
listener). It rendered "correctly" only by luck — the frontend routed on
`linkedObjectMetadataId → nameSingular`, never on `name`. This PR fixes
it at the source (`calendarEvent.linked` / `kind:
'linkedCalendarEvent'`), and the shared resolver also corrects
historical rows that carry the wrong `name`.
## Changes
**`twenty-shared`** — new `timeline` module
- `TimelineActivityKind` (`recordChange | linkedNote | linkedTask |
linkedMessage | linkedCalendarEvent | linkedRecord`) +
`resolveTimelineActivityDescriptor`, the **single** place that decodes
an activity into `{ kind, action }`. Reads the persisted `kind` when
present and falls back to legacy `name`/`linkedObjectMetadataId` parsing
(back-compat shim). Unit-tested (20 cases).
**`twenty-server`**
- Persist a nullable `kind` field on the `timelineActivity` standard
object (entity shape + field-metadata builder + universalIdentifier).
- Producers (`timeline-activity.service.ts`, the two participant
listeners) set `kind` explicitly; dev seeder populates it.
- Fix the `calendarEvent.linked` mislabel.
**`twenty-front`**
- Static `TIMELINE_ACTIVITY_PRESENTERS` registry replaces the render
`switch`, the icon `if`-chain, the diff-validation name-parsing, and the
`name.match(/note|task/i)` title-prefetch hack.
- New `EventRowGenericLinked` so an unknown linked object type renders a
real "linked a {object}" row instead of falling through to the wrong
(main-object) renderer.
## Migration / compatibility
- The `kind` column on this **workspace** standard object is created by
the normal workspace metadata sync — no hand-written migration. It is
**nullable**, so pre-upgrade rows degrade gracefully through the
resolver shim (they resolve correctly from `linkedObjectMetadataId` +
`name`). An optional backfill workspace command could populate `kind` on
old rows later; not required for correctness.
- No GraphQL breaking change — `kind` is additive, `name` is retained
for display/search.
## Test plan
- `twenty-shared` unit tests (resolver) ✅
- `typecheck` + `lint:diff-with-main` green on `twenty-front`,
`twenty-server`, `twenty-shared` ✅
- Reset + reseed a workspace: `kind` is populated for all seeded rows
(recordChange / linkedMessage / linkedNote / linkedTask /
linkedCalendarEvent) with no nulls ✅
- Manual end-to-end verification via Playwright on person / company
record timelines — screenshots in a follow-up comment.
Screenshots attesting the rendering (incl. the calendar fix) are posted
as a comment below.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01YRueWMo4UyaX2em8R2cdio)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21950?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. -->
|
||
|
|
5e8932001c |
Simplify Recall webhook logic function config (#22026)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22026?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. --> |
||
|
|
ceecf33555 |
fix(twenty-partners): restore partner side panel field visibility (#22024)
## Summary - Restores 17 partner profile fields in the `FIELDS_WIDGET` view backing the Partner record page side panel (My Profile, admin partner views). - Read-locks `validationStage` and `partnerTier` for the Partner role so admins still see them on the record page but partners do not on My Profile. - Renames legacy `profilePicture` label to "Profile Picture (legacy)" to distinguish from the new file field. - Bumps `twenty-partners` to **1.1.3** (already deployed to prod). ## Test plan - [ ] `yarn lint` in `packages/twenty-apps/internal/twenty-partners` — 0 errors - [ ] `yarn twenty dev --once` on a local workspace — sync succeeds - [ ] As admin: open a Partner record → side panel shows all profile fields including validationStage and partnerTier - [ ] As Partner role (My Profile): side panel shows profile fields but **not** validationStage or partnerTier - [ ] Upgrade path: install v1.1.3 on an existing workspace — view fields update in place <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22024?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. --> |
||
|
|
11218561d0 |
fix(deps): pin form-data under nx and zapier-platform-core to 4.0.6 (#22023)
## Summary Adds two scoped `resolutions` (`nx/form-data` + `zapier-platform-core/form-data` → `4.0.6`) forcing the lone vulnerable `form-data@4.0.5` up to the patched `4.0.6`, resolving [Dependabot alert #1506](https://github.com/twentyhq/twenty/security/dependabot/1506) — CVE-2026-12143 / GHSA-hmw2-7cc7-3qxx (CRLF injection via unescaped multipart field names/filenames, vulnerable `>=4.0.0 <4.0.6`). ## Why scoped resolutions (not a parent-bump) - `form-data@4.0.5` is pinned **exactly** by `nx@22.7.5` (root devDep) and `zapier-platform-core@19.0.0` (twenty-zapier) — and **both still pin 4.0.5 in their latest release**, so no parent upgrade carries the fix. - Every *other* form-data consumer already resolves `4.0.6` naturally via its `^4.0.x` range, so the two scoped pins simply **dedupe** nx/zapier's copy onto that existing 4.0.6 — no new copy introduced. - Scoped, not global, to match the existing `express/qs` + `@cypress/request/qs` two-entry pattern (only form-data 4.x is in the tree). ## Changes - `package.json`: the two `resolutions` entries **plus** a matching `"//resolutions"` doc entry (advisory, why-no-parent-bump, scope rationale, drop condition). ## Verification - `yarn install --immutable` passes. - No form-data in `[4.0.0, 4.0.6)` remains in `yarn.lock`. |
||
|
|
bab71afe54 |
fix(deps): bump vitest to 4 in twenty-meeting-bot (drops vulnerable esbuild) (#22025)
## Summary Bumps **vitest** `^3.1.1 → ^4.1.9` in `twenty-meeting-bot`, which lets **vite** resolve to **8.0.16** — and vite 8 dropped esbuild entirely (moved to rolldown). That **removes the vulnerable transitive `esbuild@0.27.7` outright**, resolving [Dependabot alert #1470](https://github.com/twentyhq/twenty/security/dependabot/1470) — GHSA-g7r4-m6w7-qqqr (esbuild dev-server arbitrary file read on Windows, `>=0.27.3 <0.28.1`). ## Why a parent-bump, not a resolution - The vulnerable esbuild came from `vite@7.3.5` (`esbuild ^0.27.0`, a `0.x` caret capped at `<0.28` — so `yarn up` couldn't reach the fix). - vite is gated by vitest's vite range: vitest **3.x** allows only `^5||^6||^7` (caps vite at 7 → esbuild 0.27); vitest **4.x** allows `^8`, and **vite 8 has no esbuild dependency at all**. - So bumping vitest lets vite resolve to 8, which **eliminates the vulnerable dependency entirely** — no `resolutions` entry to force or maintain. (Matches the repo's stated preference: fix by upgrading the parent, not by resolution.) ## Verification - `yarn install` — vite resolves to `8.0.16`; all `@esbuild/*@0.27.7` platform packages pruned; the only esbuild left is `0.28.1` (already-fixed, from another consumer). - `yarn typecheck` — passes. - `yarn test:unit` — **202 tests / 30 files pass** under vitest 4.1.9, no peer warnings; `vite-tsconfig-paths` still compatible with vite 8. - `yarn install --immutable` — passes. - (Integration `yarn test` is gated on a live Twenty server, so not run here — that requirement is independent of this bump.) - Separate yarn project — changes are confined to `twenty-meeting-bot/{package.json,yarn.lock}`; no root impact. ## Note vite 8 supports tsconfig-paths resolution natively (`resolve.tsconfigPaths: true`), so `vite-tsconfig-paths` could be dropped in a follow-up — left as-is to keep this change minimal. |
||
|
|
c6aca3f0ea |
fix(calendar): ID-first chunked import for Google and CalDAV (#22015)
Google and CalDAV returned full events and imported them inline in the list-fetch job. Large/initial syncs overran BullMQ's lock, the job stalled, the workspace query runner was released mid-import, and TypeORM threw 'Query runner already released'. Mirror the messaging pipeline: every provider now returns event IDs only, cached in Redis; the import job drains them in CALENDAR_EVENT_IMPORT_BATCH_SIZE chunks and re-enqueues until empty, so no single job runs long. Adds Google/CalDAV import-by-id services and a provider dispatcher; removes the full-events inline path. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22015?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. --> |
||
|
|
9b2ca6f3f7 |
bump sdk and app version for call recording bot app (#22019)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22019?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. --> |
||
|
|
e9d5d71cd3 |
Wire up search field metadata (#21964)
## Part 1 - Exact scope of the current PR (#21964) close https://github.com/twentyhq/core-team-issues/issues/2586 This PR introduces `searchFieldMetadata` as a first-class flat metadata entity and migrates the existing search surface onto it, with **no change to which records are searchable** (ISO with `main`). In scope (what the PR does): - New flat entity `searchFieldMetadata` (universalIdentifier, applicationId, **`position`**, maps, conversions), registered in the central flat-entity constants and the migration build orchestrator. - `searchVector.asExpression` is **derived server-side** from `searchFieldMetadata` rows (validated by `isSafeTsVectorExpression`); never trusted from client input. - **Derivation order is deterministic, driven by each row's `position`** ([compute-search-vector-as-expression-from-search-field-metadatas.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-search-field-metadata/utils/compute-search-vector-as-expression-from-search-field-metadatas.util.ts)), replacing the previous non-deterministic `(createdAt, id)` sort. That sort collapsed to random UUIDs for standard fields (same `createdAt`), so any rename/relabel rewrote the `STORED` generated column to a logically-identical-but-textually-different expression and produced a permanent per-workspace diff vs the standard definition. Ordering now equals provisioning order; ties break on `universalIdentifier`. - Provisioning at object creation mirrors the existing surface exactly **and seeds `position`**: - custom objects -> the `name` field only, at `position: 0` ([build-default-search-field-metadatas-for-custom-object.util.ts](packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-search-field-metadatas-for-custom-object.util.ts)) - standard objects -> their curated `SEARCH_FIELDS_FOR_*` sets, `position` = the curated index - Backfill (instance + workspace commands in `2-16`) provisions rows for existing workspaces with the same surface **and the same positions** (standard from the curated standard maps, custom `name` = `0`), scoped to the workspace's own custom application ([build-search-field-metadata-backfill-operations.util.ts](packages/twenty-server/src/database/commands/upgrade-version-command/2-16/utils/build-search-field-metadata-backfill-operations.util.ts)). The `position` column is added in the same `2-16` fast instance command as `universalIdentifier`/`applicationId`. - Field rename of an already-indexed field recomputes `asExpression` (positions preserved, so order is stable) ([recompute-search-vector-on-field-rename.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/recompute-search-vector-on-field-rename.util.ts)). - Field delete drops the matching row(s) and recomputes; remaining rows keep their relative order (no renumber) ([from-delete-field-input-to-flat-field-metadatas-to-delete.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-field-metadata/utils/from-delete-field-input-to-flat-field-metadatas-to-delete.util.ts)). - Object relabel is **additive** and ISO/regression-fix only: it indexes the new label identifier **appended last (`position = max(existing) + 1`)** without dropping `name` ([recompute-search-vector-on-label-identifier-update.util.ts](packages/twenty-server/src/engine/metadata-modules/flat-object-metadata/utils/recompute-search-vector-on-label-identifier-update.util.ts)). This is a deliberate, temporary bridge. Explicitly OUT of scope (deferred): - No API to edit `searchFieldMetadata` (no user-facing search-field configuration, including `position` — it is internal and only written by provisioning/backfill/recompute). - No auto-indexing of arbitrary searchable fields. Creating a custom TEXT/EMAILS/etc. field does NOT add it to search (the `computeSearchFieldMetadataCreationForFields` behavior was removed in `e6820ad`). - No field-type-transition handling (field type is immutable - not in `FLAT_FIELD_METADATA_EDITABLE_PROPERTIES`, so that path was dead code). - No `position` validation (uniqueness/range) and no multi-vector / per-field `weight` config — deferred to the configurable-search follow-up (#1428). Net: `searchFieldMetadata` becomes the source of truth for the *same* surface as `main`. The only intentional divergences from `main` are "relabel preserves `name`" (additive) and the deterministic `position`-ordered `asExpression` (a correctness/perf fix that is byte-identical to provisioning order, so it does not change the searchable surface). --------- Co-authored-by: Félix Malfait <felix@twenty.com> |
||
|
|
99e7c2cde0 | Improve meeting bot recording tab layout and transcript speakers ui (#22016) | ||
|
|
293ff4c462 |
Auto-generate app cover images from the app logo (#22011)
<img width="1388" height="858" alt="image" src="https://github.com/user-attachments/assets/59f16bf7-5908-4624-b3af-51416bbebba3" /> ## What When an app is built (`twenty build` / `twenty publish`), the SDK now generates a marketplace cover image and sets it as the app's screenshot, but only when the app declares a `logoUrl` and has no `screenshots`. The cover composites the app's logo and the Twenty logo over the branded halftone backdrop, matching the design reference. ## Why Most apps ship a logo but no screenshots, so their marketplace detail page had no hero visual. This gives them a polished cover for free, with no per-app design work. ## Notes for reviewers - Generation lives in the build path (`operations/build.ts`), not `buildManifest`, so `twenty dev` and the shared manifest builder are untouched. It is best-effort: on failure it logs a warning and the build continues. - The cover is written to `.twenty/output` and registered as a public asset + screenshot, so the existing copy/checksum/serve pipeline handles it unchanged. No app source files are modified. - Adds `sharp` as a runtime dependency of `twenty-sdk` (a build-time tool, like `esbuild`); it is not bundled into built apps. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22011?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. --> |
||
|
|
4f1ffa0a96 |
fix(twenty-partners): coerce null fields in TFT opportunity import (#22017)
## What The TFT `HTTP Request` action POSTs `null` for empty fields (e.g. `amountMicros:null`, `closeDate:null`). The import schema typed those as `z.number()/z.string().optional()`, which reject `null` (it is not `undefined`), so the endpoint returned `ok:false / invalid_input` before any API call. ## Fix A `dropNulls` preprocessor on the request schema converts `null` (top-level or nested) to "field absent" before validation. Null optional fields are simply omitted from the created opportunity; required `name` still fails correctly if null. No schema-shape or behaviour-contract change. ## Tests Added a case feeding the failing payload shape (`amountMicros:null`, `closeDate:null`) → `created:true` with `amount`/`closeDate` omitted. 42/42 unit pass, lint clean. Patch bump `1.1.1 → 1.1.2`. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22017?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. --> |
||
|
|
c52c983b90 |
Source app About description from README and improve internal app READMEs (#22012)
## What - The SDK manifest build now sources an app's `aboutDescription` (the long-form "About" tab content) from its `README.md`. An explicit `aboutDescription` in the config still wins, matching the existing marketplace CDN fallback. - Removed the now-duplicated `aboutDescription` from internal app configs and deleted the standalone `ABOUT_DESCRIPTION` constant files. - Rewrote internal app READMEs to read as user-facing About content: stripped developer/build/source-path noise, and expanded the thin ones. `call-recording` and `self-hosting` (one-liners over substantial apps) and `people-data-labs` were rewritten from a close reading of the code; `twenty-exa` was verified for accuracy. - Added a unit test (and a fixture README) covering README → `aboutDescription` in the build. ## Why The README and the About description were maintained separately and drifted. Making the README the single source keeps the About tab accurate and removes duplicated copy. ## Notes for reviewers - Internal apps depend on the published `twenty-sdk`, so the build change takes effect for them after an SDK release + dependency bump. Until then, published apps still get README → `aboutDescription` via the marketplace CDN sync. - Standard/Custom app descriptions are unchanged (they are resolved in the frontend, not via the manifest). <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22012?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. --> |
||
|
|
558f2509c2 |
fix(deps): pin undici under @module-federation/dts-plugin to ^7.28.0 (#22014)
## Summary Adds a **scoped** `resolutions` entry pinning `undici` under `@module-federation/dts-plugin` to `^7.28.0`, resolving **6 Dependabot alerts** — #1523 & #1566 (high), #1522 & #1570 (medium), #1565 & #1572 (low); all `undici >=7.0.0 <7.28.0`. ## Why a scoped resolution (not a parent-bump or a global pin) - The vulnerable `undici@7.24.7` is pinned **exactly** by the transitive build tool `@module-federation/dts-plugin@2.5.1`. There's no parent to bump — its parents pin it and it doesn't loosen the pin at latest — so a committed scoped resolution (matching the existing `@nestjs/graphql/ws`, `express/qs` pattern) is the right fix. - A **global** `undici` resolution would be wrong: the tree also has `undici@6.27.0` (`^6.25.0`, outside the advisory) and the latest undici is `8.5.0`, so forcing all undici to 7.x would break the 6.x consumer. - `^7.28.0` resolves to `7.28.0` (highest 7.x), which **dedupes** with the `undici@7.28.0` already in the tree (via `^7.25.0`) — no new copy is introduced. ## Result - `undici@7.24.7` is gone; the only undici 7.x is now `7.28.0`. `undici@6.27.0` (6.x) is untouched. - Build-tooling dependency (module-federation type generation) — not in the production server runtime; `undici 7.24 → 7.28` is a compatible minor bump. ## Verification - `yarn install --immutable` passes (lockfile consistent with CI). - No undici in `[7.0.0, 7.28.0)` remains in `yarn.lock`. |
||
|
|
6520db22ca |
fix(deps): bump opentelemetry suite to core 2.8.0 (+ sentry 10.59) (#22010)
## Summary Bumps the OpenTelemetry suite onto the **`@opentelemetry/core` 2.8.0** wave (plus Sentry `10.51 → 10.59`, which carries the otel instrumentation), resolving [Dependabot alert #1510](https://github.com/twentyhq/twenty/security/dependabot/1510) (`@opentelemetry/core < 2.8.0`). ## Why a parent-bump, not a `resolutions` entry The vulnerable `@opentelemetry/core` is transitive, pulled in by the otel packages we declare (`exporter-metrics-otlp-http`, `exporter-prometheus`, `sdk-metrics`) **and** by `@sentry/*` (which bundles `@opentelemetry/instrumentation-*`). The otel **stable** packages pin `core` to their own exact version and are version-coupled — forcing `core` ahead of the suite via `resolutions` risks runtime breakage. So this bumps the declared parents instead. ## Changes - `twenty-server/package.json`: - `@opentelemetry/exporter-metrics-otlp-http` `^0.200.0 → ^0.219.0` - `@opentelemetry/exporter-prometheus` `^0.217.0 → ^0.219.0` - `@opentelemetry/sdk-metrics` `^2.0.0 → ^2.8.0` - `@sentry/{nestjs,node,profiling-node}` `^10.51.0 → ^10.59.0` - `yarn dedupe` collapses the remaining transitive `core@2.7.1` (caret consumers) onto `2.8.0` — the whole stable set (`core` / `resources` / `sdk-trace-base` / `sdk-metrics`) is now `2.8.0`. - **`@types/pg` added as a direct devDependency.** The newer Sentry drops the instrumentation that used to *transitively* provide `@types/pg`; twenty-server imports `pg` directly (`set-pg-date-type-parser.ts`), so it now declares its own types — fixing a latent fragility the bump exposed. ## Verification - `nx typecheck twenty-server` — **0 errors** (validates the otel/sentry API surface we call is intact). - `yarn install --immutable` passes. - No `@opentelemetry/core < 2.8.0` remains. - Lockfile churn is contained to the observability subtree (otel/sentry + their transitive deps; net **−615 lines**). > Sentry resolved to `10.59.0` rather than the just-published `10.60.0` due to the repo's `npmMinimalAgeGate`. > Worth a quick server-boot check during review to confirm Sentry/otel init at runtime. |
||
|
|
0f451897cf |
Make twenty-ui theming a consumer-facing API (#22007)
**What** - Add `useTheme()` and `useThemeColorScheme()` as the public theme accessors, and migrate the 88 internal `useContext(ThemeContext)` call sites to them. `ThemeContext` stays exported. - Make `ThemeProvider` overridable and scopeable: new `applyToRoot` (default `true`), `overrides` (a `--t-*` map), and `className` props. When scoping is requested it renders a `display: contents` wrapper that also serves as the themed portal container, exposed via `ThemeScopeContext` / `useThemeContainer()`. `AppTooltip` and `Modal` portal into that container, falling back to `document.body`. - Document the `--t-*` override contract in the README; barrels regenerated. **Why** Consumers had no stable theme accessor (they reached into the raw context) and no supported way to re-theme. This adds both without behavior change. **Reviewer notes** - The default path is unchanged: `applyToRoot` defaults to `true`, so the colorScheme class still lands on `<html>` and portaled overlays (tooltips, dropdowns, modals) stay themed. The global class is load-bearing for body portals; scoping is opt-in. - `twenty-front` is untouched (migrating its consumers is a separate follow-up). <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22007?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. --> |
||
|
|
85406a58fb |
Use minimal babel presets for wyw to fix the website Cloudflare build (#21994)
The website's Cloudflare build (`opennextjs-cloudflare` / Turbopack) was failing with `_defineProperty is not a function` while linaria/wyw evaluates `twenty-ui/dist/theme.cjs` at build time. It regressed in #21946, whose twenty-ui build rework changed the emitted `theme.cjs` so the theme objects ship as runtime object spreads (`{ ...THEME_COMMON }`). **Cause:** wyw evaluates modules in Node through `next/babel`, which pulls in `preset-env` + `transform-runtime`. Those re-lower the runtime spreads into `@babel/runtime` helpers imported as ESM; wyw then `require()`s that ESM module in a CJS context where the export is not callable, so `_defineProperty` fails. **Fix:** wyw runs in Node and needs no downleveling, so replace `next/babel` with minimal presets (`@babel/preset-typescript`, `@babel/preset-react`, `@wyw-in-js/babel-preset`, plus `@babel/plugin-transform-export-namespace-from`), matching twenty-front's wyw config. No `@babel/runtime` helpers get injected. Kept on the website side so twenty-ui keeps react/react-dom as peer deps (#21946). Note: no blocking PR check runs the website production build, so this is best validated via the website preview build or the twenty-infra deploy. |
||
|
|
16c9782c96 |
i18n - website translations (#22001)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22001?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> |
||
|
|
6a4dc37c6d |
Organize twenty-website product-feature into folder structure (#21999)
Applies the PipelineVisual folder structure to every other file in `product-feature`. - Each feature visual (Import, Tasks, Files, Emails, Dashboard, Contacts) becomes an `XVisual/` folder: shell + `index.ts` barrel, with `components/`, `data/`, `types/` (one export per file), and `utils/` as applicable. `BarChart`/`DonutChart` move into `DashboardVisual` (exclusive to it); `RecordTabHeader` stays shared. - The section's non-visual files get the same treatment: `components/` (Tiles, TileVisual, TileContent, ScrollEntrance, RecordTabHeader), `data/`, `types/`, `utils/`. `ProductFeature.tsx` stays the section shell. - Drop dead code: unused `WindowChrome` and the now-orphaned `product-feature-scene` token. No behavior change — `index.ts` barrels keep all import paths stable. Typecheck, check-conventions, oxlint, and unit tests pass locally. |
||
|
|
7b45380777 |
feat(ai): large tool output handling + navigation tools (#21982)
## Summary
Large tool outputs (e.g. a workflow run that serializes to ~70k tokens)
blow the chat context budget and force per-tool "raw" variants. This PR
handles oversized outputs generically in one place:
1. **Producer:** when a tool result exceeds a byte budget, it is spilled
to a `FileFolder.AgentChat` file and replaced with a compact `{ spilled,
outputRef, shape, hint }` envelope.
2. **Consumer:** two bounded, in-server navigation tools —
`extract_json_path` and `search_output` — let the model dig into the
spilled file by `fileId` without spinning up `code_interpreter`.
Together they add a fast, auditable middle tier between "truncated
inline preview" and "full code_interpreter relay," and enable an
enterprise "restricted" mode (spill + navigation, no sandbox).
## Data flow
```mermaid
flowchart TD
exec["resolveAndExecute / hydrateToolSet closure"] --> compact[compactToolOutput]
compact --> enabled{"spillLargeOutput enabled? (chat only)"}
enabled -->|no| inlineRaw["inline raw (MCP, workflow, sandbox bridge)"]
enabled -->|yes| size{"bytes > MAX_INLINE_TOOL_OUTPUT_BYTES?"}
size -->|no| inline["inline result"]
size -->|yes| skeleton["jsonShapeSkeleton + largeOutputHint"]
skeleton --> write["writeFile(AgentChat)"]
write --> envelope["return { spilled, outputRef, shape, hint }"]
envelope --> model[Model]
model --> nav["extract_json_path / search_output / code_interpreter (by fileId)"]
```
## Part 1 — Navigation tools (consumer)
- `extract_json_path`: extracts a sub-tree from a spilled JSON file by a
JSONPath-lite expression (dot/bracket access, array slicing,
single-level wildcard), with `maxItems`/`maxDepth` bounding. No filters
or recursive descent — those belong to `code_interpreter`.
- `search_output`: grep-like line search with context lines and
stateless `offset` pagination (`{ matches, totalMatches, hasMore }`).
- Both read from `FileFolder.AgentChat` by `fileId`, enforce their own
output byte cap, and are registered in `ActionToolProvider` (always
available; read-only).
## Part 2 — Spill producer
- Spilling slots in right after the existing `compactToolOutput` step at
the two seams in `ToolRegistryService` (`resolveAndExecute` and the
`hydrateToolSet` execute closure).
- `ToolOutputSpillService.spillIfTooLarge()` measures
`Buffer.byteLength`; over `MAX_INLINE_TOOL_OUTPUT_BYTES` (16 KB ≈ 4k
tokens) it writes the full payload and returns the envelope. Spill
failures never block the call (inline + warning).
- `jsonShapeSkeleton` computes a bounded structural map (depth 4, arrays
as `"array[N] of <type>"`, id-keyed maps collapsed, long leaves as size
markers, hard-capped at 1024 bytes) so the model knows the key paths in
one pass.
- Optional per-tool `largeOutputHint` (on the `Tool` type, threaded via
the descriptor) is used as the hint when present, else a generic hint.
The `shape` is always computed generically.
## Surfaces
Spilling is an opt-in flag (`spillLargeOutput`) mirroring
`compactOutput`:
| Surface | `spillLargeOutput` | Behavior |
| --- | --- | --- |
| AI chat / agent | `true` (in `chat-execution.service.ts`) | Spill on;
nav tools + `code_interpreter` in catalog |
| External MCP clients | unset | Raw output |
| Workflow agents | unset | Raw output |
| `code_interpreter` sandbox bridge | unset (it's an MCP call) | Raw
output |
The sandbox bridge inherits "no spill" for free via the MCP path — no
header sniffing, no `ToolContext.source` field.
## Design constraints (anti-micro-OS)
Exactly two navigation tools, no composition/piping, read-only, bounded
output. The boundary is: expressible as a single path lookup or text
search → nav tool; aggregation/correlation/transform →
`code_interpreter`.
## Notes / deviations from the plan
- `jsonShapeSkeleton` and `ToolOutputSpillService` live under the `tool`
module (not `tool-provider/output-transforms`) to avoid a `tool →
tool-provider` import cycle.
- Spill files use `{ isTemporaryFile: false, toDelete: false }` (same as
`code_interpreter`); `isTemporaryFile` here means files-field promotion,
not a TTL.
## Test plan
- [x] `extract-json-path` + `search-output` util unit tests (23 cases)
- [x] `jsonShapeSkeleton` unit tests (6) and `ToolOutputSpillService`
unit tests (4)
- [x] oxlint + oxfmt clean on changed files; `twenty-server` typecheck
clean (pre-existing unrelated errors aside)
- [ ] Manual: trigger an oversized tool result in chat, confirm the
envelope is returned and `extract_json_path` / `search_output` read the
spilled file by `fileId`
## Why no automated e2e
Spilling is chat-only and the chat path runs a live model, so the
black-box MCP integration harness can't deterministically trigger a
spill (MCP intentionally doesn't spill). The seam is small, explicit
flag-threading mirrored on `compactOutput`, covered by the unit suites.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21982?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. -->
|
||
|
|
c18787350f |
fix: month and year dropdowns in settings logs date picker (#21529)
### Summary - Fixes #21514 and Issue 2 - **Issue 1**: when opening the calendar and choosing a month or year, those lists could appear underneath the calendar, making them impossible to see and use. (Issue #21514) - **Issue 2**: after opening the calendar icon menu, clicking the month or year controls don't work, so you couldn’t actually change the month or year. Before: <img width="355" height="434" alt="607363028-0d3a302e-9dba-4d9a-b354-ad7cbcd1fba5" src="https://github.com/user-attachments/assets/b1c357d1-a7cf-4572-8737-721cf4e2597a" /> After: https://github.com/user-attachments/assets/ffc26447-ff34-4f11-a3b4-4c329e446ec4 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21529?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. --> --------- Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
e1c962acba |
feat(meeting-bot): configure Recall recording retention hours (#21978)
## Summary
- Sends an explicit Recall.ai recording retention policy when creating
or rescheduling meeting bots.
- Uses the optional server variable
`MEETING_BOT_RECORDING_RETENTION_HOURS` instead of a workspace/app
variable.
- Defaults to `166` hours (6 days and 22 hours), keeping Twenty-hosted
deployments below Recall.ai's 7-day free storage window while still
allowing self-hosters to configure a longer retention period.
## Why
Recall.ai accounts created after June 12, 2025 retain recording media
forever unless retention is configured. Twenty ingests the meeting
artifacts into its own storage, so Recall.ai media retention should be
bounded by default to avoid unnecessary third-party storage cost.
## Changes
- Replaces the days-based app variable with the server variable
`MEETING_BOT_RECORDING_RETENTION_HOURS`.
- Adds a default retention constant of `166` hours.
- Builds `recording_config.retention = { type: 'timed', hours }`
centrally through `getRecallBotRecordingConfig()`.
- Applies the same recording config to both bot creation and bot
rescheduling.
- Documents the server variable and warns that values above `168` hours
may incur Recall.ai storage charges.
- Updates Recall API tests to assert retention is sent and invalid
values fall back to the safe default.
## QA
- [x] `yarn test:unit`
- [x] `yarn lint`
- [x] `yarn exec tsc --noEmit -p tsconfig.spec.json`
- [x] `git diff --check`
- [x] Live Recall.ai bot payload includes `recording_config.retention =
{ type: 'timed', hours: 166 }`
---------
Co-authored-by: Emmanuel Hernandez <emmanuel.hernandez@clickbalance.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: ehconitin <nitinkoche03@gmail.com>
|
||
|
|
6f50b9d01e |
fix(twenty-partners): update Partner view in place on upgrade instead of deleting (#21995)
## Why Upgrading the already-installed `twenty-partners` app in place (0.5.x → 1.x) via `yarn twenty app:install` aborts during the sync reconcile: ``` view: INVALID_VIEW_DATA: Cannot delete the only view for this object (379b11d5-…) viewField: INVALID_VIEW_DATA: Label identifier view field cannot be deleted (21afcc69-…) ``` The marketplace-v2 change deleted `all-partners.view.ts`. On an installed workspace that view is the Partner object's primary view and holds the **label-identifier** viewField (the `name` column). Twenty's manifest sync refuses to delete an object's *only* view or a label-identifier viewField, so the in-place upgrade fails. (Fresh installs are unaffected; only upgrades from a version that had `all-partners` hit this.) ## What Repurpose the retired `all-partners` identity for `partners-validated` so the sync performs an **update in place** instead of a delete: - `partners-validated.view.ts` now uses the old view id `379b11d5-…`, and its `name` column reuses the old label viewField id `21afcc69-…`. - Remove the now-dangling `ALL_PARTNERS_VIEW_UNIVERSAL_IDENTIFIER` constant (its file was already gone). - Patch bump `1.1.0` → `1.1.1`. The resulting view is the intended "Partners Validated"; the other retired Partner view (`validated-partners`) deletes cleanly because the object keeps other views. ## Revision **Patch** — migration bugfix, no new behaviour. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21995?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. --> |
||
|
|
4789ba6265 |
feat(ai): add AI tools to list and inspect workflow runs (#21983)
- Add `get_workflow_run` and `list_workflow_runs` AI tools so the workflow agent can troubleshoot failed or misbehaving workflow runs — listing runs with optional filters (workflow, status, limit) and inspecting a specific run's steps, errors, and failed step logs. - Enforce `rolePermissionConfig` on all three read tools (`get_workflow_run`, `list_workflow_runs`, `get_workflow_current_version`) instead of bypassing permission checks, consistent with how `create_complete_workflow` and database CRUD tools work. - Add unit tests for the three tools covering permission forwarding, success paths, and error paths. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21983?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> |
||
|
|
47b48d83f7 |
fix(front): include isUIEditable/isRemote in CreateOneObjectMetadataItem so new objects aren't read-only until refresh (#21796)
## Problem
After creating a custom object in **Settings → Data Model**, the new
object's **"New Field"** (and **"Add relation"**) buttons are missing.
They only appear after a hard page refresh.
## Root cause
`CreateOneObjectMetadataItem`
(`packages/twenty-front/src/modules/object-metadata/graphql/mutations.ts`)
selected only a subset of object-level fields and omitted
`isUIEditable`, `isRemote`, `isSystem`, `isUICreatable`,
`universalIdentifier`, `shortcut`, and `duplicateCriteria` — all of
which are present in the shared `ObjectMetadataFields` fragment used by
the bootstrap query.
`useCreateOneObjectMetadataItem` writes the mutation response into the
metadata store via `addToDraft`. Because the mutation resolves *after*
the SSE create event and `addToDraft` replaces entries by `id`, the
reduced mutation response overwrites the fuller record that arrived over
SSE. The stored object then has `isUIEditable === undefined`, so
`isObjectMetadataReadOnly` returns `true` (`!undefined`), and
`ObjectFields` hides the action buttons via its `{!readonly && …}`
guard.
A hard refresh "fixes" it only because the bootstrap query repopulates
the store from `ObjectMetadataFields`, which includes the missing
fields.
## Fix
Add the missing object-level fields to the `CreateOneObjectMetadataItem`
selection so a newly created object matches the bootstrap shape, and
regenerate the metadata GraphQL types. No other code changes required.
## How to test
1. Go to **Settings → Data Model** and create a new custom object.
2. Open the new object's **Fields** tab.
3. ✅ The **"New Field"** button is visible immediately — no refresh
needed.
Before this change, the button was hidden until a manual refresh.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21796?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. -->
|
||
|
|
0e6d96bb5e |
fix(workflow): stop trigger/action filter Conditions from flashing on edit (#21952)
## Problem
Adding a condition to a database-event **trigger** (the new Conditions
section), the **Filter** action, or the **If/Else** action causes the
just-added condition to flash out and back in.
## Root cause
`WorkflowEditActionFilterBodyEffect` seeds the builder's local jotai
atoms from the persisted `defaultValue` through an effect that
**resynced whenever the live atoms differed from `defaultValue`** (the
atoms were in the effect deps and the equality check compared atoms vs
`defaultValue`).
A local edit writes the atoms **synchronously**, then persists through
an **async** mutation — and for an *active* workflow that mutation first
creates a draft version over the network. During that window the atoms
are ahead of the still-stale `defaultValue`, so the effect treated it as
"out of sync" and overwrote the edit back to the stale value, then wrote
it again once the save landed. That round-trip is the flash.
The resync existed for a real reason: the atoms are module-cached per
`instanceId` and persist across mounts, and the trigger shares a single
**constant** `instanceId` (`'trigger'`), so a previous trigger's filters
must be overwritten when a different one is opened. (This is also why
the `?? { stepFilterGroups: [], stepFilters: [] }` fallback was added in
#21868 — to reset builder state deterministically between trigger
edits.) So a naive "init-once" fix would reintroduce that stale-state
leak.
## Fix
Resync from `defaultValue` **only when `defaultValue` itself changes**,
tracked via the last-synced value in `useState` (not the live atoms).
This:
- never clobbers an in-flight local edit → no flash;
- still re-seeds when switching the trigger/action being edited → no
stale-state leak;
- preserves reflecting genuine external `defaultValue` changes.
The `hasInitialized*` flags are no longer needed and are removed (along
with the now-unused `stepId` prop on the effect).
## Tests
Adds a regression test covering: seeding from `defaultValue` on mount,
the **no-clobber-while-stale** invariant (the flash), and resync on a
genuine `defaultValue` change. Verified the no-clobber test **fails**
against the old "resync against live atoms" behavior and passes with the
fix.
## Verification
- `nx typecheck twenty-front` ✅
- `nx lint:diff-with-main twenty-front` ✅ (0 warnings / 0 errors)
- New unit test: 3 passing ✅
## Known residual / follow-up
On an *active* workflow, the first edit creates a draft version over the
network; making a second edit before that round-trip completes leaves a
narrow window where the optimistic echo of the first value could
momentarily win. Far narrower than the current flash-on-every-edit.
Eliminating it entirely (and resolving the still-open HIGH-severity
"constant `instanceId`" review flag from #21868) would mean giving the
trigger a unique `instanceId` per workflow version + a React `key` to
reset on remount — proposed as a separate, scoped follow-up.
https://claude.ai/code/session_01XnjtzFepMJX2VFwnQVcQbV
---
_Generated by [Claude
Code](https://claude.ai/code/session_01XnjtzFepMJX2VFwnQVcQbV)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21952?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. -->
|
||
|
|
9e31ffdf68 |
feat(messaging): webhook push sync for Gmail, Calendar and Microsoft (#21970)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21970?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. --> |
||
|
|
84f4ac9082 |
fix(deps): dedupe esbuild to 0.28.1 (#21993)
## Summary Dedupes the transitive **esbuild** so the lone `^0.28.0 → 0.28.0` copy collapses onto the existing `0.28.1`, resolving [Dependabot alert #1438](https://github.com/twentyhq/twenty/security/dependabot/1438) (vulnerable range `>=0.27.3 <0.28.1`). ## Details - `esbuild@^0.28.0` was resolving to `0.28.0`; `0.28.1` is already in the tree (via `^0.28.1` + existing scoped `esbuild` resolutions), so `yarn dedupe esbuild` collapses the `^0.28.0` descriptor onto `0.28.1` — **lockfile-only**, no `package.json` change and no `resolutions` entry. - The large line count is esbuild's ~25 platform-binary subpackages (`@esbuild/*@0.28.0`) being pruned as orphans. The diff is **esbuild-only** (28 removed keys, all `esbuild`/`@esbuild/*`). - Untouched: `esbuild@0.25.5` and `~0.27.0 → 0.27.2` are outside the advisory range (`<0.27.3`). ## Verification - `yarn install --immutable` passes (lockfile consistent with CI). - No esbuild in `[0.27.3, 0.28.1)` remains in `yarn.lock`. |
||
|
|
c7ad1ff8ee |
Route Recall webhooks by workspace metadata (#21991)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21991?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. --> |
||
|
|
ec3b9beae5 |
fix(deps): bump http-proxy-middleware to 2.0.10 (#21992)
## Summary Bumps the transitive **http-proxy-middleware** `2.0.9` → `2.0.10`, resolving [Dependabot alert #1574](https://github.com/twentyhq/twenty/security/dependabot/1574) (vulnerable range `>=0.16.0 <2.0.10`). ## Details - The parent's `^2.0.9` range already permits the fix, so this is a **lockfile-only** bump (`yarn up -R http-proxy-middleware`) — no `package.json` change and no `resolutions` entry needed. - The unrelated `http-proxy-middleware@^3.0.5 → 3.0.7` is outside the advisory range and untouched. - Transitive **build-tooling** dependency — not part of the production server runtime; patch-level security fix. ## Verification - `yarn install --immutable` passes (lockfile consistent with CI). - No `2.0.9` remnant remains in `yarn.lock`. |
||
|
|
c98620e14c |
Rework website PipelineVisual to match twenty-front kanban (#21989)
Reworked the product page's `PipelineVisual` to match twenty-front's kanban — board header, card layout/fields, hover states, and font weights. Also loaded and pinned Inter (twenty-front's product font) for the app-preview mockups, which previously fell back to the system font. <img width="879" height="625" alt="image" src="https://github.com/user-attachments/assets/396f0a90-114b-47f9-92a6-2e74d732a91e" /> |
||
|
|
50c4659cae |
feat(twenty-partners): import an opportunity from TFT via manual workflow (#21979)
## What
Adds a one-way, manual copy of a single Opportunity from the
**twentyfortwenty (TFT)** workspace into **partners**. No automatic/echo
sync — one record per button press.
A TFT-side **manual Workflow** (a "Run workflow" button on the
Opportunity record) → **HTTP Request** action → `POST /s/opportunities`
on the partners app. The new `import-opportunity-from-tft` logic
function:
- **Shared-secret guard** on the `x-application-secret` header vs the
existing `PARTNER_APPLICATION_SECRET` app variable (the SDK's
`isAuthRequired` only accepts user JWTs, not API keys — same pattern as
`submit-partner-application`).
- **Idempotent on `tftOpportunityId`** (a field the partners Opportunity
object already has); falls back to `name` for manual calls. Re-press →
no duplicate.
- **Find-or-create** the Company (by name) and the point-of-contact
Person (by primary email), then `createOpportunity` with `name / amount
/ closeDate / stage / companyId / pointOfContactId`.
## Out of scope
- The **TFT-side workflow is a UI step** (built once in the TFT
workspace) — it can't live in this repo. The exact HTTP action config +
body mapping lives in the workflow itself.
- Owner/workspace-member copy and stage-enum remapping are intentionally
skipped (YAGNI).
## Files
- `src/logic-functions/import-opportunity-from-tft.logic-function.ts` —
the handler + manifest.
- `src/logic-functions/__tests__/import-opportunity-from-tft.test.ts` —
unit tests (auth reject / idempotent / mapped create).
- `package.json` — version bump **0.5.5 → 0.6.0** (minor; new feature).
## Verification (local bundle)
- `yarn test:unit` 3/3 · `yarn lint` 0/0 · `yarn twenty dev --once` →
`created logicFunction import-opportunity-from-tft`, no manifest
warnings.
- Live `POST /s/opportunities` with the secret → `201
{ok:true,created:true,id}` (confirms the app role can create Opportunity
+ Company + Person). Re-POST same `tftOpportunityId` → `created:false`.
Wrong secret → `unauthorized`.
## Deploy note
Additive (new logic function + HTTP route) — upgrades cleanly with
`deploy` + `install`. `PARTNER_APPLICATION_SECRET` is already set on
prod, so no new application variable to configure.
|
||
|
|
6e7d8ef96c |
[Twenty-front]: Record table Header drag and drop functionality (#21304)
Closes #21303 and https://github.com/twentyhq/core-team-issues/issues/151 https://github.com/user-attachments/assets/45cee1be-464f-467e-a1c0-cf5354ff87db --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
46642c81c9 |
fix(front): unblock email verification on the central domain (blank modal) (#21980)
## Problem
After clicking the email-verification link on the central domain (e.g.
`app.twenty.com/verify-email?...`), a new user is left staring at a
**blank white auth modal** and onboarding never continues. The email is
actually verified — the user is just never moved off the verify-email
page.
## Root cause
`VerifyEmailEffect` (mounted on `/verify-email`) handles the
central/workspace‑agnostic domain like this:
```tsx
if (!isOnAWorkspace) {
await verifyEmailAndGetWorkspaceAgnosticToken(emailVerificationToken, email);
return enqueueSuccessSnackBar(successSnackbarParams);
}
```
It renders nothing of its own in this branch (`return <></>`) and relies
entirely on the auth hook to navigate.
The onboarding workspace-creation refactor (**#21641** "Let users pick
their workspace subdomain during sign-up", refined by **#21723**)
changed `navigateAfterMultiWorkspaceSignInUp`:
- **Before:** a user with `0` workspaces was sent through
`createWorkspace()`, which created the workspace and **redirected to the
workspace subdomain** — navigating away from `/verify-email`.
- **After:** for multi-workspace it now only does
`setSignInUpStep(SignInUpStep.WorkspaceCreation)` (the new
name/subdomain/logo form) — **no navigation**.
`signInUpStepState` is read **only by the `SignInUp` page**
(`/sign-in-up`), which renders `SignInUpWorkspaceCreationForm` for that
step. But the user is on `/verify-email`, whose route renders only
`VerifyEmailEffect` — which knows nothing about the step state and
returns an empty fragment. Nothing bridges the gap
(`usePageChangeEffectNavigateLocation` also won't redirect, because
`/verify-email` is whitelisted in `ONGOING_USER_CREATION_PATHS`), so the
user is stuck on an empty modal.
### Scope of the breakage
- **Broken:** new user, multi-workspace instance (Twenty Cloud central
domain), email verification enabled, signing up to create a workspace
(`0` workspaces). The `2+`-workspaces case (`WorkspaceSelection`) is the
same.
- **Not affected:** the single existing-workspace case (still does a
real `redirectToWorkspaceDomain`), the workspace-subdomain verification
path (`verifyEmailAndGetLoginToken` → `verifyLoginToken`), and
single-workspace self-host.
## Fix
After a successful workspace-agnostic verification, hand off to the
`SignInUp` page so it mounts and renders whatever step the hook just
set:
```tsx
if (!isOnAWorkspace) {
await verifyEmailAndGetWorkspaceAgnosticToken(emailVerificationToken, email);
enqueueSuccessSnackBar(successSnackbarParams);
return navigate(AppPath.SignInUp);
}
```
This is intentionally scoped to `VerifyEmailEffect` (the only entry
point that lives on a route which doesn't host the sign-in-up step UI).
The in-app sign-in/sign-up callers of
`navigateAfterMultiWorkspaceSignInUp` are already on `/sign-in-up`, so
they're untouched — keeping their query params (invite tokens, billing
checkout, returnToPath) intact. For the single existing-workspace edge
case, the hook's redirect still wins.
## Testing
- New `VerifyEmailEffect.test.tsx`:
- central-domain success → navigates to `AppPath.SignInUp` + shows the
success snackbar;
- failure → does **not** hand off to `SignInUp` (error state is shown);
- workspace subdomain → workspace-scoped path is untouched (no
workspace-agnostic call, no `SignInUp` hand-off).
- `nx typecheck twenty-front` ✅, `oxlint --type-aware` + `oxfmt` on
changed files ✅.
https://claude.ai/code/session_017oVwW12hC42RdCgSKK8dFP
---
_Generated by [Claude
Code](https://claude.ai/code/session_017oVwW12hC42RdCgSKK8dFP)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21980?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. -->
|
||
|
|
24533b510c |
feat(twenty-partners): marketplace v2 — Application-driven matching workspace (#21816)
## Summary Restructures the Twenty Partners app into an **Application-driven matching workspace**: leads post briefs (Opportunities), partners browse and **self-apply**, admins review applications and assign a winner. The candidacy funnel lives on `Application.state`; the deal lifecycle lives on the stock Opportunity `stage`. Version **1.0.0** — **breaking**: removes the legacy `matchStatus` field and the auto-match flow. Prod upgrade path is **uninstall → deploy → install** (not an in-place upgrade). ## How "Apply" works — no workflow, no special permission Partners apply by **creating an Application directly** from a listed brief (a normal record write, governed by the Application object permission). The `on-application-created` logic function (shipped in the manifest) then resolves the partner from `createdBy`, sets `state = APPLIED`, stamps `partner`/`partnerUser`/`lastActivityAt`, and dedupes by (opportunity, partner). - **No `WORKFLOWS` permission flag.** An earlier iteration used a manual "Apply" workflow, but running a manual workflow requires the `WORKFLOWS` flag, which **cannot be granted on an app-owned role** (the manifest sync drops `role → permissionFlag` links, and the metadata API rejects out-of-band grants on app roles). Self-apply via record-create sidesteps this entirely and is prod-viable as-is. - `Application.state` **defaults to `APPLIED`** so a partner never sees a misleading "Invited" flicker while the async handler runs. Admin invites set `INVITED` explicitly. ## ⚠️ Manual setup after install (per workspace) 1. **`yarn rls:configure`** — applies the partner row-level predicates and verifies field-locks (predicates can't ship in the manifest). Required for partner scoping. 2. **"Mark as Winner" workflow** — one manual-trigger workflow on **Application** → *Update Record* that sets `Opportunity.partner`, which drives the WON/BACKUP cascade. Admins run it (admins bypass the flag via `canUpdateAllSettings`); equivalent to editing the Opportunity's `partner` field directly. Steps in `src/workflows/README.md` (the **Apply** section there is superseded by self-apply). ## What's included - **Data model:** new `BACKUP` Application state; symmetric cascade owned by `on-opportunity-partner-won` — assign → winner `WON`, other applicants `BACKUP`; unassign → all reopen to `APPLIED`. `Opportunity.partner` is the single source of truth. - **Removed:** `matchStatus` field + `on-opportunity-auto-match` (dead). Deal lifecycle now on the stock `stage`. - **Partner row-level security (B7):** RLS predicates scope partners to their own `Partner`/`Person`/`Company`/`Application` rows; `Opportunity` is `(partnerUser IS me) OR (isListed = true)` so listed briefs are visible to all partners; `Application` is `(partnerUser IS me) OR (lastActivityAt IS EMPTY)` — the IS-EMPTY branch lets a partner's own insert pass (partnerUser is stamped just after insert). Field-locks make Opportunity `stage`/`amount` and most Application fields read-only for partners (pitch stays editable). Applied via `yarn rls:configure`. - *Trade-off:* an unstamped application (lastActivityAt null) is briefly readable by any partner — sub-second window, permanent only if the handler fails to stamp. Acceptable for an internal marketplace; the front-component Apply path (below) would remove it. - **Idempotency:** `on-application-created` dedupes duplicate applications by (opportunity, partner). - **Views & navigation**, reorganized into sections: - **Partner Workspace:** Open Briefs · My Applications · My Profile · My Deals - **Matching Admin:** Briefs to Match · Deals (board) · Applications · Applications by Opportunity · All Opportunities · Follow-up Applications · Follow-up Briefs - **Partners:** per Stage · per Country · Partner Applications · Validated (per-group COUNT) - Opportunity & Partner record **side panels** via FIELDS_WIDGET views (surface relations incl. `applications`, so a brief shows all its applications). ## Known issues / follow-ups - **Pre-existing failing unit test (not introduced here):** `on-partner-application-created › "posts a Discord embed when an APPLICATION-sourced partner is created"` — the handler/test are byte-identical to base; tracked separately. - **Follow-up views** lack the "older than 7 days" staleness filter — no confirmed relative date operand in this Twenty version (TODOs left in the views). - **Apply UX (future):** a front-component "Apply" button on the brief, calling an authenticated `/s/apply-to-brief` logic function (runs as the app), would replace the "create a record" entry point — nicer UX, and it removes the RLS IS-EMPTY trade-off. Not required to ship. ## Testing - Partner self-apply verified end-to-end as a partner (create application from a brief → lands on `APPLIED`). - Unit tests for the WON/BACKUP cascade + application handlers pass (the one failing test above is the pre-existing, unrelated Discord handler). - Lint clean (`oxlint`). <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21816?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-light.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
242b989c0e |
fix(emails): stop reply composer infinite re-render (#21935)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21935?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. --> |
||
|
|
a5aac3c21e |
Logic function handler name hardened validation (#21956)
# Introduction Introduce centralized handlerName validation for the logic function handlerName inside the flat logic function validator Even if not safe by definition, avoid string interpolation inside the local driver executor when retrieving the handler name from the parent module <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21956?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. --> |
||
|
|
a884945aca |
fix(deps): remediate HIGH image vulns (multer, ws, nodemailer) (#21984)
## What Clears the HIGH-severity AWS Inspector findings on the `twenty-server` container image. All three have stable, in-range fixes — no prereleases. | Package | From → To | CVE | Path | |---------|-----------|-----|------| | multer | 2.1.1 → **2.2.0** (resolution) | CVE-2026-5038, CVE-2026-5079 (DoS) | transitive via `@nestjs/platform-express` | | ws | 8.20.1 → **8.21.0** (resolution) | CVE-2026-48779 | pinned by `@nestjs/graphql` (8.21.0 already in tree) | | nodemailer | 8.0.10 → **9.0.1** | GHSA-p6gq-j5cr-w38f | nested in `imapflow`; bumped `imapflow` 1.3.6 → 1.4.2 which depends on nodemailer 9.0.1 | ## Notes - **multer 2.2.0 is the stable fix.** The advisories ([CVE-2026-5038](https://advisories.gitlab.com/npm/multer/CVE-2026-5038/), [CVE-2026-5079](https://advisories.gitlab.com/npm/multer/CVE-2026-5079/)) list both `2.2.0` and `3.0.0-alpha.2` as fixed; Inspector reported only the `3.0.0-alpha.2` prerelease, but we stay on the stable 2.x line. - **nodemailer:** the top-level dep was already `^9.0.1`; only `imapflow`'s nested copy was stale. imapflow 1.4.0 still ships nodemailer 8.0.10 and 1.4.1 ships 9.0.0 (< the 9.0.1 fix), so **1.4.2 is the minimum** that pulls the patched nodemailer. - Lockfile-only resolution for multer/ws (they're transitive); imapflow is a direct dep bump. yarn.lock net-shrinks from deduping. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21984?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. --> |
||
|
|
6055262012 |
Deploy website only when twenty-website changes (#21977)
## Problem Every push to `main` redeploys the website. `cd-deploy-main.yaml` dispatches the infra `auto-deploy-main` fan-out, which unconditionally triggered `deploy-website`. Since most PRs don't touch `packages/twenty-website/**`, the majority were full, identical Cloudflare builds — wasted CI and noise. ## Fix Detect website changes here (using the existing reusable `changed-files.yaml`) and pass the result as a `deploy_website` input on the **single** `auto-deploy-main` dispatch: - New `website-changed-files` job checks `packages/twenty-website/**`. - The existing `auto-deploy-main` dispatch now carries `-f deploy_website=<true|false>`. - This repo keeps triggering **only** `auto-deploy-main` — it never dispatches `deploy-website` directly. `twenty-infra` decides whether to run the website deploy (per @FelixMalfait's review: the public repo shouldn't be able to run arbitrary `twenty-infra` workflows directly). - Server/front deploy is **unchanged** — still every merge. ## Paired change & merge order Companion PR: **twentyhq/twenty-infra#747** — adds the `deploy_website` input to `auto-deploy-main` and gates the website dispatch on it. **Merge twenty-infra#747 FIRST**, then this one. (If this merges first, it would pass an input the old `auto-deploy-main` doesn't accept, failing the dispatch. Infra-first only delays website auto-deploys until this lands — no breakage.) |
||
|
|
1646bdf35e |
Add twenty-partners on internal ci apps (#21975)
as title <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21975?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. --> |
||
|
|
d2083e7a1b |
Set OpenAI Responses store false for AI chat and agents (#20888)
## Summary This PR sets `openai.store = false` for Twenty's `@ai-sdk/openai` AI calls. This follows the approach discussed in #20877: instead of adding a new Twenty-specific Zero Data Retention config variable, OpenAI Responses calls no longer rely on OpenAI-stored response/item references. This should help Zero Data Retention organizations and may also avoid stale persisted-item replay errors for non-ZDR OpenAI users. Changes included: - Adds a shared OpenAI provider-options helper that merges `openai.store = false` for `@ai-sdk/openai` models. - Applies the helper to AI chat `streamText` calls. - Applies the helper to workflow/agent `generateText` calls. - Preserves OpenAI encrypted reasoning metadata through DB/UI message mappers so reasoning context can be replayed without stored OpenAI item references. - Does not add a new env/config variable. Related to issue #20877. ## Behavior / Tradeoffs This changes OpenAI Responses behavior for all Twenty OpenAI users, not only ZDR users. The intended benefit is that Twenty no longer depends on OpenAI-stored response/item references. The main tradeoff is reduced provider-side item-reference reuse for non-ZDR OpenAI users. To reduce the impact for reasoning models, this PR preserves `providerMetadata.openai.reasoningEncryptedContent` through message persistence/replay so reasoning context can still be provided without stored OpenAI item references. ## Tests - Focused server Jest tests for OpenAI provider-options merging and reasoning metadata mapping. - Focused frontend Jest test for reasoning metadata mapping. - `oxlint` and `oxfmt --check` on changed files. - `git diff --check`. --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> |
||
|
|
2dc6034a6c |
Add twenty meeting bot to internal application ci (#21974)
- remove useless github-connector (validated with @charlesBochet) - add twenty-meeting-bot to internal apps |
||
|
|
5f22908588 |
Decouple twenty-ui Avatar from app server-URL config (#21968)
Makes `twenty-ui`'s `Avatar` render the `avatarUrl` it receives instead of building it from `window._env_`/`window.location` at module load, so the library no longer depends on the app environment. URL resolution moves to `twenty-front` via a `getAbsoluteImageUrl` helper applied at the call sites. Part of making twenty-ui a standalone library. |
||
|
|
e59e102448 |
chore: bump version to 2.16.0 (#21973)
## 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/21973?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> |
||
|
|
96a2987610 |
Fix/sanitize chart filters on save (#21958)
# Fix: sanitize chart filters referencing deactivated/deleted fields ## Summary When a field used in a chart (graph) widget filter was later **deactivated or deleted**, saving the page layout failed with a backend error such as: > Chart "...": One of the chart filters uses "...", but it was deleted. Please remove or replace this filter rule. This happened even after the user tried to remove the offending filter rule, because the invalid filter could still end up in the saved configuration. This PR makes invalid chart filters get cleaned up reliably — both as the user edits filters and, as a safety net, at save time. ## Root causes - **Edit-time persistence kept invalid filters.** `handleFiltersUpdate` persisted the current filter state to the page layout draft without sanitizing it against the object's active fields. An invalid filter (referencing a deactivated/deleted field) was re-saved on every update, blocking the configuration from being accepted. - **Save never enforced the cleanup.** `useSavePageLayout` serialized the draft as-is. The "filters referencing deactivated/deleted fields will be automatically removed on save" promise shown in the warning banner was only honored reactively (when the filter panel was actively edited), never at the actual save boundary. A chart whose filter panel wasn't touched kept its stale invalid filter in the payload. - **Query time treated inactive fields as valid.** `useGraphWidgetQueryCommon` considered all fields (including inactive ones) valid, so deactivated-field filters were never dropped when running the chart query. ## Changes ### Edit-time (keeps draft and UI in sync as you edit) - `ChartFiltersSettings` — sanitize filters in `handleFiltersUpdate` before writing to the draft, dropping any filter whose `fieldMetadataId` is not in the active-fields set. - `dropChartRecordFiltersWithDeletedFields` — enhanced to also clean up filter groups left orphaned once invalid filters are removed (iteratively removing empty groups and re-parenting checks). - `useGraphWidgetQueryCommon` — restrict valid field IDs to `isActive` fields so deactivated-field filters are silently dropped at query execution. - `ChartFiltersDeletedFieldsWarning` — updated copy to mention both deactivated and deleted fields. ### Save-time safety net (guarantees no invalid filter is ever persisted) - New `sanitizeChartFiltersInPageLayoutDraft` util — walks every chart widget in the draft and drops record filters (and now-orphaned groups) whose `fieldMetadataId` is not in the widget object's set of active fields. It leaves non-chart widgets untouched and leaves filters intact when the object metadata can't be resolved (avoids wiping valid filters during metadata loading). - `useSavePageLayout` — builds a `Map<objectMetadataId, Set<activeFieldId>>` from `useObjectMetadataItems()` and sanitizes the draft before converting it to the update input. This layer only ever removes filters whose field is genuinely deactivated/deleted — the exact set the backend rejects — and never removes filters pointing at valid fields. ## Tests - `dropChartRecordFiltersWithDeletedFields.test.ts` — extended coverage for orphaned filter-group cleanup. - `sanitizeChartFiltersInPageLayoutDraft.test.ts` — new: drops deactivated/deleted-field filters on save, keeps valid filters, cleans up orphaned groups, leaves non-chart widgets alone, and leaves filters untouched when object metadata is unresolved. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21958?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> |
||
|
|
6ce7d04f9d |
Fix field widget textarea focus reset (#21959)
Short description: Keeps the field widget textarea on a local draft value while focused so record-store rewrites do not reset the active caret. # Before Typing in an editor-mode text field can lose caret position when the global record store is rewritten by an external record update/refetch. https://github.com/user-attachments/assets/1ee7a819-2c27-4d09-aae5-814c2cf27181 # After The focused textarea should preserve the in-progress draft and caret while still updating sibling previews optimistically and flushing the final value on blur. https://github.com/user-attachments/assets/41832493-021f-46e8-bc75-722bbb1cd7b7 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21959?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. --> |
||
|
|
b1ada79d72 |
fix(front): scope relation table widget via currentRecordId (#21965)
## Context Follow-up to #21293 (merged). That PR added a bespoke `relationTableFilter` to keep a relation field rendered as a record-table widget scoped to the host record. It turns out to be redundant. ## Why it's redundant The relation table widget's view already carries an `isCurrentRecordSelected` relation filter on the inverse field — it's baked in by `useAddDraftViewForFieldRelationTableWidget` when the widget is configured. That filter is resolved through the `currentRecordId` that `FieldWidgetRelationTable` provides via `RecordFilterValueDependenciesContext`, and `turnRecordFilterIntoGqlOperationFilter` turns it into exactly `{ `${inverseField}Id`: { in: [recordId] } }`. So the hand-built `relationTableFilter` duplicated a filter the existing mechanism already produces from `currentRecordId`. ## Changes - Remove `relationTableFilter` from `RecordFilterValueDependenciesContext` - Stop reading/applying it in `useFindManyRecordIndexTableParams` and `useAggregateRecordsForRecordTableColumnFooter` - Delete the `getRelationTableFilter` util and its test - `FieldWidgetRelationTable` provides `currentRecordId` only Net −263 lines; relies on the existing `isCurrentRecordSelected` + `currentRecordId` scoping path. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21965?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. --> |
||
|
|
8553c574db |
Improve twenty-ui packaging for standalone publishing (#21946)
Quick packaging wins to move twenty-ui closer to a standalone publishable library. - Move `react`/`react-dom` to `peerDependencies` (`^19.0.0`) so consumers provide a single React and we avoid duplicate-React bugs. They stay in `devDependencies` for the in-repo build, and `vite.config.ts` now derives the Rollup `external` list from peer deps too so React stays externalized instead of bundled. - Declare `type-fest` in `dependencies`. It was a phantom dep (resolved only via root hoisting) and its types are referenced by the emitted json-visualizer `.d.ts`, so standalone consumers need it. - Move build-only `glob` to `devDependencies` and add `typescript` (both used only by `generateBarrels.ts`). - Make `tsconfig.json` self-contained by inlining the base compiler options, and point the Vite `cacheDir`/`optimizeDeps.exclude` at package-local paths. Verified: typecheck, build (React confirmed externalized in `dist`, not inlined), dts emission, and unit tests all pass. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21946?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. --> |
||
|
|
2276e12c12 |
fix(server): skip callRecordings widget in calendar-event page sync when field is absent (#21967)
## Context On main's auto-upgrade, `SyncCalendarEventRecordPageCommand` (2.15.0 workspace command) failed for workspaces that don't have the call-recording feature metadata, aborting the upgrade with: ``` Migration action 'create' for 'pageLayoutWidget' (universalIdentifier: f473b435-...) failed Caused by: Field metadata not found for universal identifier: 48d6d151-... (calendarEvent.callRecordings) ``` ## Root cause The command always included the `callRecordings` page-layout widget. That widget's configuration references the `calendarEvent.callRecordings` relation field (`48d6d151`). Workspaces that never had the `callRecording` object / relation field synced fail transpilation with `ENTITY_NOT_FOUND`, and since one workspace failure aborts the segment, the whole upgrade stops. On the affected environment, ~half of active/suspended workspaces lack both the `callRecording` object and the `calendarEvent.callRecordings` field. ## Fix Only add the `callRecordings` widget when the `callRecordings` field actually exists in the workspace (checked via `flatFieldMetadataMaps.byUniversalIdentifier`). This mirrors the command's existing guard on the `calendarEvent` object. Workspaces without the field still get the fields / participants / timeline widgets; the callRecordings widget is simply skipped. The view fields for the record page do not reference `callRecordings`, so only the widget needed guarding. ## Test plan - [x] `nx typecheck twenty-server` - [x] `oxlint --type-aware` on the changed file: 0 errors - [ ] CI <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21967?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. --> |
||
|
|
6d7380dfec |
Remove unused call-recording application (#21966)
as title <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21966?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. --> |