Commit Graph

10617 Commits

Author SHA1 Message Date
Thomas Trompette 1ec59beb8b docs(user-guide): remove unsupported Between operator for Number filters (#21838)
## What

The [Filters & Sorting user
guide](https://docs.twenty.com/user-guide/views-pipelines/capabilities/filters-and-sorting)
lists a **Between** operator for **Number** fields, but it does not
exist in the product and we do not plan to implement it. This removes it
from the operators table.

| Field Type | Before | After |
|---|---|---|
| Number | Equals, Greater than, Less than, **Between**, Is empty |
Equals, Greater than, Less than, Is empty |

The **Date** row is intentionally left untouched.

## Why

Avoids documenting a capability that does not exist (the same gap that
prompted #20932). Closing the external PR that attempted to add it for
numbers; the docs should reflect what we actually ship.

## Notes

- Only the canonical English source is edited; localized copies under
`packages/twenty-docs/l/*` are regenerated by the existing i18n
docs-translation workflow.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21838?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-19 12:58:50 +02:00
Félix Malfait 5674f693d7 fix: prevent Create Workspace redirect from being cancelled (#21835)
## Summary

Clicking **Create Workspace** in the multi-workspace dropdown did
nothing.

The handler closed the dropdown right before redirecting:

```ts
const createWorkspace = () => {
  closeDropdown(MULTI_WORKSPACE_DROPDOWN_ID); // unmounts this component
  redirectToDefaultDomain({ ... });           // schedules window.open ~1ms later
};
```

`redirectToDefaultDomain` → `useRedirect` wraps the navigation in
`useDebouncedCallback(..., 1)`. `closeDropdown` flips the dropdown
content to `{isDropdownOpen && ...}` → `false`, unmounting
`MultiWorkspaceDropdownDefaultComponents` — the component that owns that
debounced callback. `use-debounce` drops the pending call on unmount, so
the queued `window.open` never fires. React commits the unmount before
the 1ms timer, so it loses every time. Regression from #21723, which
added the `closeDropdown` call.

## Fix

Remove the `closeDropdown` call. The redirect navigates the whole page
away, so closing the dropdown first is unnecessary — and it mirrors the
sibling "switch workspace" handler, which already redirects without
closing.

## Why not reorder, or drop the debounce?

The 1ms debounce in `useRedirect` is intentional (#9079, "sleep before
redirect"). Callers set cookie-backed state immediately before
redirecting — e.g. `redirectToDefaultDomain` clears the
`lastAuthenticateWorkspaceDomain` cookie via `useCookieStorage`.
Deferring the hard navigation by one macrotask lets that cookie write
flush before the page tears down; removing it risks dropping the write.
Reordering wouldn't help either, since the unmount still beats the
timer. So the debounce is left untouched.

## Logout is not affected

`signOut` → `clearSession` navigates with `window.location.assign(...)`
directly (synchronous, not debounced) and never calls `closeDropdown`,
so it can't hit this race.

## Testing

- Before: clicking Create Workspace → `window.open` called 0 times, page
unchanged.
- After: navigates to
`<defaultDomain>/welcome?action=create-new-workspace` and renders the
"Create your workspace" form.
- Switch-workspace and Log out both still work.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21835?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-19 12:57:06 +02:00
Thomas Trompette 4075018834 fix(workflow): label manual trigger record output as Record/Records (#21832)
## What

The manual trigger output schema exposed the triggering record(s) under
a node labeled **Payload**. Relabels it to match what the node actually
contains:

- **Single-record** availability → **Record**
- **Bulk-records** availability → **Records**

## Why

"Payload" was a misnomer — the node holds the record(s) that triggered
the workflow. This is a display-label-only change.

## Notes for reviewers

- **No migration.** The persisted output schema key stays `payload`, so
existing variable references (`{{trigger.payload.x}}`) are unaffected.
- The front recomputes the output schema on the fly
(`computeStepOutputSchema`), so the variable picker shows the new labels
immediately, including for existing triggers.
- The backend (`workflow-schema.workspace-service`) is updated to match
for newly persisted/re-saved schemas. Previously persisted schemas keep
"Payload" until re-saved.
- Added `WORKFLOW_TRIGGER_RECORD_LABEL` /
`WORKFLOW_TRIGGER_RECORDS_LABEL` and removed the now-unused
`WORKFLOW_TRIGGER_PAYLOAD_LABEL`.
- Unit tests updated for both single and bulk cases (55/55 passing).

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21832?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-19 12:36:14 +02:00
martmull 8f7d7101ff Add application to test input in workflow (#21830)
https://github.com/twentyhq/twenty/pull/21791 follow up
2026-06-19 11:56:56 +02:00
Thomas Trompette 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.
2026-06-19 09:48:14 +00:00
Abdullah. 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.
2026-06-19 11:46:28 +02:00
Félix Malfait 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>
2026-06-19 11:40:55 +02:00
Harsh Singh 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>
2026-06-19 11:26:52 +02:00
martmull 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>
2026-06-19 11:08:52 +02:00
github-actions[bot] 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>
2026-06-19 10:40:27 +02:00
Parship Chowdhury 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>
2026-06-19 10:40:14 +02:00
Etienne 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. -->
2026-06-19 08:25:20 +00:00
Félix Malfait 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. -->
2026-06-19 10:24:31 +02:00
Abdullah. 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.
2026-06-19 10:22:46 +02:00
Félix Malfait 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. -->
2026-06-19 09:50:29 +02:00
nitin 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. -->
2026-06-19 13:07:21 +05:30
Rashad Karanouh 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.
2026-06-19 09:35:47 +02:00
Raphaël Bosi 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. -->
2026-06-19 09:10:01 +02:00
Abdullah. 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.
2026-06-19 08:56:00 +02:00
Abdullah. 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.
2026-06-19 08:49:07 +02:00
Abdullah. 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.
2026-06-19 08:47:11 +02:00
Abdullah. 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).
2026-06-19 08:46:32 +02:00
Yash Singh 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. -->
2026-06-19 08:29:47 +02:00
neo773 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>
2026-06-19 02:22:27 +02:00
Brendan Erofeev 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>
2026-06-19 00:09:57 +00:00
neo773 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. -->
2026-06-19 01:59:35 +02:00
Charles Bochet 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)
2026-06-19 01:58:08 +02:00
Thomas des Francs 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`. |
| ![Before: Options menu displayed
TABLE](https://raw.githubusercontent.com/twentyhq/twenty/7b1752e53cfa63868eb8ea46bd74fbadf89f3091/.github/pr-assets/view-type-labels/before.png)
| ![After: Options menu displays
Table](https://raw.githubusercontent.com/twentyhq/twenty/7b1752e53cfa63868eb8ea46bd74fbadf89f3091/.github/pr-assets/view-type-labels/after.jpg)
|

## 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.
2026-06-18 23:37:28 +00:00
Priyanshu Bartwal 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>
2026-06-19 01:36:27 +02:00
neo773 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. -->
2026-06-19 01:21:50 +02:00
Weiko 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
2026-06-19 01:19:49 +02:00
Félix Malfait 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>
2026-06-18 19:16:57 +00:00
Abdullah. 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"
/>
2026-06-18 20:43:56 +02:00
Abdullah. 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.
2026-06-18 20:43:24 +02:00
Marie 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>
2026-06-18 20:42:07 +02:00
Abdullah. 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.
2026-06-18 20:40:14 +02:00
Félix Malfait 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>
2026-06-18 17:56:14 +02:00
github-actions[bot] 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>
2026-06-18 16:49:03 +02:00
Thomas Trompette 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
2026-06-18 16:43:40 +02:00
Rashad Karanouh 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. -->
2026-06-18 14:29:35 +00:00
Abdullah. 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.
2026-06-18 19:29:21 +05:00
Raphaël Bosi 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. -->
2026-06-18 16:11:38 +02:00
Etienne 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. -->
2026-06-18 14:10:04 +00:00
nitin 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. -->
2026-06-18 19:04:25 +05:30
Thomas des Francs 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. -->
2026-06-18 13:30:05 +00:00
Weiko 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. -->
2026-06-18 15:23:24 +02:00
github-actions[bot] 2b3b2362db i18n - docs translations (#21789)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2026-06-18 15:21:04 +02:00
Thomas Trompette 22baf2c6c5 fix(server): prevent enum migration failure for long identifier names (#21748)
Fixes https://github.com/twentyhq/twenty/issues/20524

## Problem

Adding (or renaming/removing) an option on a `SELECT` / `MULTI_SELECT`
field failed for fields whose object + field name combination is long,
surfacing to the user only as:

> Migration action 'update' for 'fieldMetadata' failed — Migration
execution failed.

The real underlying Postgres error was:

```
type "_personalInsurancePolicyOrQuote_insuranceCoverageClassification" already exists
ALTER TYPE "..."."_personalInsurancePolicyOrQuote_insuranceCoverageClassifications_enum"
  RENAME TO "..._insuranceCoverageClassifications_enum_old"
```

## Root cause

PostgreSQL truncates identifiers to **63 bytes** (`NAMEDATALEN - 1`).

The enum type name
`_personalInsurancePolicyOrQuote_insuranceCoverageClassifications_enum`
is **69 chars**, so it was already stored truncated to 63
(`..._insuranceCoverageClassification` — the `_enum` suffix chopped
off).

Multi-select / select option changes go through the rename-and-recreate
path in `alterEnumValues`, which renames the enum to `<name>_old`. That
candidate is 73 chars → Postgres truncates it back to the **same 63-byte
string** as the source → `type "..." already exists`. The failure is
deterministic, so every retry on that field failed. The transaction
rolls back cleanly, leaving no `_old` artifacts behind.

The temporary column name (`<column>_old`) had the same latent bug for
very long field names.

## Fix

Add `buildTemporaryIdentifier(base, suffix)` to
`WorkspaceSchemaEnumManagerService`, which trims the base name so the
`_old` suffix survives within 63 bytes and stays distinct from the
original. Applied to both the temporary enum name and the temporary
column name.

The `_old` type/column are transient (dropped within the same
transaction), so the trimmed name only needs to fit and not collide —
which it now does.

## Test

Added `workspace-schema-enum-manager.service.spec.ts` reproducing the
exact failing object/field names. Both assertions (target identifier ≤
63 bytes; truncated source ≠ truncated target) fail on `main` and pass
with the fix.

## Recovery

No manual cleanup needed for affected workspaces — failed migrations
rolled back cleanly. Once deployed, option edits on long-named fields
work; the field remained fully usable in the meantime (only option
changes were blocked).
2026-06-18 15:09:12 +02:00
Raphaël Bosi a2d030e97a Fix flaky Argos diffs (#21771)
Inputs and selects with a trailing icon (copy button, chevron, etc.)
were flaky in Argos: the icon would intermittently flag as appearing
twice.

The icon's x-position depends on font-rendered content width, and under
Argos's `fit-content` capture the whole row is content-sized. Inter is
loaded from the Google Fonts CDN with `display=swap`, and Argos's font
gate (`document.fonts.status === "loaded"`) doesn't reliably catch the
late swap, so the snapshot sometimes lands pre-swap and sometimes
post-swap. The diff then paints both the old and new icon positions,
which reads as a duplicated icon.

This adds a Storybook loader that force-loads the Inter weights and
awaits `document.fonts.ready` before each story renders, so screenshots
are always taken post-swap. Fixes the whole class at once rather than
per component.

Note: affected trailing-icon stories will need a one-time Argos
re-baseline.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21771?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-06-18 13:05:31 +00:00
Raphaël Bosi b4113de74a Accessibility fix pass for twenty-ui input components (#21776)
Re-enables the live Storybook axe gate across the twenty-ui `input`
domain (17 story files) by removing the inherited `a11y: { test: 'todo'
}` overrides. Pilot for the wider a11y fix pass; other domains follow
the same playbook.

**What changed**
- Accessible names added to icon-only buttons, `Toggle`, `Checkbox`, and
the button groups via small additive `aria-label`/`ariaLabel` props on 8
components. Where this fully fixes the story, the override is dropped so
the full gate applies.
- Color-contrast (design-token level) is deferred — not changed — via a
new shared `A11Y_DEFER_COLOR_CONTRAST` parameter (`@ui/testing`) that
disables only the `color-contrast` rule while every other axe rule stays
enforced. Grep the constant to find all deferrals when tokens are
darkened later.
- `CatalogDecorator`: unique cell ids (clears `duplicate-id-aria`) and
dimension titles switched from empty `h1/h2/h3` to `div` (clears
`empty-heading`). Both help every domain's catalogs.

**Reviewer notes**
- No visual change: edits are aria / id / story-args / axe-config only,
so Argos parity holds.
- Input axe gate goes from 12 failing to 60/60 passing; typecheck and
lint green.

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