dee653dfa6d21282ba3672cdbeffb0963c500e2e
13862 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dee653dfa6 |
fix: move message list member backfill command to 2.25 (#23271)
## What
Moves the `backfill-message-list-members-junction-target` workspace
upgrade command from `2.24.0` to `2.25.0`.
Introduced in #23176 (commit
|
||
|
|
ba5cb6ba15 |
fix(server): repair missing keyValuePair.applicationId on 2.23 upgrades (#23272)
Fixes #23254 ## Problem Upgrading a self-hosted instance from `2.23.x` to `2.24.0` leaves `core.keyValuePair` without the `applicationId` column. Database-backed config loading then fails on startup and on every refresh (~every 15s) with: ``` column KeyValuePairEntity.applicationId does not exist ``` The frontend shows "Unable to reach the backend". ## Root cause `AddApplicationIdToKeyValuePairFastInstanceCommand` was added in #23089 (after `2.23.x` shipped) but registered under the already-released `2.23.0` segment: ```ts @RegisteredInstanceCommand('2.23.0', 1784659343818) ``` The upgrade cursor is **positional and forward-only**: - `resolveStartCursor` resumes at `lastAttemptedIndex + 1`. A fully-upgraded `2.23.x` instance has its cursor at the last `2.23` workspace command, which sits *after* this newly-inserted fast command in the sequence. So the runner steps right over it and the DDL never runs. - The upgrade-aware metadata layer decides "applied" the same way (`stepIndex < currentCursor` in `upgrade-aware-entity-metadata.adapter.ts`). Since the step index is below the cursor, the column is considered applied and is **not** hidden from TypeORM SELECTs, so every query references a column that was never created. Fresh `2.24.0` installs replay the whole sequence, so only `2.23.x -> 2.24.0` upgrades are affected. The instance log `1 fast instance ... for 2.24.0` confirms the command landed in the `2.23.0` bundle rather than `2.24.0`. ## Fix - Add `RepairKeyValuePairApplicationIdFastInstanceCommand` under the current version (`2.24.0`) with a fresh timestamp, so it sorts last in the sequence and runs for every existing instance regardless of cursor position. Its DDL mirrors the original command and is fully idempotent (`ADD COLUMN IF NOT EXISTS`, `DROP INDEX IF EXISTS` + recreate, `ADD VALUE IF NOT EXISTS`), so it is a no-op on healthy instances. `down()` is intentionally empty: the column lifecycle is owned by the `2.23.0` introduction command. - Repoint the entity's `@WasIntroducedInUpgrade` to the new command so the column stays hidden from queries until the repair has actually run, eliminating the error window during the migration itself. ## Notes - `2.24.0` (`TWENTY_CURRENT_VERSION`) is the correct target: the upgrade sequence only covers previous + current versions, so a command under `2.25.0` (a next version) would not run. If a version bump lands before this merges, the command should be moved to the new current version. - Follow-up worth considering: nothing currently prevents registering a command under a version in `TWENTY_PREVIOUS_VERSIONS`. A startup validation rejecting that would have caught this at PR time. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23272?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. --> |
||
|
|
abe4d7491c |
Cap nested relation query concurrency (#23252)
## Context Common API queries load selected relations after fetching the root records. Relation loading is batched: one query pipeline loads a relation for all parent records, so this is not an N+1 problem. However, every sibling relation currently starts concurrently through `Promise.all`. Nested relations repeat the same behavior recursively. A wide selection can therefore submit many independent relation query pipelines at once. Existing query complexity and record limits restrict what can be requested, but they do not limit how much database work starts concurrently. ## What this changes This PR adds a request-local FIFO concurrency limiter for nested relation loading. - At most four `findRelations` pipelines execute concurrently. - One limiter is created for the outer relation-loading call. - The same limiter is shared by every recursive level. - Queued work starts as permits become available. - Permits are released in `finally`, including when a query fails. Conceptually: ```text Before: all sibling relations -> database concurrently nested siblings -> more database work concurrently After: all sibling relations -> FIFO queue -> at most 4 database pipelines nested siblings -> same FIFO queue and same limit ``` Note: Also addressing https://github.com/twentyhq/twenty/pull/23251#discussion_r3644510597 |
||
|
|
9390c28cb6 |
ci: report ci-shared-status-check on merge_group (#23276)
Follow-up to #23275. `ci-shared` was the one required check left off that batch, so `ci-shared-status-check` still sits at "Expected - Waiting for status to be reported" and blocks the merge queue. Same fix as the other workflows: add a `merge_group` trigger and gate `changed-files-check` with `if: github.event_name != 'merge_group'`. On a queued candidate, `changed-files-check` skips, `shared-test` (gated on `any_changed`) cascades to skipped, and the `always()`-gated `ci-shared-status-check` job reports success in seconds. The full suite still runs on `pull_request`. --- _Generated by [Claude Code](https://claude.ai/code/session_015mZozbvyvha1S6wsEVLBnF)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23276?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. --> |
||
|
|
29747f5d6b |
ci: report required status checks on merge_group so the queue isn't blocked (#23275)
Follow-up to #23216, which added the `merge_group`-triggered `upgrade-mutation-guard`. This makes the merge queue actually usable. ## Why The merge queue waits for every **required status check** to report a conclusion on the `merge_group` candidate commit, and there is no queue-only subset: it uses the branch's required status checks. Our required `ci-*-status-check` contexts only trigger on `pull_request`, so on a queued PR they sit at "Expected - Waiting for status to be reported" and block the queue until the status-check timeout (60 min), which then counts them as failed. Only `upgrade-mutation-guard` (from #23216) triggers on `merge_group`, so today it is the only check that reports in the queue. ## What Add a `merge_group` trigger to each of the seven required-check workflows and short-circuit the expensive work so the check reports success in seconds, while the full suite keeps running on `pull_request` to gate PRs. `upgrade-mutation-guard` stays the only check the queue genuinely validates against `main`. Mechanism: on `merge_group` the root jobs skip, everything downstream cascades to `skipped`, and the `always()`-gated `*-status-check` job runs, sees no failing needs, and succeeds. Kept as the same job in the same workflow so the required-check context is byte-identical to the PR-level one (a separate pass-through workflow could register a different context and not satisfy branch protection). Per workflow: - **ci-front**: trigger only. It already cascades - `changed-files-check` is `pull_request`-only and `front-sb-build` gates on `push || any_changed`, so nothing runs on `merge_group`. - **ci-server**: trigger + `if: github.event_name != 'merge_group'` on the three ungated root jobs (`changed-files-check`, `upgrade-changed-files-check`, `server-previous-version-upgrade-mutation-guard`). The guard would otherwise fail on `merge_group` since `pull_request.base.sha` is empty there; the queue-side guard in `ci-merge-queue.yaml` already covers that case. - **ci-sdk / ci-website / ci-test-docker-compose**: trigger + the same guard on `changed-files-check`. - **ci-twenty-apps**: trigger + the guard on `discover` (its `ci`/`integration` jobs gate on `discover` output, so they cascade off). ## Settings note This complements the branch-protection changes for the queue (enable the queue, max group size 1, per #23216). If the branch has **other** required checks beyond these seven whose workflows are `pull_request`-only, they need the same `merge_group` treatment or the queue will wait on them too. --- _Generated by [Claude Code](https://claude.ai/code/session_015mZozbvyvha1S6wsEVLBnF)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23275?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. --> |
||
|
|
bb4e427196 |
ci: run upgrade mutation guard in the merge queue against main (#23216)
Follow-up to #23215 (merged). Rebased on `main`. ## Why #23215 fixes an instance of a class of bug: an upgrade command whose version is chosen at `generate:instance-command` time from `TWENTY_CURRENT_VERSION`, then left behind when `main` bumps the version before the PR merges (base-drift). The command ships one minor early and instances already on the newer version skip it forever. The existing `server-previous-version-upgrade-mutation-guard` in `ci-server.yaml` runs on `pull_request`, so it validates against the PR's base. When the base is stale (main moved after the branch was cut), the guard reads the branch's own `TWENTY_CURRENT_VERSION` and the check passes even though the command is now a version behind main. That is exactly how the original bug slipped through. ## What The version-directory and append-only-timestamp validation is extracted into a shared composite action, `.github/actions/upgrade-mutation-guard`, diffed against a caller-supplied `base_sha`. It is called from two places: - **`ci-server.yaml`** (PR-level guard, `base = pull_request.base.sha`) for fast feedback. The job keeps its existing name/check. This replaces ~290 lines of inline shell. - **`ci-merge-queue.yaml`** (new, `merge_group`-triggered, `base = merge_group.base_sha`). GitHub builds each merge-queue candidate on top of the current tip of `main`, so the checks read `TWENTY_CURRENT_VERSION` and the existing per-directory timestamps from main's real state at merge time. Because the candidate is rebased onto main, base-drift is caught by construction: the same validation simply runs where the base is guaranteed current. No origin/main comparison hack; the logic now lives in one place. ## Bypass semantics The guard has two independent checks, and they are treated differently on purpose: - **Version-directory check** keeps its `ci:allow-previous-version-upgrade-mutation` bypass, a deliberate, reviewed escape hatch for legitimately touching a previous-version directory. The PR-level guard reads the label directly; the merge-queue guard resolves it from the queued PR (the `merge_group` event carries no labels) and passes it to the composite action, which skips only the version-directory step. - **Timestamp / append-only check has no bypass.** The old `ci:allow-upgrade-command-timestamp-exception` label is removed. A fake or out-of-order timestamp rewinds the upgrade cursor and re-hides already-applied columns, so there is no "allowed" version of it: the timestamp just has to be configured correctly (real epoch millis, strictly greater than every existing command in the same version directory). If a blocking existing max is itself a fabricated future timestamp, re-slot that command to its real merge epoch rather than reaching for a bypass. Preventing previous-version mutation is the guard's primary purpose. In the merge queue the guard job always runs and skips only the version-directory step when the bypass label is set, so it reports a real success/failure (the required check never resolves to a skipped state, and a label-lookup failure fails closed) and the timestamp check always runs. ## Requires a settings change (not in this diff) Enabling the merge queue and marking the check required are branch-protection settings, not file changes. After merge, an admin needs to: 1. Enable the merge queue for `main` in branch protection. 2. Add `CI - Merge Queue / upgrade-mutation-guard` to the merge queue's required checks. ## Notes - Composite action, not a `workflow_call` reusable workflow, deliberately: converting the `ci-server.yaml` job to a reusable-workflow call would rename its status check to `server-previous-version-upgrade-mutation-guard / ` and break that required-check mapping in branch protection. A composite action dedups the logic while keeping both callers' check names intact. --------- Co-authored-by: Paul Rastoin <paul.rastoin@gmail.com> |
||
|
|
c0c5ba33e8 |
i18n - translations (#23269)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23269?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> |
||
|
|
b61134ea5b |
i18n - translations (#23268)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
923035bf48 |
Extract front component host wrapper into hooks (#23262)
Refactors `createHtmlHostWrapper` into composable hooks (`useHtmlHostElementProps`, `useComposedElementRef`, `useCaretPreservingElementRef`) as groundwork for the geometry mirror. Behavior-focused, no feature change: - Caret preservation moves to a stable ref + `useLayoutEffect` re-assertion (covered by the caret suites). Highest regression surface in the series, isolated here for focused review. - The remote `ref` prop is now swallowed via `INTERNAL_PROPS` instead of leaking onto host elements. First of three PRs splitting the geometry mirror work. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23262?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. --> |
||
|
|
3ee8fc0973 |
Add front component skeleton loader (#23261)
Front components (dashboard widget, side panel, settings preview) showed blank space during their entire load. They now show a shimmering full-area skeleton continuously, from the lazy chunk load through metadata fetch, token/SDK wait, and worker boot, until the real UI mounts. The skeleton is threaded down as an optional `loadingFallback` prop so the shared `twenty-front-component-renderer` package stays dependency-free (react-loading-skeleton stays in twenty-front). The command-menu headless component opts out by not passing a fallback, so it stays blank as before. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23261?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. --> |
||
|
|
3a8f086d15 |
Converge drag and drop on shared dnd-kit primitives, remove @hello-pangea/dnd (#23211)
Follow-ups recorded in #23023, done in one pass. ## Shared primitives - Folded `PageLayoutWidgetSortableItem` and `PageLayoutWidgetDropLine` into the shared `DragDropItemSortableCell` / new `DragDropItemDropLine` (new `data`, `dropLine`, `highlightWhileDragging`, `hasTransition` props). - Added generic `DragDropProviderDragStartEvent` (and DragMove/DragOver/DragEnd/DropTarget) helpers and deleted the 7 copied `Parameters<...>` extractions across the dnd hooks. - Replaced the `useMovePageLayoutWidgetUp/Down` implementations (~140 lines) with `moveWidgetWithinTabInDraft`. - Migrated the remaining page-layout test suites onto `pageLayoutDraftFixtures`. ## Tab reordering off Pangea - Tabs are sortable cells on the same provider as widget drags, segregated by dnd type, so widget drops on tab buttons keep working while tabs reorder. - Reordering is ID based (`reorderTabInDraft`: insert before the hovered tab), which keeps the pinned first tab in place without index arithmetic. - Preserved overflow behaviors: the dropdown stays open while a tab drag is in flight, dropping a tab on the "+N More" button appends it and opens the dropdown, and both the visible strip and the overflow list have end drop zones. ## Fields configuration editors off Pangea - Group reorder, field reorder and cross-group field moves now run on the shared cells (same drop line and end-zone patterns). ## DraggableList off Pangea - `DraggableList` / `DraggableItem` keep their consumer-facing API — the ~9 consumers now type their handlers with a local `DraggableListDropResult` instead of pangea's `DropResult` — but run on the shared sortable cells; each list's uuid group doubles as its dnd type so nested lists stay isolated from page-level providers. - Items register their index in a list-scoped registry so the end drop zone can resolve the append index at drop time (with insert-before semantics an item could otherwise never reach the last position). - Deleted three dead files that only existed for pangea plumbing (the side panel navigation placeholder, `getCssCompatibleDraggableProps`, the orphaned `recordGroupPendingDragEndReorderState`). ## Record table row drag off Pangea - Rows register through `useSortable` directly on the row element — no wrapper div, so row CSS, sticky cells and virtualization stay untouched — with the grip cell wired as the drag handle via the shared sortable handle ref context. - Both table modes (virtualized flat list and record groups) share a `DragOverlay` clone that replaces pangea's virtual-mode `renderClone`, and end drop zones per record group (and after the virtualized list) allow dropping after the last row or into an empty group. - The drop handlers keep their pangea-shaped result object, retyped as a local `RecordDragDropResult`, so the position computation logic is untouched. ## Pangea removed `@hello-pangea/dnd` is gone from `package.json` and the lockfile, along with its orphaned transitive entries (`css-box-model`, `raf-schd`, `react-redux`, `redux`). Nothing in the repo imports it anymore. ## Dashboards: cross-tab widget drag for grids react-grid-layout drags never enter dnd-kit, so the bridge hit-tests the pointer against the tab buttons' `data-page-layout-tab-drop-target-id` rects during grid drags, highlights the hovered tab through state, and on drop moves the widget to the destination grid below its existing content (`moveWidgetToGridTabInDraft`, `buildTabWidgetLayouts`). The grid's own post-drag layout commit is suppressed once so it does not overwrite the cross-tab move. ## Fixes found while testing - With `feedback: 'clone'`, the drag source is its own initial drop target and its placeholder is a DOM clone taken at drag start, so the drop line rendered into the source got baked into the placeholder and stuck there for the whole drag. The line is now hidden on the source cell, leaving a single indicator at the actual target. - Reorderable tabs collapsed to text height and sat top-aligned next to "+ New Tab" because the sortable cell wrapper defaults to `display: block; height: auto`, breaking the tab height chain — the tab list now uses the cell's `fill` mode so tabs stretch to the strip height again. ## Testing Playwright against the dev app: - Record page: widget reorder up and down in the pinned column (single blue drop line at the target), drag to another tab via its tab button (highlight + move), drag back into content at a specific position, chained cross-tab moves, tab reorder with vertical drop line, new tab creation. - Overflow (narrow viewport): drop a tab on "+N More" (appends last, dropdown opens), reorder inside the dropdown (stays open), drag a tab from the dropdown back to the visible strip. - Dashboard: grid drag within a tab, cross-tab drag onto a tab button (hover highlight, widget lands below destination content, remaining widgets keep their positions), save and reload persistence in both directions. - Fields editor: field reorder, group reorder, field move across groups, plus the Move Up / Move Down widget actions. Since the pangea-removal commits: - Typecheck, oxlint and oxfmt green over the full front source; unit suites green including the migrated `useStartRecordDrag` test (jest needed a scoped transform exemption for `@preact/signals-core` once dnd-kit reached the side-panel suites). - Storybook visual regression unchanged across ~700 stories — expected, since the migrated surfaces render identical DOM at rest (drop lines and drag overlays only exist mid-drag). - The tab strip fix reverses the exact regression mechanism: the sortable cell wrapper defaulted to `display: block; height: auto`, collapsing the tab height chain next to the full-height "+ New Tab" button; `fill` restores the stretch. --- _Generated by [Claude Code](https://claude.ai/code/session_01XKRCzzu8oGyocXZtFp7VEG)_ <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23211?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> |
||
|
|
d53ef11fe2 |
fix: bump linkify-it to 5.0.2 (Dependabot) (#23236)
Bumps **linkify-it -> 5.0.2** (sole descriptor `^5.0.1`, caret already permits it; recursive `yarn up`, lockfile-only, no resolution). Clears [1799](https://github.com/twentyhq/twenty/security/dependabot/1799) (GHSA-v245-v573-v5vm, high). `yarn install --immutable` passes. 5.0.2 published 2026-07-01, clears the age gate. |
||
|
|
bb22b216db |
Fix front components rendering a blank panel in Firefox (#23213)
Fixes #22973 In Firefox, front components rendered a blank panel with only `DataCloneError: Exception object could not be cloned` in the console. Accessing `caches` in the opaque-origin sandbox worker throws a Gecko `Exception` (worker-side CacheStorage code up to v2.22, or any component code touching it since), and `@quilted/threads` posts thrown values raw over the MessagePort. Firefox cannot structured-clone these exceptions, so the error report itself failed and the render promise never settled. Thread errors are now flattened to clonable payloads and rehydrated on the other side, so the real error surfaces in the error box instead of silently hanging the panel. Also makes the CacheStorage guards exception-safe (v2.23 already moved that code host-side, which removed the main trigger). Verified end to end in stock Firefox 149: before, a component touching `caches` hangs silently; after, render rejects with the full `NS_ERROR_FAILURE` diagnostic. Chromium behavior unchanged. ```mermaid sequenceDiagram participant Host as Host (React) participant Worker as Sandbox worker (null origin) participant Threads as @quilted/threads rect rgb(250, 235, 235) note over Host,Threads: Before — Firefox hangs Host->>Worker: render(component) Worker->>Worker: throws Gecko Exception<br/>(typeof caches) Worker->>Threads: postMessage(rawException) Threads--xHost: DataCloneError:<br/>Exception could not be cloned note over Host: CALL_RESULT never arrives<br/>render() promise never settles → blank panel end rect rgb(232, 245, 233) note over Host,Threads: After — error surfaces Host->>Worker: render(component) Worker->>Worker: throws Gecko Exception Worker->>Threads: serialize → { name, message, stack } Threads->>Host: postMessage(clonable payload) Host->>Host: rehydrate → Error, reject render() note over Host: error box shows NS_ERROR_FAILURE end ``` <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23213?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. --> |
||
|
|
ae2c8978d1 |
i18n - docs translations (#23258)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
e5c9fcf058 |
Add PostgreSQL connection pool pressure metrics (#23251)
## Summary - Add pool gauges for total, idle, waiting, and maximum connections - Record PostgreSQL connection acquisition duration and failures - Instrument core, workspace primary, and optional replica data sources - Add unit tests covering gauges, acquisition timing, failures, and deduplication <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23251?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. --> |
||
|
|
d0863dd1f7 |
i18n - translations (#23253)
Created by Github action --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
1fdb5605f1 |
feat: kanban, calendar and grouped-table layouts for relation field widgets (#23112)
## Context A relation field widget on a record page can already embed a record-scoped view rendered as a **table** (`FieldDisplayMode.TABLE`) — e.g. a Company's Opportunities. This brings **kanban, calendar and grouped-table** to that same embedded view, so the board/calendar stays scoped to *this* record's related records (not a standalone all-records widget — that was the earlier #23003 approach, closed). Builds directly on the merged dashboard widget layouts (#22963), reusing its renderer, draft/save pipeline, and settings dropdowns. ## Approach — extend the existing "Table" display mode The relation field widget already stores a `viewId` and renders it through the layout-agnostic `RecordTableWidgetRendererContent` (which branches on the embedded view's `type`), scoped to the current record via `RecordFilterValueDependenciesContext`. So rendering + persistence already work for any widget view type — only the authoring UI and one server gate were missing. **No new `FieldDisplayMode`, no data migration.** ## Server - `view-widget-upsert.service.ts`: a field widget in table display mode (`isFieldTableWidget`) could already persist viewFields/filters/sorts through this path, but was **blocked from updating view settings** (`type` / group-by / calendar), pinning its embedded view to a table. The widget-type guard earlier in the method already rejects every widget kind other than record-table and field-table, so the now-redundant record-table-only guard on the view-settings branch is dropped. The allowed-widget-view-types check and the downstream group-by / calendar-field validations still apply equally. ## Frontend - **One merged Layout picker.** The field widget's Layout dropdown lists **Field / Card / Table / Kanban / Calendar** in a single flat list — you pick Kanban directly, instead of "Display as: Table" first and a separate embedded-view layout second. Picking a view layout selects the `TABLE` display mode under the hood, seeds the record-scoped embedded view on first use (with a default group-by / date field), and applies the layout in the same click. Kanban/Calendar are disabled with a hint ("Needs a Select field" / "Needs a Date field") when the relation target can't support them — same gating as the dashboard picker. The row's icon and description reflect the effective selection (e.g. Kanban), and the dropdown mounts the draft-init effect so switching straight from Field/Card to Kanban works before the table renderer has ever mounted. - **Contextual rows** (Group by / Date field / Calendar view / Hide empty groups) extracted from the dashboard panel into a reusable `WidgetViewLayoutSettingsRows` (source object passed in — fixed to the relation target; no Source / Limit rows) and surfaced under the picker while a view layout is active. Its standalone layout row is hidden here (`isLayoutRowHidden`) since layout lives in the merged picker. - Reuses the dashboard draft snapshot + `upsertViewWidget` save pipeline and the group-by/calendar dropdown components unchanged. ## Scope - **One-to-many relations only** (matches the existing `getFieldWidgetAvailableDisplayModes` gate; junction / many-to-many stay table-only — a pre-existing inconsistency left untouched here). - Field-widget **calendars inherit the dashboard's behavior** (month read-only by default; day/week + drag-to-reschedule only behind `IS_CALENDAR_WEEK_VIEW_ENABLED`), since it's literally the same renderer. ## Tests - Server integration (`upsert-view-widget-view-settings.integration-spec.ts`): a FIELD + TABLE widget can switch its embedded view to `KANBAN_WIDGET` (with group-by) and `CALENDAR_WIDGET` (with date field), and the kanban group-by validation still applies through the newly-opened path. - Front unit: `getWidgetViewLayoutSettingsItemIds` (keyboard-nav row ids per layout/flag/group state). ## Follow-ups (intentionally not in this PR) - Migrate the dashboard settings panel onto the shared `WidgetViewLayoutSettingsRows` (kept out to avoid churning the just-merged #22963 file; behavior-preserving refactor). https://claude.ai/code/session_01E5N87kwwZWhDtEQaP72cMf |
||
|
|
a3a6a55051 |
i18n - docs translations (#23250)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
fdb8865933 |
test: cover uninstall logic function hook execution (#23249)
Follow-up to #23227 ([review comment](https://github.com/twentyhq/twenty/pull/23227#pullrequestreview-4771629391)): adds integration coverage for the `defineUninstallLogicFunction` hook execution on uninstall. ## What New integration suite `successful-uninstall-application-logic-function-hook.integration-spec.ts` that drives the real `syncApplication` / `uninstallApplication` GraphQL flow and asserts on the executor wiring: - When the synced manifest declares an `uninstallLogicFunction`, uninstalling the application resolves the hook and calls `LogicFunctionExecutorService.execute` exactly once, before deletion, with the `{ version }` payload. - When the manifest declares no uninstall hook, uninstalling the application does not call the executor. The executor is spied via the running app container (`getAppProviderByClassName`) and stubbed to a success result, so the test verifies the server-side resolution/trigger path deterministically without depending on the local function runtime. ## Test plan - `npx jest --config ./jest-integration.config.ts successful-uninstall-application-logic-function-hook` passes (2/2). - Typecheck green for twenty-server; oxlint and oxfmt clean on the new file. --- _Generated by [Claude Code](https://claude.ai/code/session_016zJPggkVEw1V7SqnPSiUQx)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23249?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: martmull <martin@twenty.com> |
||
|
|
f793f3c5a9 |
Fix application logos resolving to null on install and sync (#23245)
## Problem Application icons are no longer resolved: `logo` is `null` on `FindManyApplications` / `FindOneApplication`, so app chips and the settings applications table fall back to initials avatars. ## Root cause Manifests produced by the current SDK carry the logo path in `manifest.application.logo`; `logoUrl` is deprecated and stripped by `normalizeApplicationAssets` (external URLs are dropped, relative ones are moved to `logo`). Three server call sites still read only the deprecated `logoUrl`: - `application-sync.service.ts` (`syncApplication`): wrote `logo: manifest.application.logoUrl ?? null` on every install/upgrade/dev sync, overwriting `application.logo` with `null` - `application-sync.service.ts` (`buildVirtualDryRunFlatApplication`): same read on the dry-run path - `application-install.service.ts` (`ensureApplicationExists`): same read on the create path Since both `Application.logo` and the `Application.logoUrl` resolve field derive from that column, icons went null everywhere. Separately, OAuth-only apps (e.g. "Twenty CLI" from dynamic client registration) never get a logo at all: `OAuthRegisterInput` accepts `logo_uri` but the registration controller dropped it, so `applicationRegistration.logoUrl` also resolves to null for those. ## Fix - Read `manifest.application.logo ?? manifest.application.logoUrl ?? null` at all three sites, matching the fallback already used by `importLogoFile` and `fromManifestApplicationToDisplayFields` - Persist `logo_uri` into `applicationRegistration.logo` on OAuth dynamic client registration; `buildLogoUrl` passes absolute URLs through, so the consent screen and app chips can resolve it Existing rows that were already nulled will self-heal on the next app upgrade/sync, since the sync path rewrites `logo` from the manifest. --- _Generated by [Claude Code](https://claude.ai/code/session_01Us7BE5yBguYTACg5H3Y85z)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23245?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. --> |
||
|
|
f7683b7e47 |
i18n - translations (#23247)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
09e20eee5f |
i18n - translations (#23246)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23246?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> |
||
|
|
abc82d66b7 |
Consolidate per-queue worker tuning in one explicit config file (#23229)
## Context
Follow-up to the worker configuration analysis and to the worker pool
split rolled out in twentyhq/twenty-infra#805/#806. Worker tuning was
previously spread across two partial constants (`QUEUE_WORKER_OPTIONS`,
`MESSAGE_QUEUE_PRIORITY`), and most queues silently relied on implicit
BullMQ defaults.
## What this PR does
Introduces a single dedicated file to pilot worker behavior per queue:
`src/engine/core-modules/message-queue/message-queue-worker-config.constant.ts`
`MESSAGE_QUEUE_WORKER_CONFIG` declares, for **every** queue, an
explicit:
- `priority` (applied when enqueuing, lower runs first)
- `concurrency`
- `lockDuration`
- `maxStalledCount`
- `boundedShutdownDrain`
Explicitness is enforced at compile time: the record is typed
`Record<MessageQueue, { priority: number; workerOptions:
Required<MessageQueueWorkerOptions> }>`, so adding a queue without
declaring its full configuration is a type error, and no field can be
omitted.
Wiring changes:
- `message-queue.explorer.ts` passes
`MESSAGE_QUEUE_WORKER_CONFIG[queueName].workerOptions` when creating
workers
- `bullmq.driver.ts` reads the enqueue priority from the same record
- `message-queue-worker-options.constant.ts`,
`message-queue-priority.constant.ts` and
`ai-stream-lock-duration.constant.ts` are removed (the AI stream lock
duration is inlined into the one config entry that used it)
## Behavior
No behavior change — the previously implicit BullMQ defaults
(concurrency 1, lockDuration 30s, maxStalledCount 1) are now spelled out
per queue, and the existing overrides (`ai-stream-queue`: concurrency 20
/ 10 min lock / no stall retry / bounded shutdown drain;
`logic-function-queue`: concurrency 10) and all priorities are carried
over unchanged.
## Validation
- `npx nx typecheck twenty-server` ✅
- `oxlint --type-aware` + `oxfmt --check` on changed files ✅
- `npx jest "message-queue"` (7 tests) ✅
Companion infra PR: twentyhq/twenty-infra#808 moves the worker pool
topology (replicas, resources, queue filters) into a dedicated
`workers.yaml` per environment.
Session: https://claude.ai/code/session_01TL6Te48Lkys5NxyG9j2Nz6
|
||
|
|
91f0b77fdb |
Only suggest vetted apps in onboarding install step (#23222)
The onboarding "Install your first apps" step now only suggests marketplace apps where `isVetted` is true. |
||
|
|
fb52635d2a | Add defineUninstallLogicFunction hook for applications (#23227) | ||
|
|
cada1ef6d7 |
feat(website): live community stats, drop hard-coded fallback (#23047)
## Problem The menu's GitHub star and Discord member counts almost always render the hard-coded snapshot (49.6K / 6.6K, frozen June 2026), not live numbers. The render-time fetches run unauthenticated from Cloudflare Workers, whose egress IPs are shared across tenants; GitHub's unauthenticated quota is 60 req/hr per IP, so the call is effectively always rate-limited. Live prod today shows the frozen 49.6K GitHub count next to a live Discord count, confirming only GitHub is affected. ## Change - GitHub fetch sends `Authorization: Bearer $GITHUB_STATS_TOKEN` when the env var is set, moving it onto its own 5,000 req/hr quota. Discord keeps the public invite endpoint, which works fine from Cloudflare's IPs. - The hard-coded fallback is deleted rather than refreshed. `CommunityStats` fields are now `number | null`, resolved live -> last-good -> null, and the menu renders an icon-only chip when a count is genuinely unavailable. A fake number can never ship. - Last-good values persist in the worker's existing OpenNext R2 bucket (`NEXT_INC_CACHE_R2_BUCKET`, key `community-stats/latest.json` outside the `incremental-cache/` prefix), so a third-party outage shows the numbers from the previous refresh. No new infrastructure. - Revalidation tightens from 1h to 15min (~4 GitHub calls/hour, shared cache entry across pages). - `MenuSocial`/`MenuDrawer` import `formatCompactCount` from its module directly: the community barrel now re-exports server-only code, and a client value-import would pull `getCloudflareContext` into the client bundle. ## Rollout - twentyhq/twenty-infra#795 passes the built-in Actions token at build time so prerendered pages ship with a real star count from the first request after deploy. - One manual step: set the `GITHUB_STATS_TOKEN` secret (fine-grained PAT, public read-only, no permissions) on the twenty-website-dev and twenty-website-prod workers. Until it exists, behavior degrades to today's minus the fake numbers. ## Tests 5 unit tests cover the resolution ladder: live wins, cache fills a failed fetch, null on cold-cache failure, both-fail serves cache without overwriting, nothing written when nothing succeeded. Verified against the dev server: menu renders live 53.3K / 6.9K. |
||
|
|
d1b556f4a8 |
i18n - docs translations (#23244)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23244?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> |
||
|
|
7f1e3d3541 |
fix(admin): prevent fallback to 'latest' string when dockerhub tags c… (#22885)
Fixes #22849 ### What changed? When the `AdminPanelVersionService` hits a DockerHub API error (or filters out all valid tags), it was previously hardcoded to return `'latest'`. This caused the frontend to display `Latest version: latest`. I updated the GraphQL DTO to make `latestVersion` nullable, and modified the service fallback and unit tests to return and expect `null` instead. The frontend (`SettingsAdminVersionDisplay`) already has logic to handle a falsy version and gracefully display `No latest version found`, so this backend fix entirely resolves the UX issue without touching the frontend. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22885?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. --> |
||
|
|
0a53cc745d |
fix: bump brace-expansion 1.x/2.x lines (Dependabot) (#23240)
Bumps **brace-expansion 1.x -> 1.1.16** and **2.x -> 2.1.2** (caret consumer ranges permit both; recursive `yarn up`, lockfile-only, no resolution). Clears [1766](https://github.com/twentyhq/twenty/security/dependabot/1766) and [1767](https://github.com/twentyhq/twenty/security/dependabot/1767) for GHSA-3jxr-9vmj-r5cp (high, ReDoS). **Deliberately not covered:** the 5.x line stays at 5.0.6 because `nx@22.7.5` pins it exact (minimatch's `^5.0.x` consumers do lift to 5.0.7, but the nx copy remains), so alert [1765](https://github.com/twentyhq/twenty/security/dependabot/1765) stays open. Same nx-exact-pin situation as axios; will be handled in the resolutions batch. `yarn install --immutable` passes. 1.1.16 and 2.1.2 published 2026-07-08, clear the age gate. |
||
|
|
d6abb26cdb |
fix: bump body-parser to 1.20.6 / 2.3.0 (Dependabot) (#23239)
Bumps **body-parser** on both lines: `~1.20.3`/`~1.20.5` -> **1.20.6** and `^2.2.1` -> **2.3.0** (all within declared ranges; recursive `yarn up`, lockfile-only, no resolution). Clears [1779](https://github.com/twentyhq/twenty/security/dependabot/1779) and [1780](https://github.com/twentyhq/twenty/security/dependabot/1780) (GHSA-v422-hmwv-36x6, low). `yarn install --immutable` passes. 1.20.6 published 2026-07-09, 2.3.0 published 2026-06-15, both clear the age gate. |
||
|
|
78e0c11b34 |
fix: bump svgo to 3.3.4 (Dependabot) (#23238)
Bumps **svgo -> 3.3.4** (sole descriptor `^3.0.2`, caret already permits it; recursive `yarn up`, lockfile-only, no resolution). Clears [1802](https://github.com/twentyhq/twenty/security/dependabot/1802) (GHSA-2p49-hgcm-8545, high). `yarn install --immutable` passes. 3.3.4 published 2026-07-11, clears the age gate. |
||
|
|
35e6f58d70 |
fix: bump dompurify to 3.4.12 (Dependabot) (#23237)
Bumps **dompurify -> 3.4.12** (sole descriptor `^3.4.11`, caret already permits it; recursive `yarn up`, lockfile-only, no resolution). Clears [1801](https://github.com/twentyhq/twenty/security/dependabot/1801) (GHSA-c2j3-45gr-mqc4, low). `yarn install --immutable` passes. 3.4.12 published 2026-07-11, clears the age gate. |
||
|
|
b2a61adee7 |
fix: bump immutable to 5.1.8 (Dependabot) (#23235)
Bumps **immutable -> 5.1.8** (sole descriptor `^5.1.5`, caret already permits it; recursive `yarn up`, lockfile-only, no resolution). Clears [1796](https://github.com/twentyhq/twenty/security/dependabot/1796) (GHSA-v56q-mh7h-f735, high) and [1797](https://github.com/twentyhq/twenty/security/dependabot/1797) (GHSA-xvcm-6775-5m9r, high). `yarn install --immutable` passes. 5.1.8 published 2026-06-25, clears the age gate. |
||
|
|
5ee50b16db |
fix: bump fast-uri to 3.1.4 (Dependabot) (#23234)
Bumps **fast-uri -> 3.1.4** (sole descriptor `^3.0.1`, caret already permits it; recursive `yarn up`, lockfile-only, no resolution). Clears [1798](https://github.com/twentyhq/twenty/security/dependabot/1798) (GHSA-4c8g-83qw-93j6, high) and [1805](https://github.com/twentyhq/twenty/security/dependabot/1805) (GHSA-v2hh-gcrm-f6hx, high). `yarn install --immutable` passes. 3.1.4 published 2026-07-19, clears the 3-day age gate. |
||
|
|
148dc6dfaa |
Let server route resolvers answer the caller synchronously (#23233)
## Problem
A server route resolver can only return a dispatch target (`{
workspaceId, targetLogicFunctionUniversalIdentifier, payload }`), and
`ServerRouteTriggerService` always acks `202 {queued:true}`. The target
function runs off the queue, after the response has been sent, so its
return value can never reach the caller.
That makes it impossible to integrate a provider whose webhook URL has
to be proven with a handshake on the same response. Slack's Events API
is the case that surfaced it: `url_verification` sends `{ type,
challenge }` and will not accept the Request URL unless the challenge
comes back on that POST.
## Change
A resolver may now return a `Response` (the existing
`LogicFunctionHttpResponse`) instead of a dispatch target. The route
sends it as-is via `buildRouteTriggerResponse` and enqueues nothing.
- Reuses the marker and builder that HTTP route triggers already use, so
there is no new response shape.
- Dispatch results behave exactly as before; the resolver error path is
unchanged, just hoisted out of `parseResolverResult` so it runs before
the branch.
- SDK: `ServerRouteResolverResult` becomes `ServerRouteDispatchResult |
LogicFunctionHttpResponse`.
Additive: a resolver that returns a dispatch target sees no behavior
change. Previously, returning this shape threw
`RESOLVER_INVALID_RESULT`.
## Testing
`server-route-trigger.service.spec.ts` gains a case asserting the
resolver's response is sent verbatim and nothing is enqueued. 15/15
pass.
## Context
Split out of #22984 (Slack conversational assistant), which needs this
to complete the Slack Events URL verification. That PR depends on this
one merging first.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23233?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. -->
|
||
|
|
5bc97f8591 |
chore: sync AI model catalog from models.dev (#23242)
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/23242?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> |
||
|
|
6b99bcea7f |
feat(partners): require Twenty experience on apply, drop Cal success (#23223)
## Summary - Add a dedicated **Experience** step to the partner apply wizard (milestones, ≥200-char narrative, proof URL) before commercials - Stop collecting `applicationNotes`; rename Expertise chrome away from “experience” - Replace the post-submit Cal.com embed with a review-and-reach-out thank-you so unqualified inbound no longer books intros automatically **Companion PR (app):** #23224 — Partner schema, submit persistence, triage views, Tally CSV mapper (`twenty-partners` v1.4.0). Land the app PR with or before this one. ## Test plan - [ ] Open apply modal: wizard order is identity → profile → expertise → experience → commercials - [ ] Experience step blocks continue without ≥1 milestone, narrative ≥200 chars, and a valid https URL - [ ] Submit creates/updates Partner with the three experience fields (with #23224 deployed) - [ ] Success screen has no Cal embed / book-later CTA - [ ] `npx jest --config=jest.config.mjs partner-application` passes locally (71 tests) --------- Co-authored-by: Abdullah <125115953+mabdullahabaid@users.noreply.github.com> |
||
|
|
34c2e11dcb |
i18n - translations (#23230)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23230?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> |
||
|
|
2c79093b74 |
feat: agent roleUniversalIdentifier for manifest-driven role assignment (#23206)
## Summary - Adds optional `roleUniversalIdentifier` on `AgentManifest` / `defineAgent` so apps can declaratively assign a role to an agent (same config shape as `defaultRoleUniversalIdentifier`). - Wires `agentUniversalIdentifier` as a sync many-to-one FK on `roleTarget`, and emits a deterministic `roleTarget` from the agent during app sync (create / update / delete). - Enables app agents (e.g. Slack assistant) to get a role on install without postInstall hooks or manual admin assignment. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23206?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. --> |
||
|
|
8b7ac02464 |
i18n - translations (#23226)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
18fb0946e6 |
v1.4.0 — Raise partner bar: Twenty experience fields + triage (#23224)
## Summary **Version:** `twenty-partners` **v1.4.0** (minor — new Partner fields + apply contract) - Add Partner fields `twentyExperience`, `twentyExperienceNotes`, `twentyExperienceProofLink` and persist them from `submit-partner-application` (≥200-char narrative at API boundary) - Surface Twenty experience on applications / validated / per-stage triage views and the Partner record side panel (drop empty Introduction from that panel) - Add pure Tally CSV match/map helpers (ops import script stays outside the repo) for backfilling existing partners by `partnerId` **Companion PR (website):** #23223 — Experience step on apply + thank-you without Cal. ## Test plan - [ ] `yarn twenty apply -r <remote>` on a workspace — Partner gains the three experience fields - [ ] Website apply (with #23223) persists milestones / notes / proof link on create and email-linked update - [ ] Applications + Validated views show experience columns; record side panel lists experience fields - [ ] `yarn lint` clean; `yarn test:unit` covers schema + map-tally helpers - [ ] After Tally campaign: dry-run then apply CSV import via local ops script under `~/twenty/docs/superpowers-specs/raise-bar-import/` |
||
|
|
59eead238d |
message list member backfill (#23176)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23176?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. --> |
||
|
|
97d2b52a71 |
fix(front): duplicate junction chip after Add New in relation picker (#23185)
Clicking Add New in a junction relation picker rendered the newly created target twice until a page reload. The handler appended the created junction to the source record's store field after awaiting the mutation, but useCreateOneRecord's post-optimistic effect had already attached it, so the same junction id ended up in the field array twice (single row in DB). Removes the redundant manual append in both to-many picker flows. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23185?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. --> |
||
|
|
6623901eb4 |
chore: bump version to 2.25.0 (#23221)
## 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/23221?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> |
||
|
|
b91c2a6457 |
fix(server): repair missing applicationRegistration.logoFileId on upgraded instances (#23215)
## Problem closes https://github.com/twentyhq/twenty/issues/23210 Self-hosted instances on 2.23.x fail their workspace upgrade with: ``` column ApplicationEntity__ApplicationEntity_applicationRegistration.logoFileId does not exist at UpgradePeopleDataLabsApplicationCommand.runOnWorkspace ``` The `2-21` instance command that adds `core."applicationRegistration"."logoFileId"` was merged ~20 minutes after the 2.22 version bump (PR #22827, `94192a2164`), so it first shipped in 2.22 while registered under `@RegisteredInstanceCommand('2.21.0', ...)`. The upgrade runner resolves its start position from the last recorded command and only moves forward. Any instance that had already run a 2.21.x binary has its cursor past that slot, so the command is skipped permanently and the column is never created. `UpgradeAwareEntityMetadataAdapter` decides column visibility positionally (`index < currentCursor`), not by whether the command actually ran, so it keeps `logoFileId` in the SELECT list and the instance reports "Up to date" while the column is absent. **Affected:** instances that ran 2.21.x, then upgraded to >= 2.22. Instances that went from <= 2.20 straight to >= 2.22 replayed the full sequence and are fine. `logoFileId` is populated lazily by design (NULL is a supported state), so no backfill is added. ## Changes **1. Idempotent DDL guard in the failing workspace command** `2-23-workspace-command-...-upgrade-people-data-labs-application.command.ts` now ensures the column exists at the top of `runOnWorkspace`, before the `findOne` that crashes on affected instances. It uses the core `DataSource` (`@InjectDataSource()`) because `core."applicationRegistration"` is instance-global, guards with a per-process boolean in addition to the SQL-level `IF NOT EXISTS`, and copies the full statement list (column + unique + FK constraints) verbatim from the 2.21 command. In dry-run it probes `information_schema.columns` and returns instead of running the crashing query. **2. Fast instance command in 2.23** New `2-23-instance-command-fast-1784823473532-add-logo-file-id-to-application-registration.ts`, registered at the end of the 2.23 fast segment (highest timestamp), running the same idempotent DDL. This covers the normal 2.22 -> 2.23 path and, critically, instances with zero provisioned workspaces where the workspace command body never executes. The shared DDL lives in `2-23/utils/ensure-application-registration-logo-file-id-column.util.ts` so both paths stay byte-for-byte identical. Class name follows the `Early2_4` / `Early2_5` precedent to avoid colliding with the 2.21 command. The fix lives entirely in 2.23: instances stuck at the failing workspace command retry it every run, and 2.22 -> 2.24 jumps still replay the 2.23 segment. ## Ops note Instances failing right now can be unblocked immediately by running the same `ALTER TABLE` block by hand against their core database (byte-for-byte what the command does). Worth including in the 2.23 patch release note. ## Verification - New fast instance command re-slotted last in the 2.23 fast segment (timestamp `1784823473532` > current max `1784659343818`). - Manual repro path: boot `twentycrm/twenty:v2.21`, seed, stop, run `upgrade` from this branch, assert the column exists and `upgrade:status` reports 0 failed. The default v1.22 baseline does not reproduce it (replays from cursor 0). --- _Generated by [Claude Code](https://claude.ai/code/session_01YAuDR585cx7FyAKoiT32j3)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23215?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: Paul Rastoin <paul.rastoin@gmail.com> |
||
|
|
5c7915ba56 |
Skip install apps onboarding step for invitees (#23214)
Users joining an existing workspace via an invitation link were shown the install apps onboarding step after skipping the email-connect step, even though the backend never assigns them that step (and apps they selected were silently not installed). The frontend optimistic transition unconditionally mapped SYNC_EMAIL to APPS_INSTALLATION. It now checks workspaceMembersCount === 1, like the existing PROFILE_CREATION to INVITE_TEAM transition, so invitees go straight to profile creation. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23214?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. --> |
||
|
|
5160415f40 |
fix(workflow): create core mirror rows during workflow prefill (#23204)
## Problem Seeded workflows are inserted during workspace activation (and dev seeding) via raw SQL in `prefill-workflows.util.ts`, which bypasses the ORM entirely. The async dual-write listener that mirrors `workflow` / `workflowVersion` into `core.workflow` / `core."workflowVersion"` never fires for these rows, so every newly activated workspace is born with: - no core mirror rows, and - NULL `coreWorkflowId` / `coreWorkflowVersionId` soft-refs. This is permanent, growing drift. The core-consistency check reports every new workspace as 2 unlinked workflows + versions, and once trigger dispatch reads from core these seeded workflows would silently stop working. ## Change Insert the `core.workflow` and `core."workflowVersion"` mirror rows and stamp the soft-refs inside the same prefill transaction. Field mapping mirrors the dual-write / backfill exactly: - `core."workflowVersion".workflowId` = the workspace workflow id (what the trigger-map cache groups by) - `triggers` = jsonb `[trigger]` - `applicationId` = `workspace.workspaceCustomApplicationId` (throws if missing, same contract as the sync path) - `core.workflow.lastPublishedVersionId` = the workspace version id - one ACTIVE core version per workflow (satisfies the partial unique index) Core ids are deterministic v5 (same helper/namespace as the existing prefill ids) so the existing `.orIgnore()` re-run guard stays idempotent — a re-run hits the PK conflict and is skipped instead of inserting duplicate orphan core rows. The large diff is mostly re-indentation: the step/trigger arrays were extracted to consts so the same objects feed both the workspace and core inserts. The step/field UUID literals are unchanged from main. ## Verification - `nx lint:diff-with-main twenty-server`: clean. - Typecheck (isolated `tsc`): no errors in the changed file (`nx typecheck twenty-server` is blocked by a pre-existing `twenty-shared` build error on main, unrelated). - Runtime: pending a `database:reset` + cross-schema parity query confirming zero drift on a freshly seeded workspace. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23204?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. --> |
||
|
|
4d09c400a4 |
Remove DATABASE_EVENT_JOBS_CHUNK_SIZE and Promise.all from logic function trigger jobs (#23205)
## What - `LogicFunctionTriggerJob` now processes a single `LogicFunctionTriggerJobData` payload instead of an array processed with `Promise.all`. - Removed `DATABASE_EVENT_JOBS_CHUNK_SIZE` and the `lodash.chunk` usage in `CallDatabaseEventTriggerJobsJob`. - Added `bulkAdd` to `MessageQueueService` and both drivers (BullMQ driver uses native `queue.addBulk`, sync driver processes payloads sequentially). `CallDatabaseEventTriggerJobsJob` uses it to enqueue all payloads in one call. - Updated the other producers (`ServerRouteTriggerService`, `ApplicationInstallService`, `ConnectionProviderOauthFlowService`, `CronTriggerCronJob`) to enqueue a single payload instead of a one-element array, and updated the corresponding specs. ## Why Each logic function execution now gets its own queue job, so a failing execution only retries itself instead of re-running the whole chunk, and job-level retry/metrics apply per execution. --- _Generated by [Claude Code](https://claude.ai/code/session_018VCs2kopnDiZCL41eToxQF)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23205?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. --> |
||
|
|
65d399c70d |
feat(page-layout): drag widgets across tabs on record pages (#23023)
## What When editing a record page layout, you can now drag a widget out of one tab and into another. The most common case works: drag from the left column (the pinned first tab, in full mode) into the tab you're currently viewing. Ways to move a widget across tabs: - **Into the visible tab's content** — drop it into another vertical-list tab's list to place it at a precise index. A blue line shows exactly where it will land. - **Onto a tab button** — drop a widget onto another tab's button to move it to that tab; the button highlights while hovered. - **Into an empty tab / the end of a tab** — an end-of-list drop zone (wrapping the add-widget area) accepts the widget, so an empty tab is a valid drop target and widgets can be appended to the end of a populated one. Within-tab reordering keeps working as before, now with the same blue drop-line indicator. ## How The record-page widget list is migrated from `@hello-pangea/dnd` to `@dnd-kit/react` (already used elsewhere in the app, e.g. navigation-menu-item and record-board). A single `DragDropProvider` spans the left column, the tab bar, and the active tab content, which is what makes cross-list drag possible — Pangea scopes each list to its own context, so cross-tab drag wasn't expressible there. - Widgets are dnd-kit sortables grouped by `tabId`; dropping into a different group is a cross-tab move. - The drop line uses `useSortable().isDropTarget` on the targeted widget, plus an end-of-list droppable for append/empty-tab. - Each record-page tab button is a `useDroppable` target for widgets, opt-in per vertical-list tab so canvas/grid tabs keep their native placement. - The drag lifecycle lives in one router hook (`usePageLayoutWidgetDragAndDrop`) that routes to two pure, unit-tested draft utils (`moveWidgetWithinTabInDraft`, `moveWidgetToTabInDraft`). The side-panel "Move to tab" action shares the same `moveWidgetToTabInDraft` util, so drag and menu paths converge on one mutation. - Drop resolution reuses the shared module #23071 landed: `getDestinationIndex` compensates same-tab downward moves for the source-removal shift so the drop line and the landing slot agree, and the shared `preventNativeDragStart` guard stops links/images inside widget content from starting a native URL drag. ## Scope Deliberately staged to the record-page widget list. Not included (follow-ups): - Tab reordering and the field-config editors still use `@hello-pangea/dnd`; finishing the full page-layout removal of Pangea is separate. - Grid/dashboard cross-tab drag (the source there is `react-grid-layout`, which needs a cross-system bridge). - #23071 has merged and this branch sits on it; the remaining convergence is a follow-up: fold `PageLayoutWidgetSortableItem`/`PageLayoutWidgetDropLine` into the shared `DragDropItem*` cells, export a generic drag-event-type helper to delete the 7 copied `Parameters<...>` extractions, replace `useMovePageLayoutWidgetUp/Down` with `moveWidgetWithinTabInDraft`, and migrate the remaining page-layout test suites onto `pageLayoutDraftFixtures`. ## Testing - Unit tests for `moveWidgetToTabInDraft` and `moveWidgetWithinTabInDraft` (incl. the non-vertical destination guard); the three suites now share one fixture module (`page-layout/testing/pageLayoutDraftFixtures`). - The downward off-by-one is covered by the shared `getDestinationIndex` unit tests from #23071. - Full `page-layout` suite green (161 files / 1055 tests), plus typecheck, lint, and format. - The drag interaction itself (drop precision incl. downward same-tab drops, line/highlight, clone feedback) still needs a manual pass in the running app. |
||
|
|
96a2456367 |
feat(workflow): periodic core-consistency check for workflows, versions and triggers (#23103)
Monitoring for the soft-ref migration. The `workflow` /
`workflowVersion` dual-write into core is **best-effort** (async, not
transactional; failures only go to Sentry), so `core.workflow` /
`core.workflowVersion` can silently drift from the workspace source of
truth. This adds a periodic job that detects that drift across **all
three workflow entities** and emits it as metrics.
Supersedes the earlier inline shadow-parity approach that lived on this
branch — that only covered trigger dispatch and added a cache read +
diff to every cron tick and every DB-event batch (too much hot-path
overhead). This is broader and fully off the dispatch path.
## What
A cron (`cron:workflow:core-consistency-check`, every 3 hours, wired
into `cron:register:all`). Per run:
- **Bounded**: `SELECT DISTINCT "workspaceId" FROM core."workflow"` —
only workspaces that actually use workflows (skips the large majority).
- Per such workspace, emit a drift metric per `(entity, driftType)` —
detect-only, plus a triage log:
- **workflow** and **workflowVersion** — `unlinked` / `missingCore` /
`orphanCore` / `fieldMismatch`, via cross-schema `COUNT` aggregates
(core + workspace are the same DB, so indexed joins — no rows pulled
into JS).
- **automated triggers** — the `workflowAutomatedTrigger` table vs the
`workflowAutomatedTriggerMaps` cache: `inTableNotCache` /
`inCacheNotTable` / settings `mismatch`.
- Per-workspace failures are isolated (caught → Sentry) so one bad
workspace does not stop the sweep.
## Why it is efficient
Two central `core.*` queries + a few `COUNT` queries per
*workflow-using* workspace, on a relaxed cadence, off the dispatch path.
Shardable across ticks later if needed.
## Metrics
`workflow-core-consistency/{workflow,version,automated-trigger}/drift`
counters, attribute `driftType`. Dashboards: twentyhq/twenty-infra#800.
## Not in scope
Detect-only — no auto-heal (the existing backfill/rebuild command can
heal). No dispatch or flag changes.
## Test
- The consistency SQL (workflow/version sync counts, orphan counts,
trigger read) validated against a live workspace (it surfaced real drift
there — unlinked versions + an orphan core version). The whole
cron→service→SQL→metric pipeline is proven live: the cron is already
emitting real drift counters on a running server.
- Unit specs for the service (clean → no metric; per-entity drift per
dimension; per-workspace error isolation).
- Command boots and registers via `cron:register:all` (verified).
Typecheck + lint clean.
|