ceecae30db4377925b9b41e404487f643fc4e640
12889 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ceecae30db |
fix(workflow): show fields for system objects in record-updated trigger (#21826)
## Problem On the **Record is updated** (and **upserted**) workflow trigger, picking a record type under the **Advanced** submenu (i.e. a *system* object) showed an empty "Fields (Optional)" list — you couldn't select any field to watch. ## Root cause The trigger's field picker (`WorkflowFieldsMultiSelect`) was called with `actionType="UPDATE_RECORD"`, which runs each field through `shouldDisplayFormField`. For `UPDATE_RECORD` that predicate requires `(isUIEditable ?? true)` — correct for the *Update Record action* (you can't write to a read-only field), but wrong for a *trigger*, where you're choosing which fields to **watch for changes** and editability is irrelevant. System objects define their fields with `isUIEditable: false`, so every field failed the gate and the list rendered empty. ## Fix Add DATABASE_EVENT trigger type to separated from action type. The `UPDATE_RECORD` / `UPSERT_RECORD` action paths are untouched. |
||
|
|
8ff494e5e8 |
fix(security): bump tar to 7.5.16 in seed-dependencies (PAX file smuggling) (#21829)
## fix(security): bump tar to 7.5.16 in seed-dependencies (PAX file smuggling) Resolves [Dependabot Alert #1500](https://github.com/twentyhq/twenty/security/dependabot/1500). ### What `tar` (`node-tar`) `<= 7.5.15` applies a PAX size override to intermediary GNU long-name/long-link headers, causing a tar-parser interpretation differential (file smuggling) — [GHSA-vmf3-w455-68vh](https://github.com/advisories/GHSA-vmf3-w455-68vh). Patched in `7.5.16`. This is the **seed-dependencies holdout** deferred from the main tar PR (#21813): that lockfile + its checksum constants were also touched by the form-data PR, so it was carved out to avoid a conflict. The form-data PR has since merged, unblocking it. ### How - Refreshed `tar` `7.5.13 -> 7.5.16` in `seed-dependencies/yarn.lock` (transitive via `^7.5.4`, which already permits it) — an in-range refresh, no override. - Regenerated `DEFAULT_YARN_LOCK_CHECKSUM` in `get-default-application-package-fields.util.ts` so the row-stored checksum matches the value recomputed from file content in `application.service.ts` (the deps-layer cache key; `logicFunctionCreateHash` = SHA-512, first 32 hex). `package.json` is unchanged, so `DEFAULT_PACKAGE_JSON_CHECKSUM` is unaffected. ### Verification - No `tar <= 7.5.15` remains in the seed lockfile. - Both checksum constants verified to match the canonical recompute of the current seed files. - Lint + format pass on the changed `.ts` file. |
||
|
|
adf6eb572b |
feat(billing): embed Stripe Payment Element in onboarding (#21759)
## What & why Replaces the hosted Stripe Checkout redirect on the onboarding "Choose your plan" step (credit-card trial) with an inline Stripe **Payment Element**, so users never leave the app to enter card details. ## How it works - **Frontend:** a deferred `<Elements mode="setup">` renders the Payment Element, themed via the Appearance API. On Continue: `elements.submit()` → `checkoutSession` mutation creates the trialing subscription server-side and returns its pending SetupIntent `clientSecret` → `stripe.confirmSetup()` confirms the card (handling 3DS) → redirect to the existing `/plan-required/payment-success`. - **Backend:** new `BILLING_STRIPE_PUBLISHABLE_KEY` config var exposed via `/client-config`; the card path creates the subscription with `payment_behavior: default_incomplete` + a free trial (so Stripe attaches a `pending_setup_intent`) and returns its client secret. The hosted-Checkout code path is removed. - The **no-credit-card** trial path is unchanged. - Billing address collection is **disabled** in the Payment Element to reduce friction; `automatic_tax` is correspondingly disabled (tax needs an address — collect it later, e.g. at conversion / via the billing portal). ## Required before this works 1. Set `BILLING_STRIPE_PUBLISHABLE_KEY` (`pk_…`) on the server (infra change pending). 2. Run `nx run twenty-front:graphql:generate --configuration=metadata` against a server exposing the updated schema (see inline note on the hand-authored document). 3. Verify in Stripe test mode: happy path, 3DS (`4000 0025 0000 3155`), a decline. ## Verified typecheck (front + server), oxlint + oxfmt clean, `client-config.service.spec` passing. Not run here: the app end-to-end / Stripe test mode and `graphql:generate` (no server/DB in the dev container). I've left self-review comments inline flagging cleanup opportunities plus a couple of architectural/tech-debt items. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01TxCfinXq7abSrbF7aTw2cA --- _Generated by [Claude Code](https://claude.ai/code/session_01TxCfinXq7abSrbF7aTw2cA)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21759?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: Claude <noreply@anthropic.com> |
||
|
|
1e744a761d |
fix: reorder table columns against visible fields only (#20940) (#21084)
## Fixes #20940 ### Problem The "Move Left" / "Move Right" actions in the table column header menu were unreliable. Clicking them often produced no visible change, or appeared to move the column an inconsistent number of positions. ### Root cause `useMoveRecordField` computed the swap target from **all** record fields (`currentRecordFieldsComponentState`) sorted by position — including hidden and non-readable columns. As a result, a move frequently swapped positions with an *invisible* neighbor, leaving the visible column order unchanged. This was also inconsistent with the drag-and-drop reorder path (`useReorderVisibleRecordFields`), which already operates only on the visible field set, and with the dropdown's own Move enable/disable logic, which is based on `visibleRecordFields`. ### Fix `useMoveRecordField` now sources the neighbor from `visibleRecordFieldsComponentSelector` — the same selector that drives the table display and the Move menu items (`isVisible && isReadable && isActive`, sorted by position). The real `position` values are still swapped, so hidden columns keep their positions and only the visible order changes. ### Tests Added `useMoveRecordField.test.tsx`, which seeds real object metadata with a hidden column interleaved between visible ones (by position) and asserts that the visible selector reorders correctly after a move. The test fails against the previous implementation and passes with this change. ### How to verify 1. Open any table view. 2. Open a column header menu and click "Move Right" / "Move Left". 3. The column now moves reliably by one visible position each click, regardless of hidden columns. --------- Co-authored-by: Harsh Singh <harsh@Harshs-MacBook-Air.local> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
5a6c02a7aa |
Create CI workflow for internal apps (#21791)
as title <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21791?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: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f61ce4ce68 |
i18n - website translations (#21827)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21827?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> |
||
|
|
c32cb78562 |
fix: filtered view resetting to unfiltered list on navigation (#21080)
## Summary - Resolves #21079 - Object navigation links no longer force the default index view, which had no saved filters. - Returning to an object after “Save as new view” now opens the last visited (filtered) view instead of the unfiltered default list. ## Test plan 1. Add a filter -> save as new view -> list is filtered 2. Navigate away and back -> filters still applied (not reset to unfiltered) ## Screencast ### Before https://github.com/user-attachments/assets/25326339-a3a1-4171-89cc-5149e254982e ### After https://github.com/user-attachments/assets/b4600043-fc7a-4670-9c68-23daa6c31ec8 --------- Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
99f4084a05 |
fix(front): pass auth headers to GraphiQL fetcher for introspection (#21821)
## Summary https://discord.com/channels/1130383047699738754/1517429151328374896 - The GraphiQL playground's automatic introspection query was firing without an `Authorization` header, causing "GraphQL introspection has been disabled" errors in production - `defaultHeaders` only pre-fills the GraphiQL headers editor UI — it does not inject headers into actual fetch requests - Added `headers` to the `createGraphiQLFetcher` config so all requests (including auto-introspection on load) are authenticated ## Test plan - [x] Open Settings > API & Webhooks, select Core schema + GraphQL, click Launch - [x] Verify the introspection query in the network tab now includes the `Authorization: Bearer ...` header - [x] Confirm the schema explorer loads successfully in a production environment (or with `NODE_ENV=production`) <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21821?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. --> |
||
|
|
a6aa03b77b |
fix(front): emit error instead of completing empty on failed token renewal (#21822)
handleTokenRenewal returned rxjs EMPTY when there was no token pair or
token renewal failed, so the operation's link chain completed without
emitting a value. Apollo Client v4 treats that as an invariant violation
("The link chain completed without emitting a value"), which surfaces as
an uncaught error during auth/token transitions (e.g. the
workspace-token swap in onboarding). Re-emit the original error so the
operation rejects cleanly instead.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21822?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. -->
|
||
|
|
569d887d1e |
[Website] Cut over to the rebuilt site (#21825)
Renaming the package so any further PRs directed to the website are targeted to the reworked code instead of diverging. Once merged, I will start preparing this for deployment to dev to test before releasing to prod. Any improvements will also be applied to this package. I avoided making significant changes to API routes so nothing breaks, but will test it thoroughly today to confirm. That said, everything is ported - double checked. Big diff PR, impossible to review, but last one! No more rebuilds. |
||
|
|
40c9a11f43 |
feat(onboarding): always show the Create Profile step (#21823)
The Create Profile step was skipped whenever the user already had a first or last name (e.g. provided during sign-up or via SSO), because the create-profile-pending flag was only set when both were empty. Always set it so the step is presented during onboarding and the user can review/confirm their profile; submitting it still clears the flag and advances. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21823?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. --> |
||
|
|
a7afff7465 |
fix call recording bot automatic leave activate after bug (#21820)
hardcoded activate_after -- 0 on everyone_left_timeout was getting rejected by recall with a 400 (activate_after can't be 0). pulled it into a named const, set to 1. its weird -- recall doc says default is 0 -- but its erroring out for us if we send zero. looks like the 0 default only applies when you leave the field out, not when you pass it explicitly(did not verify). keeping it as lowest possible value (1) for now. what the property does -- after the meet starts, how long before the bot starts watching the "everyone left" timeout. 1s is basically immediate, which is what we wanted with 0 anyway. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21820?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. --> |
||
|
|
0e97e1a908 |
feat(partners): add partner-application-triage and partner-meeting-recap skills (#21819)
## What Adds two Twenty partner-pipeline skills to `twenty-partners/src/skills/`, plus a patch version bump. ### `twenty-partner-application-triage` Ranks the partner-application backlog by net-new value and surfaces a short chase-list of high-value applicants who haven't booked a call. Read-only against the live partners workspace. Ships `rank.py` as its scoring helper. ### `twenty-partner-meeting-recap` After partner calls, pulls Fireflies meetings, matches each to an existing Partner by attendee email/domain, writes a recap (transcript-first, Fireflies summary as fallback), and injects it as a Note linked to the partner via `NoteTarget`. Skips leads/discovery calls (no Partner match) and meetings whose content isn't ready yet. Optional `--prune` deletes the Fireflies recording once its recap is safely in the CRM (confirmed first). ## Version `twenty-partners` 0.5.4 → **0.5.5** (patch: additive skill docs, no app behaviour change). ## Notes - Both skills read credentials from `~/.twenty/credentials.env`; no secrets committed. - All GraphQL queries/mutations are the proven ones used against the live workspace. |
||
|
|
ba7b435885 |
fix(security): bump webpack-dev-server resolution to 5.2.5 (HMR WS interception) (#21818)
## fix(security): bump webpack-dev-server resolution to 5.2.5 (HMR WS interception) Resolves [Dependabot Alert #1514](https://github.com/twentyhq/twenty/security/dependabot/1514). ### What `webpack-dev-server` `< 5.2.5` is affected by [GHSA-mx8g-39q3-5c79](https://github.com/advisories/GHSA-mx8g-39q3-5c79) (**Moderate**) — HMR WebSocket interception via permissive user proxies. Patched in `5.2.5`. ### How `webpack-dev-server` is already force-resolved via a scoped `resolutions` entry: its sole consumer `@electron-forge/plugin-webpack` (latest 7.11.2 / 8.x alphas) still declares `webpack-dev-server ^4`, so the resolution evicts the vulnerable 4.x line up to 5.x. The newly-disclosed CVE affects the pinned `5.2.4`, so this **bumps the existing scoped resolution `5.2.4 -> 5.2.5`** and updates its `//resolutions` doc line — extending an already-documented, load-bearing entry rather than adding a new one. **Still-required check:** `@electron-forge/plugin-webpack` still declares `^4`, so the resolution remains necessary (removing it would regress to vulnerable 4.x). ### Verification - `webpack-dev-server` resolves to a single `5.2.5` bucket; no `< 5.2.5` remains. - Not imported in our source (electron-forge build tooling only). - `yarn install --immutable` passes. Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
3675f264f1 |
Infer record pickers for record-typed logic function workflow inputs (#21494)
## Context Logic functions can declare workflow inputs typed as records or arrays of records (e.g. the People Data Labs enrichment functions), but the workflow builder rendered those as a plain text input with a variable picker, which is not usable. ## What this does - Adds an `objectUniversalIdentifier` link on input schema properties, so a record-typed input is tied to a workspace object. - The SDK build infers it from a `TwentyRecord<'objectUniversalIdentifier'>` marker type in the handler signature, reading the object's universal identifier straight from the source; explicit input schemas can still set the field directly. - The workflow builder renders these inputs as a single record picker or a record multi-select with the variable picker on the right. Selected records are stored as record ids; `TwentyRecord<UID>` is a branded `string`, so the handler signature reflects that it receives ids (a bound variable resolves to whatever the referenced step produced). - The multi-select collapses overflowing chips into a `+N` badge (reusing `ExpandableList`) and its variable picker offers both record objects and fields. - Updates the People Data Labs enrichment inputs as the reference implementation. <img width="802" height="824" alt="CleanShot 2026-06-12 at 16 54 10@2x" src="https://github.com/user-attachments/assets/a0896d74-0aab-49bd-a173-14c578a2e533" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21494?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. --> |
||
|
|
9eb90e72f9 |
[Website] Add internal enterprise key reissue endpoint. (#21807)
Port of #21660 into the redone's enterprise backend. - Extract getLicenseeFromStripeCustomer into the platform/enterprise barrel, replacing the inline licensee-from-Stripe-customer block in the activate route; cover its branches with a unit test. - Add POST /api/enterprise/reissue: a support endpoint that re-signs an enterprise key from a subscriptionId, guarded by a timing-safe compare against ENTERPRISE_ADMIN_API_SECRET. Adapted to the redone — barrel imports, node:crypto named imports, and the sibling routes' 503 configured-check guard — while keeping the original's generic non-leaking 500. - Document ENTERPRISE_ADMIN_API_SECRET in .env.example. |
||
|
|
2169e15162 |
fix(security): patch yaml across both major lines (stack overflow) (#21817)
## fix(security): patch yaml across both major lines (stack overflow) Resolves [Dependabot Alert #734](https://github.com/twentyhq/twenty/security/dependabot/734) and [#697](https://github.com/twentyhq/twenty/security/dependabot/697). ### What `yaml` is affected by [GHSA-48c2-rrv3-qjmp](https://github.com/advisories/GHSA-48c2-rrv3-qjmp) (**Moderate**) — stack overflow via deeply nested YAML collections — across two major lines: - **2.x** (`>= 2.0.0, < 2.8.3`, patched `2.8.3`) — alert #734 (runtime). - **1.x** (`>= 1.0.0, < 1.10.3`, patched `1.10.3`) — alert #697 (dev, auto-dismissed). ### How Both vulnerable copies are transitive, and `yaml` is not imported in our source: - **2.x:** the `2.8.1` bucket (`^2.0.0` / `^2.4.5`, via `vfile-matter` / `@mintlify/openapi-parser`) is deduped into the safe `2.9.0` already in the tree. - **1.x:** the `1.10.2` bucket (`^1.10.0`, via `cosmiconfig@^7.0.0`) is refreshed to `1.10.3`. Both move within ranges the parents already declared — no `resolutions` override. ### Verification - No `yaml` `1.x < 1.10.3` or `2.x < 2.8.3` resolution remains. - `yaml` is not imported in our source (transitive only). - Lockfile-only change; `yarn install --immutable` passes. |
||
|
|
ebdf77075a |
fix(security): dedupe uuid to 11.1.1 (missing buffer bounds check) (#21815)
## fix(security): dedupe uuid to 11.1.1 (missing buffer bounds check) Resolves [Dependabot Alert #1289](https://github.com/twentyhq/twenty/security/dependabot/1289). ### What `uuid` `< 11.1.1` is affected by [GHSA-w5hq-g745-h8pq](https://github.com/advisories/GHSA-w5hq-g745-h8pq) (**Moderate**) — a missing buffer bounds check in `v3`/`v5`/`v6` when a `buf` argument is provided. Patched in `11.1.1`. ### How The only vulnerable copy was a transitive `11.1.0` bucket pinned via `^11.1.0` by `typeorm@0.3.26` and `@recallai/desktop-sdk` — both ranges already permit `11.1.1`, and a `11.1.1` bucket already existed (our own packages resolve there). `yarn dedupe uuid` collapses the `^11.1.0` descriptor into the existing `11.1.1` resolution, removing the vulnerable copy. No `resolutions` override; the unrelated `13.x` bucket is left untouched. ### Verification - No `uuid < 11.1.1` resolution remains. - Our packages already resolved to `11.1.1`, so compiled output is unaffected (the change only lifts the transitive `typeorm`/`@recallai` copy). - Lockfile-only change (net −9 lines); `yarn install --immutable` passes. |
||
|
|
6bbd070172 |
fix(security): bump markdown-it to 14.2.0 (smartquotes ReDoS) (#21814)
## fix(security): bump markdown-it to 14.2.0 (smartquotes ReDoS) Resolves [Dependabot Alert #1511](https://github.com/twentyhq/twenty/security/dependabot/1511). ### What `markdown-it` `<= 14.1.1` is affected by [GHSA-6v5v-wf23-fmfq](https://github.com/advisories/GHSA-6v5v-wf23-fmfq) (**Moderate**) — quadratic-complexity DoS in the smartquotes rule via `replaceAt` string operations. Patched in `14.2.0`. ### How `markdown-it` is pulled transitively by `@graphiql/react` (`^14.1.0`) and `prosemirror-markdown` (`^14.0.0`), both of which already permit `14.2.0`. This refreshes the lockfile resolution `14.1.1 -> 14.2.0` within the existing range — no `resolutions` override. It is **not** imported in our source, and the fix is internal to the smartquotes rule (no public API change). ### Verification - `markdown-it` resolves to `14.2.0`; no `<= 14.1.1` copy remains. - Diff limited to `markdown-it` + its own `linkify-it` dep bump. - Lockfile-only change; `yarn install --immutable` passes. |
||
|
|
bebe03e453 |
fix(security): bump tar to 7.5.16 across lockfiles (PAX file smuggling) (#21813)
## fix(security): bump tar to 7.5.16 across lockfiles (PAX file smuggling) Resolves [#1472 1474 1476 1479 1481 1483 1485 1487 1489 1491 1493 1496 1498 1505](https://github.com/twentyhq/twenty/security/dependabot/1472 1474 1476 1479 1481 1483 1485 1487 1489 1491 1493 1496 1498 1505). ### What `tar` (`node-tar`) `<= 7.5.15` applies a PAX size override to intermediary GNU long-name/long-link headers, causing a tar-parser interpretation differential (file smuggling). Patched in `7.5.16`. ### Why these alerts The advisory is scanned across many independent Yarn projects, so it surfaced as one alert per lockfile: the root `yarn.lock` plus 13 `packages/twenty-apps/**` lockfiles (each pulls `tar` transitively). ### How - Refreshed `tar` to `7.5.16` in the root and all 13 app lockfiles — they hold `tar` via `^7.5.x` ranges that already permit it, so this is an in-range lockfile refresh (no override) via `yarn up -R tar`. - The root additionally had `tar@7.5.15` exact-pinned by `@mintlify/previewing`, which has **no upstream fix** (latest `4.0.1163` still pins `7.5.15`). Added a scoped resolution `@mintlify/previewing/tar -> ^7.5.16`, **extending the existing scoped tar resolutions** already used for `@electron/rebuild` and `@electron/node-gyp`. ### Not included `seed-dependencies/yarn.lock` (alert #1500) is intentionally excluded: that lockfile and its checksum constants are already modified by the open form-data PR, so its `tar` bump will follow separately to avoid a conflict. ### Verification - No `tar <= 7.5.15` remains across the root or any app lockfile. - `yarn install --immutable` passes. |
||
|
|
e2df785aec |
fix(security): bump http-proxy-middleware to 3.0.7 (multipart field injection) (#21812)
## fix(security): bump http-proxy-middleware to 3.0.7 (multipart field injection) Resolves [Dependabot Alert #1517](https://github.com/twentyhq/twenty/security/dependabot/1517). ### What `http-proxy-middleware` `3.0.4 – 3.0.6` is affected by [GHSA-gcq2-9pq2-cxqm](https://github.com/advisories/GHSA-gcq2-9pq2-cxqm) (**High**) — multipart/form-data field injection via unescaped CRLF in `fixRequestBody`. Patched in `3.0.7` for the 3.x line. ### How `http-proxy-middleware` is pulled transitively by `@nx/module-federation` and `@nx/react` via `^3.0.5`, which already permits `3.0.7`. This refreshes the stale lockfile resolution `3.0.5 → 3.0.7` within the existing range — no `resolutions` override needed. The separate `2.0.9` bucket (from `webpack-dev-server`) is outside the advisory's `>= 3.0.4` range and is left unchanged. ### Verification - No `http-proxy-middleware` copy in the vulnerable `3.0.4 – 3.0.6` range remains; the 3.x bucket resolves to `3.0.7`. - Diff is limited to the resolved version + checksum. - Lockfile-only change; `yarn install --immutable` passes. |
||
|
|
2eef2f12be |
fix(security): bump protobufjs to 7.6.4 (DoS + property shadowing) (#21811)
## fix(security): bump protobufjs to 7.6.4 (DoS + property shadowing) Resolves [Dependabot Alert #1508](https://github.com/twentyhq/twenty/security/dependabot/1508) and [#1507](https://github.com/twentyhq/twenty/security/dependabot/1507). ### What `protobufjs` in the 7.x line is affected by two advisories: - [GHSA-wcpc-wj8m-hjx6](https://github.com/advisories/GHSA-wcpc-wj8m-hjx6) (**High**, [#1508](https://github.com/twentyhq/twenty/security/dependabot/1508)) — DoS through unbounded `Any` expansion during JSON conversion. Patched in `7.6.1`. - [GHSA-f38q-mgvj-vph7](https://github.com/advisories/GHSA-f38q-mgvj-vph7) (**Moderate**, [#1507](https://github.com/twentyhq/twenty/security/dependabot/1507)) — schema-derived names can shadow runtime-significant properties. Patched in `7.6.3`. ### How `protobufjs` is pulled transitively via `^7.3.0`, which already permits the patched releases. This refreshes the stale lockfile resolution `7.6.0 → 7.6.4` (the latest `7.x`; `>= 7.6.3` covers both advisories) within the existing range — no `resolutions` override needed. The `8.x` ranges in these advisories do not apply. ### Verification - `protobufjs` resolves to a single `7.6.4` bucket (`>= 7.6.1` and `>= 7.6.3`), clearing both alerts. - Diff is limited to the protobufjs family (`protobufjs` + its `@protobufjs/*` utility deps). - Lockfile-only change; `yarn install --immutable` passes. |
||
|
|
da49ed81e4 |
fix(security): bump piscina to 4.9.3 (prototype pollution → RCE) (#21810)
## fix(security): bump piscina to 4.9.3 (prototype pollution → RCE) Resolves [Dependabot Alert #1515](https://github.com/twentyhq/twenty/security/dependabot/1515). ### What `piscina` `<= 4.9.2` is affected by [GHSA-x9g3-xrwr-cwfg](https://github.com/advisories/GHSA-x9g3-xrwr-cwfg) / CVE-2026-55388 — a **prototype-pollution gadget enabling RCE via inherited `options.filename`** (High). For the 4.x line, the first patched version is `4.9.3`. ### How `piscina` is pulled transitively by `@swc/cli@0.8.1` via `^4.3.1`, which already permits `4.9.3`. This refreshes the stale lockfile resolution `4.9.2 → 4.9.3` within the existing range — no `resolutions` override needed. ### Verification - `piscina` resolves to a single `4.9.3` bucket; no `<= 4.9.2` copy remains. - Diff is limited to piscina's resolved version + checksum (its dependency set is unchanged). - Lockfile-only change; `yarn install --immutable` passes. |
||
|
|
d205c72fa2 |
fix(security): remove vulnerable lodash 4.17.23 (code injection + prototype pollution) (#21809)
## fix(security): remove vulnerable lodash 4.17.23 (code injection + prototype pollution) Resolves [Dependabot Alert #824](https://github.com/twentyhq/twenty/security/dependabot/824) and [#823](https://github.com/twentyhq/twenty/security/dependabot/823). ### What `lodash` `<= 4.17.23` is affected by: - **Code injection via `_.template`** ([#824](https://github.com/twentyhq/twenty/security/dependabot/824), High) - **Prototype pollution via `_.unset`/`_.omit`** ([#823](https://github.com/twentyhq/twenty/security/dependabot/823), Medium) Both are patched in `4.18.0`. The repo already resolved lodash to `4.18.1` everywhere **except** one copy held at `4.17.23` by `@stoplight/spectral-functions@1.10.1`, whose `~4.17.21` range capped lodash below `4.18.0`. ### How Instead of a standing `resolutions` override, this bumps the parent that imposed the cap: **`@stoplight/spectral-functions` 1.10.1 → 1.10.3** (pulled transitively via `@asyncapi/parser` ← `@mintlify/common`, accepted through `^1.7.2`). 1.10.3 widened its lodash dependency to `^4.18.1`, so the capped bucket collapses into the existing `4.18.1` resolution and the vulnerable copy is removed — leaving the dependency graph honest with no lingering override. ### Also Refreshes `@types/lodash` to the latest **4.17.24**: bumps the `twenty-client-sdk` pin `^4.17.15 → ^4.17.24` and dedupes the stale transitive `*` bucket (4.17.15) into a single `4.17.24` resolution. Type-stub only. ### Verification - The only real `lodash` resolution is now `4.18.1` (remaining `4.17.x` entries are `@types/lodash` type stubs, not the library); `@types/lodash` resolves to a single `4.17.24` bucket. - Lockfile-only dependency change; `yarn install --immutable` passes; `twenty-client-sdk` typecheck passes. |
||
|
|
26b4d6caed |
fix(security): bump form-data to 4.0.6 (CRLF injection) (#21808)
Resolves [Dependabot Alert #1473](https://github.com/twentyhq/twenty/security/dependabot/1473), [#1475](https://github.com/twentyhq/twenty/security/dependabot/1475), [#1477](https://github.com/twentyhq/twenty/security/dependabot/1477), [#1478](https://github.com/twentyhq/twenty/security/dependabot/1478), [#1480](https://github.com/twentyhq/twenty/security/dependabot/1480), [#1482](https://github.com/twentyhq/twenty/security/dependabot/1482), [#1484](https://github.com/twentyhq/twenty/security/dependabot/1484), [#1486](https://github.com/twentyhq/twenty/security/dependabot/1486), [#1488](https://github.com/twentyhq/twenty/security/dependabot/1488), [#1490](https://github.com/twentyhq/twenty/security/dependabot/1490), [#1492](https://github.com/twentyhq/twenty/security/dependabot/1492), [#1494](https://github.com/twentyhq/twenty/security/dependabot/1494), [#1495](https://github.com/twentyhq/twenty/security/dependabot/1495), [#1497](https://github.com/twentyhq/twenty/security/dependabot/1497), [#1499](https://github.com/twentyhq/twenty/security/dependabot/1499), [#1501](https://github.com/twentyhq/twenty/security/dependabot/1501) and [#1506](https://github.com/twentyhq/twenty/security/dependabot/1506). |
||
|
|
505094650f |
fix(twenty-shared): derive short-number suffix from the rounded value (#21591)
`formatToShortNumber` (`packages/twenty-shared/src/utils/format/formatToShortNumber.ts`) picked the unit suffix from the **raw** value but printed the **rounded** figure, so `999999` rendered as `"1000k"` instead of `"1m"`, and `999999999` as `"1000m"` instead of `"1b"`. This affects number/currency cells, column-footer aggregates, and dashboard charts. The fix replaces the hard-coded band branches with a promotion loop that derives the suffix from the rounded display value, so the suffix and figure always agree at boundaries. Adds boundary, just-below-boundary, and negative-boundary tests. Red-green proven: the two new boundary tests fail on the original source (`expected "1m" but got "1000k"`); the 11 pre-existing tests still pass; all 13 pass with the fix. Verified with a standalone strict `tsc` (0 errors) and oxlint on both changed files. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21591?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. --> |
||
|
|
814b43ca41 |
feat(server): derive email/calendar timelines from object relations (#21684)
Simplifies our existing implementation that uses three different GraphQL
endpoints to just one `getTimelineEventsFrom{Person, Company,
Opportunity}Id` to `getTimelineCalendarEventsFromObjectRecord`
/closes https://github.com/twentyhq/twenty/issues/19676
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21684?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>
|
||
|
|
1b3d933900 |
fix(workflow): hide empty option for non-nullable select fields (#21075)
## Summary Closes #20139. In a workflow **Create / Update / Upsert Record** action, the field input for a `SELECT` field always offered a **"No value"** option. Selecting it clears the field — even when that field is **not nullable** (for example an opportunity `Stage`). The maintainer asked in the issue to *"not allow to put a 'No stage' if the field is not nullable"*. ### Root cause `FormSelectFieldInput` unconditionally builds a `defaultEmptyOption` and passes it as the `<Select>`'s `emptyOption`, regardless of the field's nullability: ```tsx const defaultEmptyOption = { label: label ? t`No ${label}` : t`No value`, value: '', icon: IconCircleOff, }; // ... <Select ... emptyOption={defaultEmptyOption} /> ``` The nullability is already available on the field metadata (`FieldSelectMetadata.isNullable`), and the regular (non-workflow) record input already gates its clear affordance on it in `SelectFieldInput.tsx` (`fieldDefinition.metadata.isNullable && canSelectEmpty ? handleClearField : undefined`). The workflow form input just wasn't using it. ### Fix - Add an optional `isNullable` prop to `FormSelectFieldInput` and only build the empty option when the field is nullable (`isNullable !== false`). `emptyOption` is already optional on `<Select>`, so omitting it cleanly removes the choice. - Pass `isNullable={field.metadata?.isNullable}` from `FormFieldInput`'s `isFieldSelect` branch. The prop is optional and defaults to the previous behaviour, so the other `FormSelectFieldInput` call sites (country, calling code, currency, cron, etc.) are unaffected. Scope is intentionally limited to single `SELECT`: `FieldMultiSelectMetadata` has no `isNullable` field and the multi-select input has no equivalent empty option, so it is left untouched. ## Test plan - [x] `npx oxlint --type-aware` — passes on the changed files - [x] `npx oxfmt --check` — passes - [x] `tsc --noEmit` — no type errors in the changed files - [x] Added a `NonNullable` Storybook interaction test asserting the "No value" option is not rendered when `isNullable={false}` - [ ] Manual: in a workflow Update Record step on Opportunity, the **Stage** field no longer shows a "No value" option; a nullable select still does --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
616d58bc7e |
messaging: gmail folder backfill (#21753)
demo https://github.com/user-attachments/assets/a157cee1-a8fa-4050-af1b-c31a83fb75da /closes #17095 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21753?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. --> |
||
|
|
d88eb6c16b |
fix(front): unwrap react-qr-code default export to fix 2FA crash on prod (#21804)
## Problem 2FA is broken on prod (critical, reported on Discord and in #21649): instead of the 2FA setup screen, users hit the app-wide error page — both at login-time provisioning and on **Settings > Profile > Two-Factor Authentication**. The 2FA screen flashes briefly (loader) and then the error page replaces it. Fixes #21649. ## Root cause The crash is a React render error — *"Element type is invalid: got object"* — at the exact moment the QR code renders (when `qrCode` flips from `null` to a value). `react-qr-code` is a CommonJS package (`__esModule: true`, `exports.default = QRCode`). The recent **Vite 8 / rolldown** bundler migration changed how its CommonJS default export is resolved into an ESM import: `import QRCode from 'react-qr-code'` now resolves to the **module namespace object** `{ default, QRCode }` instead of the component itself. Rendering that object as a React element throws and trips the error boundary. The import code never changed — only the bundler's module resolution did, which is why this regressed without any 2FA code change. Reproduced the resolution with an esbuild/rolldown-style bundle: the default import comes back as `{ default, QRCode }`, with the real `forwardRef` component sitting on `.default`. ## Fix Add a small `resolveCjsModuleDefaultExport` helper that returns the default export when a CommonJS import is handed back as a namespace object, and a no-op otherwise. Use it in the two 2FA QR render paths: - `SignInUpTwoFactorAuthenticationProvision.tsx` (login-time provisioning) - `SettingsTwoFactorAuthenticationMethod.tsx` (profile settings) ## Verification - `npx nx lint:diff-with-main twenty-front` ✅ (lint + format) - `npx nx typecheck twenty-front` ✅ (CI) |
||
|
|
de9af38a67 |
Fix view type label casing (#21772)
## Summary Fix the view type label shown in the object Options menu by introducing a shared `ViewType` label map instead of formatting raw enum values at each call site. I chose to fix the root cause instead of only patching the symptom: the menu was displaying the generated enum value `TABLE`, and `capitalize()` only uppercased the first character without lowercasing the rest. The new mapping gives each view type an explicit translated UI label, so the parent Options menu, the Layout submenu, the view picker, and application content rows all use the same casing source. ## Before / After | Before | After | | --- | --- | | The Options menu showed `TABLE` in uppercase. | The Options menu now shows `Table`, and the Layout submenu still shows `Table`, `Calendar`, and `Kanban`. | |  |  | ## Tests - `git diff --check` - Browser smoke test on `http://apple.localhost:3001/objects/companies` - default view Options menu still opens - custom view Options menu shows `Layout` contextual text as `Table`, not `TABLE` - Layout submenu still shows `Table`, `Calendar`, and `Kanban` - no browser console errors Not run: package lint/test commands, because this checkout has no `node_modules` installed. |
||
|
|
40e386bfb4 |
Fix(record table): Drag select position (#21579)
Fixes: #21578 https://github.com/user-attachments/assets/8a1f3aa1-b436-4ce0-a9ec-c49ab61e214c <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21579?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: Félix Malfait <felix.malfait@gmail.com> |
||
|
|
c07dd53a48 |
Scope empty fixture workspaces to upgrade integration tests (#21778)
The dev seeder activated Empty3/Empty4 workspaces without creating their DB schema, so every workspace-iterating job (e.g. the workflow cron trigger) logged 'relation does not exist' for those schemas on each run. ``` [1] query failed: SELECT * FROM workspace_4rdlooovb6mo66rdmgupv06zi."workflowAutomatedTrigger" WHERE type = 'CRON' [1] error: error: relation "workspace_4rdlooovb6mo66rdmgupv06zi.workflowAutomatedTrigger" does not exist [1] [Nest] 51868 - 18/06/2026, 5:07:04 pm ERROR [WorkflowCronTriggerCronJob] Error processing workspace 506915ec-21ca-431b-a04a-257eb216865e: QueryFailedError: relation "workspace_4rdlooovb6mo66rdmgupv06zi.workflowAutomatedTrigger" does not exist [1] Exception Captured ``` <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21778?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. --> |
||
|
|
9f30915f6f |
fix(metadata): remove deprecated isCustom from Objects and Fields (#21799)
## Context Follow-up to #21228, which deprecated `isCustom` on object/field metadata but kept it exposed because the frontend still relied on it. This removes it from the GraphQL API and the frontend entirely. ## Implementation ### Server - Remove `isCustom` `@Field` from the `Object`, `Field`, and `MinimalObjectMetadata` GraphQL types - Remove the `isCustom` `@ResolveField` resolvers and the `isCustomLoader` dataloader (+ payload/interface) - Remove `isCustom` as an internal `@HideField()` on the Object/Field DTOs used by the i18n standard-override gate > Use an explicit isStandard instead (which is the correct gating) ### Frontend - Add `getIsMetadataItemCustom` helper + `useGetIsMetadataItemCustom` hook: an item is custom when `applicationId === currentWorkspace.workspaceCustomApplication.id` - Migrate all consumers off `objectMetadataItem.isCustom` / `fieldMetadataItem.isCustom`; `isRecordFieldReadOnly` now takes a precomputed `isFieldCustom` - Drop `isCustom` from the metadata fragment/mutations/minimal query, FE types, zod schemas, and mock generators; regenerate GraphQL types ## Notes - Breaking change on the (already-deprecated) `Object.isCustom` / `Field.isCustom` GraphQL fields and the `isCustom` filter - FE semantic is "belongs to the workspace custom app" (third-party-app objects/fields are treated as non-custom) - `isCustom` on IndexMetadata / View / Skill / Agent is a separate column and is untouched - Breaking changes on REST metadata API |
||
|
|
7b5ee8a7bc |
feat(server): report enterprise instance metadata on license validation (#21793)
## What Enriches the **enterprise-only** license-validation channel (`/validate`, `/seats`) with best-effort instance metadata so the licensing backend can later reconcile seats and surface signs of license abuse (e.g. one subscription on many `serverId`s, a `serverId` on many URLs, dev-mode-in-prod). Reported alongside the existing `enterpriseKey` (and `seatCount` on `/seats`), under a new `instanceMetadata` object: | Field | Purpose | |---|---| | `serverId`, `serverUrl` | instance identity — sharing / clone signals | | `workspaceCount`, `activeUserWorkspaceCount`, `distinctUserCount` | seat reconciliation / overage | | `appVersion`, `nodeEnv`, `telemetryEnabled` | fleet/support; dev-mode-in-prod signal | | `adminContactEmail` | **single** administrative contact (oldest active user) for license administration — explicitly *not* an abuse signal | | `sentAt` | timestamp | No CRM data, record contents, or member PII beyond the one admin contact are sent. ## Why it's safe for existing instances - **Enterprise-only.** Gathering runs only after the `ENTERPRISE_KEY` checks, so free/community instances make no extra queries and send nothing — unchanged behavior. - **Never blocks a refresh.** Each lookup is isolated (`safeCount` / try-catch); any failure degrades to `null` and the license refresh / seat report proceeds. - **Purely additive.** `enterpriseKey` and `seatCount` are preserved; the `/validate` and `/seats` handlers ignore unknown fields, so this can ship ahead of any backend consumer. - **No schema or token-verification changes** → no migration, existing validity tokens keep validating. ## Verification - `nx test twenty-server` — full unit suite green (5829 passed), including the updated `enterprise-plan.service.spec` with a new metadata-payload test - `nx typecheck twenty-server` — pass - `oxlint` + `oxfmt --check` on changed files — clean ## Deliberately out of scope (follow-ups) - **Server-side correlation/detection** and **short-TTL + instance-bound validity tokens** live on the signing/billing side (`twenty-website`) and need a coordinated rollout (enforcing token binding now would break already-issued tokens). - **`adminContactEmail`** is PII on a contractual enterprise channel — the enterprise terms should disclose it before rollout. - The dev-key / build-provenance hardening discussed separately is **not** part of this PR. Opening as **draft** for review of the field set and the cross-repo rollout plan before wiring a consumer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_0114DV9tctTjVo8eggBGgtKc --- _Generated by [Claude Code](https://claude.ai/code/session_0114DV9tctTjVo8eggBGgtKc)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21793?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> |
||
|
|
5053c63f02 |
[Website] Straighten the footer 20 model (#21797)
Reduce the footer model's resting yaw (rotationY 0.457 -> 0.363) so the "20" sits level instead of leaning. <img width="1512" height="578" alt="image" src="https://github.com/user-attachments/assets/6f3151b1-b1c4-4d6e-9cec-6881e4e33845" /> |
||
|
|
b14da2f9e8 |
[Website] Port partner application form rework (required fields, skills, fail-fast) (#21802)
Ports twenty-website PR #21710 (Rashad) into `twenty-website-redone`. The old site's partner application form was reworked last week — required fields, a skills rethink, and fail-fast validation — after the redone had already ported the form, so the redone was running the pre-rework behavior. This brings it to parity. Re-derived into the redone's own conventions rather than copied: it reuses the redone's `STEP_REQUIRED_FIELDS`/`STEP_FORMAT_CHECKS` validator, keeps one-export-per-file, and injects the new `searchPool` as an opt-in prop. |
||
|
|
9de1b6330c |
feat(website - self hosts billing): add internal endpoint to reissue enterprise keys (#21660)
## Summary Adds an internal support endpoint to regenerate a customer's enterprise key when they've lost the one issued at activation. The key payload is deterministic from the Stripe subscription, so this re-emits an equivalent valid key without any new state. `GET /api/enterprise/reissue/<subscriptionId>/<secret>` - Guarded by a shared secret (`ENTERPRISE_ADMIN_API_SECRET`), compared in constant time and fail-closed when unset. - Looks up the subscription in Stripe (for the licensee) and signs the key with `signEnterpriseKey()`, reading `ENTERPRISE_JWT_PRIVATE_KEY` from the environment — the private key is never accepted from the request. - No subscription-status gate: the key alone grants nothing. Feature access still requires a validity token, which `/api/enterprise/validate` only issues after re-checking the subscription is active. ## Notes / follow-ups - The admin secret travels in the URL path, so it can land in server/proxy/CDN access logs — rotate `ENTERPRISE_ADMIN_API_SECRET` if logs are ever exposed. - No audit logging yet; worth adding (who reissued which subscription, when). <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21660?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: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
af36b8ab2d |
[Website] Carry over README assets, .well-known, and an env template (#21803)
Prep for deleting the twenty-website package and renaming twenty-website-redone to take its place. - Copy the 37 root-README documentation assets (public/images/readme/) plus the README header logo (public/images/core/logo.svg). Files only — the root README's references are left untouched; they resolve automatically after the rename. - Copy the three served verification files into public/.well-known/ (security.txt, openai-apps-challenge, microsoft-identity-association.json). - Add a tailored .env.example derived from the package's actual env reads. Same variable names, dropped unused variables. |
||
|
|
6a1b28bc12 |
feat(auth): collect the workspace logo on the sign-up creation step (#21723)
## What & why A single, consistent **workspace-creation step** for both multi-workspace and single-workspace self-host — collecting **name + logo** (and the **subdomain** in multi-workspace) — which **removes the duplicate name/logo prompt** that previously reappeared on the workspace subdomain (reported after #21641). ## Changes **One creation form for both modes** - With 0 workspaces, both multi-workspace and single-workspace route to the shared `SignInUpWorkspaceCreationForm`; `SignInUp` renders it for the `WorkspaceCreation` step regardless of domain/scope. - The subdomain field shows only in multi-workspace; single-workspace keeps its fixed address. **Logo on the creation step** - New scoped `uploadNewWorkspaceLogo(workspaceId, file)` mutation: the creator sets a logo on their just-created `PENDING_CREATION` workspace via the workspace-agnostic token (membership enforced — only the creator is a member at that point), reusing `uploadWorkspacePicture`. Upload size is capped via `settings.storage.maxFileSize` (also applied to the existing logo / profile-picture uploads). - The picked file is held locally (object-URL preview, revoked on unmount) and uploaded right after creation (non-fatal on failure). **Onboarding step → pure activation loader** - The old "Create your workspace" form (name + logo) is removed. The onboarding step now activates the pending workspace on mount and shows the loader, with a **Retry** action on failure. ## Testing - typecheck (front + server) ✅; oxlint + oxfmt clean on changed files ✅ - Unit tests: `auth.resolver.spec`, `useWorkspaceSubdomainField`, `SignInUpWorkspaceCreationForm` (multi + single-workspace), `useAuth` ✅ - Metadata GraphQL + `twenty-client-sdk` schema regenerated. Follow-up to #21641. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Xw37hR5seiCyWnppG9z4op --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
880e7144ec |
Disable Claude Code attribution on commits and PRs (#21798)
## What
Adds a committed `.claude/settings.json` that turns off Claude Code's
git attribution:
```json
{ "attribution": { "commit": "", "pr": "" } }
```
- `commit: ""` removes the `Co-Authored-By: Claude` trailer from commit
messages.
- `pr: ""` removes the "Generated with Claude Code" text from PR
descriptions.
Also narrows the `.gitignore` rule from `.claude/` to `.claude/*` +
`!.claude/settings.json`, so this one shared config is tracked while
personal `.claude/` files (plans, `settings.local.json`, etc.) stay
ignored. Includes the schemastore `$schema` for editor
autocomplete/validation.
## Why
Cloud (Claude Code on the web) sessions clone the repo fresh and read
**committed** repo config, but do **not** read a developer's local
`~/.claude/settings.json`. Setting attribution only at the user level
(the usual advice) therefore has no effect on cloud-created commits/PRs.
Committing it to the repo is what applies to every future cloud session
— and to all contributors using Claude Code on this repo.
## Scope
This is repo-wide: it affects every contributor using Claude Code on
`twenty`, not just one person. Easy to scope down later via
`.claude/settings.local.json` if anyone wants attribution back locally.
## Follow-up / verification
Per the docs, `attribution.commit: ""` hides commit text "including any
trailers", so it should also suppress the cloud-only `Claude-Session:`
trailer and the PR session-URL line — but that cloud trailer is
undocumented, so it will be confirmed on the first cloud PR opened from
a session based on `main` after this merges. If it survives, a one-line
guard in `CLAUDE.md` (also loaded in cloud) is the fallback.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01PipZFnzew1VuU48kiHm35R)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21798?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. -->
|
||
|
|
0332d8c706 |
i18n - website translations (#21795)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21795?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> |
||
|
|
7a1cfc17cc |
fix(logic-function): treat invoke timeout as a user-level error, not a platform error (#21779)
Fixes https://twenty-v7.sentry.io/issues/7527156270?project=4507072499810304 ## Problem Sentry was flooded with high-severity alerts for logic functions that simply ran too long: > Lambda timed out for function '…' during invoke (functionState=Active, phase=invoke …) A function exceeding its configured `timeoutSeconds` is a **user-level outcome** (their code is too slow), not a platform failure — but it was being reported as one. ## Root cause The two timeout mechanisms were classified inconsistently: - **Lambda's own timeout** → returns `{ status: ERROR, … }` → handled as a user error (route returns 500 with `shouldBeCapturedBySentry: false`, queue job records it without failing). Not in Sentry. ✓ - **Client-side `AbortSignal` timeout** → **threw** `LOGIC_FUNCTION_EXECUTION_TIMEOUT`, which isn't mapped in `mapErrorToRouteTriggerCode`, so it fell through to `ROUTE_TRIGGER_PLATFORM_ERROR` (Sentry) and failed the BullMQ job (Sentry). ✗ Since the executor Lambda is fixed at 900s, the client abort is the *sole* timeout enforcement for every function with `timeoutSeconds < 900` — so essentially every slow function paged the team. The `local` driver already returns an ERROR result here; only the Lambda driver threw. ## Fix On an **invoke-phase** `TimeoutError`, return a structured ERROR result instead of throwing — mirroring the Lambda's own timeout and the local driver. The timeout now flows through the normal result path: surfaced to the caller as `status: ERROR`, recorded via `handleExecutionResult` (which the throw path skipped), and kept out of Sentry. **Build- and fetch-phase timeouts still throw** and stay in Sentry — those are platform-side (executor build / code fetch too slow, even for short user code), which is exactly what the phase instrumentation exists to catch. ## Tests - Unit test on the new `buildLogicFunctionTimeoutResult` util - `npx jest logic-function-drivers/drivers/lambda` green |
||
|
|
7afc991bd6 |
Partner application form: required fields, skills rework, fail-fast validation (#21710)
## Summary (twenty-website — partner application form) - **Required fields**: website URL, city, hourly rate, minimum project are now required (client step-gate + server zod) with `*` markers. The final step validates before POSTing, so empty required fields fail fast client-side instead of round-tripping. - **Technical skills reworked to *complement* "What you cover"** (the service categories) rather than duplicate them — now a small shown set + a larger searchable-only pool of tools / technologies / industries. Field hint clarifies the intent. - **No competitor CRMs** in suggestions (Salesforce/HubSpot/Attio removed); a guard test fails if one ever reappears. Migrations surface as a generic "CRM migration". - `Form.TagInput` gains an optional `searchPool` prop (autocomplete-only entries, not rendered as chips). Companion to the app-side PR #21709. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21710?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. --> |
||
|
|
0f4cb2c2c2 |
[Website] Full-bleed hero/testimonials, nav restructure, OpenNext deploy config (#21794)
### Full-bleed backgrounds on wide screens Decorative section backgrounds were capped at the 1512px content width, leaving large empty gutters on ultra-wide viewports. - Added an opt-in `fullBleedBackground` prop to `SectionShell` that lifts the background layer off the content-width cap (default unchanged, so every other section is untouched). - Applied it to `HomeHero` (bridge halftone field) and both testimonials sections. - Capped the `NotchedCardShape` notch at its width at the content cap, so the white/dark card can span the full viewport while the notch stays fixed and centered. The footer and any other capped card are unaffected. <img width="3024" height="1718" alt="image" src="https://github.com/user-attachments/assets/a2200677-58af-4740-9257-77f6385ade28" /> <img width="3024" height="1224" alt="image" src="https://github.com/user-attachments/assets/73aa6745-b766-405e-b736-8c0c7591232e" /> ### Navigation restructure - Removed **Product** from the menu and footer nav, and promoted **Why** out of the Resources dropdown to a top-level item in Product's place. (The Product page itself is unchanged). - Resources dropdown polish: tightened the preview frame height now that the list is shorter; restored the cleaner/brighter User Guide and Developers preview assets from `twenty-website`; gave User Guide a center + 2× image scale so its halftone fills the frame like Developers, and raised `NextImage` `sizes` to keep the fine halftone crisp through that magnification. - Restored the **current-page highlight** in the Resources dropdown (active icon/label in highlight blue + marker bar), matching the old `NavDropdown`. <p> <img width="1509" height="323" alt="image" src="https://github.com/user-attachments/assets/d5702761-31f5-4b4d-9fcc-c33d5c7ae6ab" /> </p> ### OpenNext / Cloudflare deployment config Ported the Cloudflare Workers deployment setup from `twenty-website` so the same CI/deploy pipeline works against the redone package: - `open-next.config.ts` (R2 incremental cache + regional cache + skew protection), `wrangler.jsonc` (dev/prod envs — **worker names, routes, R2 buckets kept identical** for a seamless cutover), `initOpenNextCloudflareForDev()` in `next.config.ts`, the `preview`/`deploy:*`/`cf-typegen` scripts, the `@opennextjs/cloudflare` + `wrangler` devDependencies, a `.dev.vars.example` template, and the relevant `.gitignore` entries. |
||
|
|
86a4066997 |
Accessibility fix pass for remaining twenty-ui domains (#21790)
Completes the twenty-ui accessibility fix pass for the remaining 32
`a11y: { test: 'todo' }` story files (navigation, feedback,
data-display, typography, surfaces, layout, json-visualizer), following
the input pilot (#21776, merged). The package now has **zero** `test:
'todo'` overrides left.
**What changed (gate-driven)**
- Bulk color-contrast deferred via the shared
`A11Y_DEFER_COLOR_CONTRAST` (greppable, tracked for the later
design-token pass).
- Real violations the axe gate surfaced and fixed: accessible names on
`ProgressBar` (progressbar), `Modal` (dialog), and the
MultiSelect/Toggle inner Checkbox/Switch; `role="listbox"` parents for
`MenuItemSelect`/`SelectAvatar`; `ariaLabel` on
`MenuItem`/`MenuItemDraggable` icon-buttons; a `version` for
`GithubVersionLink`; and `JsonTree`'s empty array/object now renders a
proper `<li>` (`list` rule).
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21790?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. -->
|
||
|
|
c6309fd92b |
feat(workflow): auto-layout steps on AI workflow creation via shared tidy-up (#21756)
## Context
The workflow builder has a "Tidy up" action that auto-positions steps
using a
Dagre layout. However, this lived entirely in the frontend and depended
on node
dimensions measured by React Flow after rendering in the browser.
As a result, workflows (and steps) created through AI Chat / MCP tools
were never
laid out: `create_complete_workflow` accepted optional `stepPositions`
that the
LLM had to invent, and `create_workflow_version_step` stored an optional
position
verbatim. In practice this produced overlapping / poorly positioned
steps.
## What this does
Extracts the tidy-up layout into a pure, frontend-free util in
`twenty-shared` and
reuses it from both the frontend tidy-up and the server, so
AI/MCP-created
workflows are auto-laid out at creation time.
### twenty-shared
- New `computeWorkflowLayout({ nodes, edges, options? })` — a pure Dagre
layout over
a minimal `{ id, width, height }` / `{ source, target }` graph,
returning
top-left-anchored positions (matching React Flow). Ignores edges
pointing to
unknown nodes.
- New constants: `WORKFLOW_LAYOUT_DEFAULT_OPTIONS`
(ranksep/nodesep/rankdir) and
`WORKFLOW_DIAGRAM_DEFAULT_NODE_DIMENSIONS` (estimated node size for
server-side
layout, where measured sizes are unavailable).
- Added `@dagrejs/dagre` dependency.
### twenty-front
- `getOrganizedDiagram` now delegates to `computeWorkflowLayout`,
passing real
measured node sizes. No behavior change for users.
### twenty-server
- New `WorkflowVersionWorkspaceService.autoLayoutWorkflowVersion(...)`
builds the
graph topology via the existing `buildWorkflowGraph` (covers if-else
branches and
iterator loops), feeds estimated node sizes into
`computeWorkflowLayout`, and
persists through the existing `updateWorkflowVersionPositions`.
- `create_complete_workflow`: removed `stepPositions` from the tool
schema; the
server always auto-lays out after creation/edges.
- `create_workflow_version_step`: re-tidies the whole version after each
added step
(wired at the tool level so the builder UI is unaffected) and dropped
the now
redundant `position` field.
## Notes
- Server-side layout uses estimated node sizes, so it is "good enough";
opening the
workflow and running the existing FE tidy-up refines it with real
measured sizes.
- Auto-layout is wired in the MCP tools, not in the shared creation
service, so
manual step creation in the builder UI is unchanged.
## Test plan
- [x] `twenty-shared` unit tests for `computeWorkflowLayout` (linear
chain, if-else
spread, dangling-edge safety)
- [x] `twenty-shared` builds; `twenty-server` and `twenty-front`
typecheck
- [x] Lint/format clean on changed files
- [ ] Create a workflow via AI Chat / MCP and confirm steps are laid out
without
overlap
- [x] Add a step via MCP and confirm the version is re-tidied
- [ ] Frontend "Tidy up" still behaves as before
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21756?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. -->
|
||
|
|
f1d4d6aeaf |
Converge Recall transcript artifacts on meeting start (#21788)
## what - converge stuck call recordings after the meeting starts, not based on scheduled end - replace the pending-transcript cron with Recall transcript artifact reconciliation - list existing Recall transcripts before creating one, so we avoid duplicate requests - keep a local pending marker as an idempotency latch for stale/redelivered events <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21788?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. --> |
||
|
|
454758471f |
Fix side panel command menu header controls (#21747)
## Summary Tested the 3 behaviors of the issue locally. Animation is not perfect on the closing but I think this is a great v1 - Move the side-panel close action to the right side of the top bar while keeping back navigation on the left. - Keep the nav side-panel button as the command-menu entry point for direct side-panel pages and hide it while command-menu pages/history are active. - Reset command-menu search/filter state when opening the root command menu from the nav button. Fixes twentyhq/core-team-issues#2504 ## Videos ### Before https://github.com/user-attachments/assets/08c1b6b3-5fbd-4154-a85d-5072a3b7690e ### After https://github.com/user-attachments/assets/11682dea-f21c-47b5-91a8-869f30b09d96 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21747?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. --> |
||
|
|
cc4659ce11 |
fix(server): type WasRemovedInUpgrade columns with WasRemovedInUpgrade<> (#21785)
## Context WasRemovedInUpgrade type brand was introduced in https://github.com/twentyhq/twenty/pull/21228/changes#diff-1b6d688610669a46b3ee8e3a41b1c7eb0ee03e19146d0d249f95df6e56164a92R15 for the `isCustom` property deprecation. The @WasRemovedInUpgrade decorator and the WasRemovedInUpgrade<T> type are meant to go together: the type brand makes the property optional in every derived flat-entity type, so the column only needs to be declared on the entity itself. RolePermissionFlagEntity.flag had the decorator but was typed as a plain PermissionFlagType, forcing the property to be supplied everywhere. This PR: - Types flag as WasRemovedInUpgrade<PermissionFlagType> (matching the isCustom reference impl on object/field metadata). - Removes the now-redundant flag from the flat-entity construction sites, the create input, and the service call site — leaving it only on the entity. The GraphQL RolePermissionFlagDTO.flag is kept (it's an API field derived from permissionFlag.key, not the removed column). - Fixes a latent brand-leak in the flat-entity config type: toStringify is computed via object-detection, and a branded type reads as an object. This was harmless for boolean but wrongly forced toStringify: true for enum/string columns. Added UnwrapWasRemovedInUpgrade<T> and applied it so the brand is transparent making the pattern work for any type, not just booleans. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21785?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. --> |