Commit Graph

10894 Commits

Author SHA1 Message Date
Félix Malfait d81b3c3fa3 feat(twenty-sdk): extract & compile app translations into the manifest (#22236)
## Summary

**PR 2/4** of the app-metadata-translations stack. Gives app developers
the authoring side, as part of the normal manifest build — and it stays
out of the way of developers who don't translate.

- `twenty-sdk` CLI i18n pipeline: collect translatable strings from the
manifest, generate value-as-key message ids (`sha256(value)` truncated,
byte-identical to the server's `generateMessageId`), a `dev
i18n-extract` command to scaffold per-locale catalog files, and a
compile step folded into `build` that emits `manifest.translations`.
- Opt-in: no `locales/` dir → `compileApplicationTranslations` returns
`undefined` → manifest is unchanged.
- Adds an optional `locale` to the front-component execution context so
components can translate against the host locale.

## Stack
Stacks on #22235 (PR 1/4). Base branch:
`claude/app-translation-1-runtime-resolution`.

## Tests
Unit (vitest): extract/compile round-trip + message-id determinism.

## Verification note
`yarn install` could not complete in the remote dev environment, so
typecheck/lint/tests were not run locally — **CI is the source of
truth**.

https://claude.ai/code/session_01NiE7o3cd3zCLZarVkJa6UA

---
_Generated by [Claude
Code](https://claude.ai/code/session_01NiE7o3cd3zCLZarVkJa6UA)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22236?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-28 07:36:48 +02:00
Félix Malfait 41c10b9ee7 feat(server): resolve app-owned metadata translations at runtime (#22235)
## Summary

First of a **4-PR stack** that lets apps built with `twenty-sdk`
translate their metadata, resolved at runtime. The standard Twenty app
is modelled as "an app like any other" — `NULL
applicationRegistrationId` ⟺ the standard app, no special-casing.

This PR adds the server foundation and wires runtime resolution for
**object** and **field** metadata:

- New `applicationTranslation` core table + entity (nullable
`applicationRegistrationId`, `locale`, `messages` jsonb), one row per
(app, locale) to avoid multi-MB rows.
- `ApplicationTranslationCacheService` (process-local, 30s TTL) +
`ApplicationTranslationSyncService` (upsert + soft-delete from a
manifest).
- Shared `translateStandardLabel` util: application catalog → i18n
bundle → source value.
- Object/field resolvers + dataloaders prefetch and apply the per-app
catalog. The new `applicationCatalog` param is **optional**, so standard
behaviour is byte-unchanged.
- Fast instance command to create the table.

## Stack
**PR 1/4**, targets `main`. Followed by: (2) twenty-sdk extract/compile
→ `manifest.translations`, (3) resolution across the remaining metadata
resolvers, (4) the per-locale standard-override editor.

## Tests
Unit: `translateStandardLabel`, `resolveObjectMetadataStandardOverride`
(including the application-catalog path).

## Verification note
The remote dev environment for this branch could not complete `yarn
install` (no package-registry egress), so typecheck/lint/tests were not
run locally — **CI is the source of truth** for this stack. Changes
follow existing patterns.

https://claude.ai/code/session_01NiE7o3cd3zCLZarVkJa6UA

---
_Generated by [Claude
Code](https://claude.ai/code/session_01NiE7o3cd3zCLZarVkJa6UA)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22235?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-28 07:36:21 +02:00
Félix Malfait 538b180824 feat(dpa): self-serve Data Processing Agreement generator (#22243)
## What

A single, region-aware DPA that serves all customers, generated
automatically from the customer's deployment. Two layers:

1. **Click-through DPA** — recorded at signup (acceptance = execution),
resolving merge fields from the deployment region. Cloud only.
2. **In-app signed-PDF generator** — Settings → Legal → Generate DPA:
preview the agreement, enter legal entity + authorized signatory,
download a PDF pre-signed by Twenty, and store the executed copy against
the workspace with its template version + timestamp. Deep-linkable at
`/dpa` (login-gated) for `twenty.com/dpa`.

## How it resolves

A typed variable matrix (`dpa-region-config.constant.ts`) maps the
deployment region to the contracting Processor entity and terms:

- **EU (default)** → Twenty.com SAS, hosting EU/Frankfurt, governing law
France, SCC section dormant.
- **US (custom)** → Twenty, Inc., hosting US, SCC section active.

Region is a deployment-wide setting (`DPA_DEPLOYMENT_REGION`, default
EU) behind a `DpaRegionService` seam so it can later become
per-workspace without touching callers. The legal text is verbatim from
the template (generated into `dpa-template.constant.ts` directly from
the source `.docx`); only the 6 merge fields are filled and the SCC
sections (7.2–7.5) stay in the document for every region per the spec —
only field values branch. Sub-processors are deferred to
trust.twenty.com (not enumerated). Billing stays decoupled (Twenty, Inc.
remains merchant of record regardless of Processor).

## UI

Standard list + create-page pattern (mirrors API keys / webhooks): a
list of executed copies (with re-download) — or the agreement preview
when none exists — and a top-right blue **Generate DPA** CTA opening a
standard create page. The "Legal" item is intentionally **not** in the
settings menu; the page is reached via the `/dpa` deep link.

## Notable implementation details

- **PDF** is rendered server-side with `@react-pdf/renderer`. The
built-in standard-14 fonts only encode ASCII and crash on the template's
curly quotes / em–en dashes / accented Latin, so Liberation Sans (OFL)
is **subset to a Latin glyph set and embedded as base64 data: URLs** —
no font files to ship or resolve at runtime (works in dev, prod-Docker
and CI).
- New `core.dpaAgreement` table via a fast instance command (FK hash
reproduced to match TypeORM).
- Self-hosted deployments (billing disabled) skip click-through
recording and stamp a prominent "not a valid agreement" banner on the
preview and PDF.

## Tests

- Unit: resolver (per-region entity/law/SCC state, EU default, no
unresolved `{{ }}`, SCC sections present in both regions, self-hosted
notice) and HTML renderer.
- Integration (`test/integration/graphql/suites/dpa`): preview has no
unresolved fields; `generateSignedDpa` renders + persists + returns a
downloadable PDF (asserted with accented input to guard the font
regression); list re-download.

## ⚠ Needs legal input before go-live (marked `TODO_CONFIRM` in
`dpa-region-config.constant.ts`)

- Registered-office addresses for Twenty.com SAS and Twenty, Inc.
- US deployment governing law (the template only specifies France).
- DPO name and the Twenty pre-signed authorized signatory name/title.

## Out of scope (flagged per spec)

Intra-group legal agreement and any Stripe/billing-entity changes. A
future e-sign provider would plug in at `DpaService.generateSignedDpa` +
the signatory input.

> Draft until the integration test passes in CI and the legal
`TODO_CONFIRM` values are supplied.

https://claude.ai/code/session_01Ahjydxx6J1souz1s1NeA9a

---
_Generated by [Claude
Code](https://claude.ai/code/session_01Ahjydxx6J1souz1s1NeA9a)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22243?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-27 17:46:38 +02:00
Abdullah. 77c84815ef feat(website): rework the product-stepper Layout visual (#22249)
## What

Reworks the **"Layout" step** visual of the product-page stepper (the
floating record-layout-editor scene) to match the Figma references and
read as a premium, layered composition. Data is unchanged — this is
purely the look.

## Figma alignment

- **Fields editor** — the inline-edit field row now keeps the field icon
+ a bordered input + blue **Done** + the eye / ⋮ (instead of a floating
box); **"New fields"** renders as a uniform field row; section headers
get an overflow menu; field types read `Boolean` / `Date & Time`.
Dropped the redundant "Fields widget" header label.
- **"General" widget** — header chevron, the real `anonymousFelix`
avatar on Account Owner (reusing the shared asset the other
product-feature visuals use), a **Revenue** row, and a money-bag icon
for the currency field.

## Premium composition

- The nav, record, and Fields editor read as one elevated **z-stack**
(back → front): the nav is pushed right so its edge tucks under the
record (lowest z-index), the record sits above it, and the editor stays
in front — each casting a progressively stronger shadow so the depth
ordering is unmistakable.
- The **"New record / Enrich / Edit actions"** bar is centered over the
nav–record seam (`translateX(-50%)`) to tie the layered stack together
at the top.

## Testing

- typecheck · oxfmt · oxlint · check-conventions — green.
- Verified visually with headless Playwright on `/product` (Layout
step).


<img width="697" height="734" alt="image"
src="https://github.com/user-attachments/assets/58ae7ffb-13d1-4310-bbdc-864589f8bc43"
/>
2026-06-27 17:41:34 +02:00
Félix Malfait 2a21eb46c0 perf: cap to-many relation records per parent and inline chips in table (#22206)
## Problem

Record table views that show a to-many relation column (e.g. Workflows
with a "Runs" column) get slow and janky to scroll when some records
have many related records.

Two root causes, found by profiling the page live:

1. **Backend over-fetch + unfairness.** Nested one-to-many relations
were loaded with a single flat limit of `QUERY_MAX_RECORDS_FROM_RELATION
* parentCount` shared across *all* parents in the page (`WHERE
parentColumn IN (ids) LIMIT 60*N`, no per-parent cap). A single hot
parent can consume the entire budget — returning thousands of rows for
one cell, and potentially starving sibling parents of records they
actually have. The `limit * parentCount` shape shows the original intent
*was* a per-parent budget; it was just implemented as a global limit.

2. **Frontend DOM explosion.** `ExpandableList` mounts the *entire*
child array inline (clipped with `overflow: hidden`) when unfocused, and
mounts all children for measurement when focused. A cell with 2,000+
relation chips mounts ~14k DOM nodes — one observed page reached ~55k
nodes for 43 rows, producing 100–300 ms main-thread long tasks on every
scroll.

## Fix

- **Backend:** load one-to-many relations with a true **per-parent** cap
via a `LATERAL` join — each parent runs its own indexed, `LIMIT`-ed scan
that stops after the per-parent budget. This is `O(perParentLimit ×
parentCount)` and never reads or sorts a parent's full relation set. The
per-parent query is built through the workspace query builder (so it
stays schema-qualified and keeps the soft-delete predicate) and wrapped
as a `FROM` subquery; read/row-level permissions are enforced when
records are hydrated by id, as elsewhere in the relation loader.
Many-to-one is unchanged.
- **Frontend:** add an opt-in `maxInlineCount` to `ExpandableList` so
to-many relation cells mount only a small inline preview; the expand
dropdown still renders the full fetched set. Fully backward compatible
(no cap → identical behavior).

## Why LATERAL over a window function

A windowed `ROW_NUMBER() OVER (PARTITION BY parent) <= limit` is correct
and fair too, but a window function **cannot stop early within a
partition** — it must read every matching row (and sort it). Measured on
skewed data (one parent with ~4k children, on the existing single-column
join index, PG16):

| Approach | Time | Buffers | Rows read from the hot partition |
|---|---|---|---|
| Pre-PR (`LIMIT 60×N`) | 1.6 ms | 91 | ~180 total, early-stops, but
**unfair** (starves siblings) |
| Window (`ROW_NUMBER`) | 3.7 ms | 128 | **all ~4k + sort** |
| **LATERAL (`per-parent LIMIT`)** | **0.5 ms** | **57** | **~60, index
early-stop** |

LATERAL matches the pre-PR read cost while being fair, needs no new
index, and scales independently of how large any single relation is.

## Verification

- Backend integration test (`nested-relation-per-parent-limit`): a
parent with 65 children is capped at 60 while a sibling with 3 keeps all
3 — passes.
- `EXPLAIN ANALYZE` on the generated SQL: Index Scan with the `LIMIT`
pushed into the per-parent lateral (early-stop).
- Frontend unit test for the `ExpandableList` cap.
- Manual check on a table cell with 40 related records: exactly 10 chips
mount inline (down from 40), no console errors, chips still clickable
and the overflow count reflects the true total.
2026-06-27 15:12:43 +02:00
github-actions[bot] 4840233a1c i18n - website translations (#22244)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22244?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-27 14:06:24 +02:00
Félix Malfait 0e22ae0521 feat: create calendar events on Google and Microsoft accounts (#22231)
## Context

Twenty can import calendar events and send emails, but cannot create
calendar events. This adds calendar event creation on connected
**Google** and **Microsoft** accounts, mirroring the existing email-send
architecture (`message-outbound-manager`).

## What it adds

The capability is exposed three ways, all backed by the same composer →
driver → persist pipeline:

- **GraphQL mutation** `createCalendarEvent` (metadata API)
- **AI agent tool** `create_calendar_event` (flows to MCP
automatically), gated by a new `CREATE_CALENDAR_EVENT_TOOL` permission
flag
- **Workflow builder node** "Create Calendar Event" in the **Core**
section, with a full settings form (variable interpolation supported)

CalDAV/IMAP is intentionally out of scope for now (different long pole).

## Design notes

- **Reuse over reinvention** — the created event is run through the
existing inbound formatters (`formatGoogleCalendarEvents` /
`formatMicrosoftCalendarEvents`) and persisted immediately via the
existing `CalendarSaveEventsService`, so it appears in Twenty right away
and is reconciled by the next provider sync (dedup on external id).
Persistence is best-effort.
- **OAuth scopes** — Google already requests `calendar.events`
(read+write), so no change there. Microsoft moves `Calendars.Read` →
`Calendars.ReadWrite`; existing Microsoft accounts must re-consent
(surfaced as a clear "reconnect" error via a missing-scope check).
- **Deliberate invitation semantics** — `sendInvitations` is off by
default. When off, the event is created with **no attendees** on either
provider, so creating an event never silently emails external people.
When on, attendees are attached and notified (Google `sendUpdates: all`,
Microsoft's default). This sidesteps Microsoft Graph having no
per-request suppression.
- **Timezone correctness** — Microsoft Graph interprets `dateTime` as
wall-clock in the supplied `timeZone` and ignores the offset, so the
absolute instant is converted to its wall-clock form before sending
(Google honors the offset directly). Both providers end up scheduling
the same instant.
- **Conferencing** — optional Google Meet
(`conferenceData.createRequest`, with a follow-up `events.get` to
resolve the async link) / Microsoft Teams (`isOnlineMeeting`).
- Attendees are a comma-separated string everywhere (tool input, GraphQL
DTO, workflow input), consistent with `send_email` recipients; the
composer parses to its internal list.

## Test plan

- **Unit**: 45 tests covering the composer (validation, all-day
boundaries, offset enforcement, timezone, scope checks, default-account
resolution), both provider drivers, the dispatcher, and the workflow
step-log builder.
- **Integration**: `createCalendarEvent` on the `/metadata` API fails
closed with a structured error for a non-existent account (the
auth/ownership/validation path that doesn't require provider mocking).
- **Manual**: verified the workflow node appears in the Core section,
the settings form renders and round-trips (edit → autosave → reload),
and the live mutation returns a structured failure for a bogus account.

## Open question for reviewers

The metadata mutation `createCalendarEvent` shares a name with the core
schema's auto-generated `createCalendarEvent(data:)` CRUD mutation for
the CalendarEvent object — they live on different endpoints (`/metadata`
vs `/graphql`) so there's no runtime conflict, but it's a potential
point of confusion for API consumers. Happy to rename (e.g.
`createCalendarEventOnConnectedAccount`) if preferred.

## Out of scope / follow-ups

- CalDAV/IMAP support
- Event update/delete and recurrence
- Existing Microsoft accounts need re-consent for the widened scope


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22231?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: neo773 <neo773@protonmail.com>
2026-06-27 14:05:58 +02:00
Abdullah. 03f3789d13 feat(website): menu adapts to the section beneath it (#22241)
## What

The sticky menu now adopts the color scheme of whatever section sits
beneath it as you scroll, across every page. Background, logo, and
buttons ease between schemes for a smooth handoff.

## How

- **Declarative contract** — sections opt in with a `data-menu-surface`
attribute (`SectionShell`, the footer, and the product hero's mobile
sections); `useActiveSurfaceScheme` tracks which surface sits under the
menu's bottom edge (`MENU_HEIGHT_PX`) and reports its `data-scheme`.
- **One provider** — `MenuStyleProvider` lives in the `(site)` layout so
every page adapts; the Menu resolves its scheme as `override ??
activeScheme ?? prop`. SSR seeds the prop to each page's first section,
so there's no mount flash.
- **Hero handoff** — the product hero keeps the menu via a per-frame
override only while its track still covers the nav band
(`controlsMenu`); it releases to the observer *before* the track clears
the bar, so the menu stays opaque on exit and never flashes the halftone
backdrop rising behind it. The menu's `backdrop-filter` was removed (a
no-op over the opaque menu, and a GPU compositing artifact).

## Also in this PR

- **Menu folder reorg** to the `product-feature` convention:
`components/`, `effect-components/`, `data/`, and `types/` (one domain
type per file).
- **`MENU_HEIGHT_PX` token** replacing the literal `64` that was
duplicated across four files (menu row, the hero's scroll model and
component, the observer).
- **`findActiveSurfaceScheme`** extracted as a pure, unit-tested
function (inclusive top / exclusive bottom, first-match, no-match,
null-scheme).
- **Mobile AI-section fix** — the mobile hero sections now declare the
surface contract, so the menu adapts dark over the AI block on mobile
(it previously stayed light). Their color is driven from `data-scheme`
(single source) rather than a parallel prop.

## Testing

- Jest — 16 tests (scroll model + surface-selection util).
- Headless Playwright (mobile 390px) — menu `light` over the intro →
`dark` over the AI section; section colors unchanged (`#fff` /
`rgb(20,20,20)`).
- Desktop unaffected — the mobile sections are `display:none` (zero
rect), so the observer skips them and the hero override path is
untouched.
- Gates green — typecheck, oxlint, oxfmt, check-conventions.
2026-06-27 12:58:50 +05:00
github-actions[bot] f041f6dfb6 chore: sync AI model catalog from models.dev (#22242)
Automated daily sync of `ai-providers.json` from
[models.dev](https://models.dev).

This PR updates pricing, context windows, and model availability based
on the latest data.
New models meeting inclusion criteria (tool calling, pricing data,
context limits) are added automatically.
Deprecated models are detected based on cost-efficiency within the same
model family.

**Please review before merging** — verify no critical models were
incorrectly deprecated.

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

Co-authored-by: FelixMalfait <6399865+FelixMalfait@users.noreply.github.com>
2026-06-27 08:59:37 +02:00
github-actions[bot] bf7a766935 i18n - website translations (#22234)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22234?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-26 22:29:54 +02:00
github-actions[bot] ad1ac4f3c5 i18n - website translations (#22225)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22225?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-26 22:17:08 +02:00
Etienne 3525187321 fix(ai) - fixes (#22227)
- ai chat author fix (before : "workflow", after : "user")
- https://discord.com/channels/1130383047699738754/1496872385687584768

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22227?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-26 17:53:29 +00:00
Abdullah. d58ec64e10 feat(website): swap product Demo for a register Signoff; make signoff headings width-driven (#22232)
## Summary

Reworks the product page's closing section, and in doing so makes every
signoff closer width-driven.

### 1. Replace the Demo section with a register Signoff (`6a5f0c34bf`)

The product page ended on a static `AppPreview` mockup ("Try it live").
Swap it for the shared `Signoff` closer — a focused register CTA, the
same component the customers / why-twenty / partners pages use — so the
page ends by driving sign-ups instead of re-showing the product.

- New `ProductSignoff`: heading "Start moving faster today.", a
supporting line, and the **Get started + Talk to us** pair (matching
`CustomersCatalogSignoff`). Uses `SITE_URLS.appWelcome` per the
site-urls rule rather than inlining the URL.
- Removes the now-unused `product-demo` section and its 192KB background
asset.

### 2. Make Signoff headings width-driven (`0365c0d2ea`)

The signoff headings forced their two-line break with a literal `\n` in
the translated string — against the site's typography principle (fluid
type + `text-wrap: balance`, no `<br>`).

- `Signoff` now carries one default **`615px`** heading measure; all
five closers drop `\n`.
- The width was **measured, not guessed**: each heading was rendered
headlessly in the real production fonts (Aleo 300 body, Host Grotesk 300
accent — each heading mixes both) at the desktop size with `text-wrap:
balance`, then I found the common window where every heading breaks
identically: whyTwenty `[565–820]`, customers `[425–800]`, partner
`[380–730]`, product `[380–660]` — `615` sits in all of them. Each was
then verified to reproduce its exact current break at 615px.
- **Affects 4 other pages** (why-twenty, customers, customers/[slug],
partners) — all verified to render identically, and headings now reflow
on narrow viewports instead of being pinned by `\n`.
- Drops the one-off `headingMaxWidth` prop (now unused).

## Test plan

- oxfmt, oxlint, check-conventions, typecheck — all green.
- Headless measurement confirms all five signoff headings reproduce
their current desktop break at 615px (no visual regression).
- Visually confirmed the product closer and the four sibling signoffs.
2026-06-26 18:45:39 +02:00
martmull e747bc3e42 Add backfill installation feature for pre-installed apps (#22199)
## After

<img width="1070" height="514" alt="image"
src="https://github.com/user-attachments/assets/9cbd2ff5-1678-4c2f-84da-074568f37c51"
/>


## Summary
Adds the ability to backfill application installations across all
existing workspaces. This allows admins to retroactively install a
pre-registered application on every active and suspended workspace
through a background job, making the feature idempotent and
non-blocking.

## Key Changes

- **Backend Service**: Added `backfillApplicationOnAllWorkspaces()`
method to `PreInstalledAppsService` that:
  - Validates the application registration exists
  - Iterates through all workspaces using `WorkspaceIteratorService`
  - Installs the app on each workspace
  - Swallows `APP_ALREADY_INSTALLED` errors for idempotency
  - Logs success/failure counts

- **Background Job**: Created `BackfillApplicationInstallationJob` to
process backfill requests asynchronously via the message queue

- **GraphQL Mutation**: Added `backfillApplicationInstallation` mutation
to `AdminPanelResolver` that:
  - Validates the application registration exists
  - Enqueues the background job
  - Returns immediately without blocking the request

- **UI Components**: Enhanced
`SettingsAdminApplicationRegistrationGeneralToggles` with:
- New "Pre-install on new workspaces" toggle for the `isPreInstalled`
flag
  - "Backfill on all workspaces" button with confirmation modal
  - Loading state and success/error snack bar feedback

- **Data Model**: Added `isPreInstalled` field to
`UpdateApplicationRegistrationPayload` input type

- **Tests**: Added comprehensive unit tests for
`PreInstalledAppsService.backfillApplicationOnAllWorkspaces()` covering:
  - Missing registration validation
  - Successful multi-workspace installation
  - Idempotent handling of already-installed errors
  - Proper error propagation for unexpected failures

## Implementation Details

The backfill operation is designed to be:
- **Idempotent**: Already-installed apps are skipped without error
- **Non-blocking**: Runs as a background job via message queue
- **Resilient**: Per-workspace failures don't block other installations
- **Observable**: Logs aggregated success/failure counts for monitoring


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22199?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: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2026-06-26 16:21:27 +00:00
Abdullah. fea2b8736f feat(website): rebuild dashboard visual faithful to twenty-front (#22218)
Rebuilds the product-feature **DashboardVisual** to mirror
twenty-front's dashboard widgets, with colors traced to twenty-front's
actual source rather than eyeballed.

## Widgets
- **Bar — "Deals by month"**: single-series `blue8` (twenty-front's
`GRAPH_DEFAULT_COLOR`), dashed `4 4` gridlines, value labels, nice
rounded Y-ticks. Replaces the old stacked bar (stacked bars aren't used
in twenty-front).
- **Donut — "Deals by stage"**: the real opportunity pipeline
(New/Screening/Meeting/Proposal/Customer), each segment colored by that
stage option's own color from the metadata
(`red/purple/sky/turquoise/yellow`), with a center total and a paginated
horizontal legend.
- **KPIs**: big-number cards (Revenue YTD / Avg deal size / Win rate).

## Responsive — `mediaUp('md')`
The dashboard is the full-width spotlight tile, whose frame is short
below md and grows to 420px at md+. So the layout keys off md: below it
collapses to **2-up KPIs + a full-width bar** (donut and the 3rd KPI
hidden, smaller breadcrumb), and the spotlight frame's mobile min-height
is bumped so the bar has room; at md+ the full 3-KPI + side-by-side
layout returns. The donut caps at its size and shrinks with its
container.

## Notes
- Follows the `product-feature` conventions (per-visual folder,
one-export-per-file, split types).
- `Tiles.tsx`: one-line spotlight mobile min-height bump (only the
dashboard uses the spotlight tile).
- typecheck + lint + build all green.

<img width="1148" height="638" alt="image"
src="https://github.com/user-attachments/assets/1eb8f120-88c2-45d1-adf9-ec00abe11006"
/>
2026-06-26 20:57:21 +05:00
martmull 8a257e0de3 Update public app names (#22228)
- update names to comply with @twentyhq/ prefix standard
- update versions
- upgrade twenty sdks versions 

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22228?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-26 17:51:01 +02:00
Amresh Chaurasiya e1120d38b6 fix: stamp MCP and AI Agent writes with FieldActorSource.AGENT (#22215)
## Description
Fixes #21437 MCP and AI Agent writes now correctly stamped with

### Problem
Records created through MCP server were stamped as `WORKFLOW`, making
them indistinguishable from workflow-created records. This breaks
loop-protection filters that skip workflow-originated records.

### Solution
- MCP writes now correctly stamped with `createdBy.source = AGENT`
- AI Agent execution now uses `AGENT` instead of `MANUAL`
- Added `WorkspaceCacheModule` to MCP module
- Updated tests to verify AGENT source

### Files Changed
- `mcp.module.ts`: Added WorkspaceCacheModule import
- `mcp-protocol.service.ts`: Set AGENT source in buildMcpToolSet
- `mcp-protocol.service.spec.ts`: Updated tests
- `agent-actor-context.service.ts`: Changed MANUAL → AGENT
- 

## Type of Change
- [x] Bug fix (non-breaking change)

## Checklist
- [x] Code follows project style
- [x] Tests added/updated
- [x] Issue linked

Fixes #21437

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22215?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-26 17:38:05 +02:00
Raphaël Bosi c891258f34 Add v2 onboarding create profile page (#22221)
<img width="3024" height="1498" alt="CleanShot 2026-06-26 at 15 30
23@2x"
src="https://github.com/user-attachments/assets/8b4863a9-66ed-4da1-851b-473cedf71511"
/>

<img width="3022" height="1500" alt="CleanShot 2026-06-26 at 15 29
43@2x"
src="https://github.com/user-attachments/assets/22fc0e94-f670-4638-975c-f06b2b2e25e8"
/>

Adds the v2 onboarding **Create profile** page, shown right after the
import-contacts step (`PROFILE_CREATION`) for the onboarding-v2 cohort.
It renders full-screen under `BlankLayout` via the shared
`OnboardingV2Layout`, matching the Figma (340px column, inline round
avatar uploader + First/Last row, Job Title, dark Continue). The v1
modal flow is untouched and still used for non-v2 users.

Job Title is wired end-to-end: it adds a real `jobTitle` field to the
`WorkspaceMember` standard object (shared metadata constant + flat field
metadata + entity property) and a `2-17` workspace upgrade command to
backfill the field on existing workspaces. Continue persists name +
jobTitle through the existing `updateWorkspaceMemberSettings` mutation,
whose allow-list picks up the new standard field automatically.

Routing mirrors `SyncEmailsV2`: new `AppPath.CreateProfileV2`, lazy
route, and an `isOnboardingV2`-gated branch in
`usePageChangeEffectNavigateLocation` (+ tests and a Storybook story).

Reviewer notes:
- `jobTitle` is **write-only** for now (no read-back path: core
DTO/transpiler/fragment unchanged), and the field is
`isSystem`/non-UI-editable to match its siblings. Easy to surface later
if wanted.
- New `OnboardingProfilePictureUploader` is a compact round avatar
uploader reusing the same upload mutation flow as
`WorkspaceMemberPictureUploader`.


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22221?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-26 17:34:44 +02:00
martmull 1bf0d06e18 Add author to twenty apps (#22226)
as title

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22226?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-26 15:11:37 +00:00
Félix Malfait 7caf2a14c5 feat(website): show partner location instead of served regions in marketplace (#22224)
## Context

On the partners marketplace, the eyebrow shown below each partner's name
(on both the list card and the profile header) displayed the served
regions (e.g. `APAC`, `EUROPE`). The partner's actual location was
buried in the "Where & how" facts list. The location is more useful at a
glance, so this swaps the two.

## Changes

**List card (`PartnerCard`)**
- The eyebrow below the name now shows the partner's city and country
instead of the first served region.

**Profile page**
- `PartnerProfileHeader`: the eyebrow near the title now shows the real
location (city, country).
- `PartnerFactsList`: the served regions move into the "Where & how"
section as a `Regions` row, taking the place of the now-redundant "Based
in" row.

The `Regions` chip rows on the card are unchanged.

https://claude.ai/code/session_01FQRRfvPpmNsjAAnZeDc5sy

---
_Generated by [Claude
Code](https://claude.ai/code/session_01FQRRfvPpmNsjAAnZeDc5sy)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22224?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-26 16:55:49 +02:00
Abdullah. 1b4bddba6f fix(website): polish product hero AI transition (panel reveal timing + title wrapping) (#22223)
## Summary

Two desktop/tablet polish refinements to the product page hero's hero→AI
transition (`product-hero`).

### 1. Delay the Ask-AI panel reveal to after the wipe (`b638d600`)

The Ask-AI side panel used to reveal *during* the dark "wipe up" (keyed
to the morph `0.45 → 0.70`), so it competed with the rising black edge
and piled onto the heaviest motion phase — rough on weaker laptops.

It's now keyed to raw scroll progress over `[0.60, 0.70]` — the
previously-idle hold after the wipe settles at `0.55` — so it slides in
on its own beat once the eye has followed the black up. `morphProgress`
is pinned at `1` across the whole post-wipe stretch, so leaving the
morph clock was the only way to time the panel *after* the wipe. The
conversation playback moves with it, so the chat doesn't stream while
the panel is still `width: 0`.

### 2. Keep the AI heading to three lines below desktop (`8ae69a4b`)

The AI heading is the page's longest line. Below `md`, only the mobile
copy renders, where the measure was frozen at `360px` while the heading
font scales fluidly toward `md` — so on tablet widths the title crammed
onto four lines, recovering only at `921px` when the `672px` measure
kicks in.

Adds an `sm`-breakpoint measure step (`560px`) scoped to the AI heading;
the intro heading and the desktop measuring path keep their existing
`360/672` steps.

## Test plan

- `product-hero-scroll-model` jest suite updated and green (11/11),
including a new post-morph panel-timing test.
- Lint (`check-conventions` + `oxlint` + `oxfmt`) and typecheck green.
- Visually confirmed: the panel reveals after the wipe settles, and the
AI title holds three lines across tablet widths.
2026-06-26 19:43:45 +05:00
Weiko adf56cac56 fix(workflow): avoid 'file not found' when duplicating unbuilt code steps (#22179)
## Context
Duplicating a workflow version (e.g. when editing an active workflow)
clones each Code step's logic function via copyResources, which copied
both the source and the built artifact unconditionally. Logic functions
created from source are not built yet (no index.mjs,
isBuildUpToDate=false), so copying the missing built file threw
FILE_NOT_FOUND.

Copy the built artifact only when it exists. This is safe because the
duplicate inherits isBuildUpToDate=false and is rebuilt lazily on
activation or run.

In seed dev case for example, they are created with source but never
built

## Test


https://github.com/user-attachments/assets/5a7febba-413b-4041-985e-f84a99a5ebda
2026-06-26 16:19:35 +02:00
Thomas des Francs b84f748237 Fix navigation opened section spacing (#22222)
## Summary

- Remove the collapsed empty `Opened` sidebar section when no opened
object exists.
- Restore the first navigation section top alignment with the settings
`User` section.

## Before/After

Visual verification after the fix: in the current fixture data, the
first visible main sidebar section (`Favorites`) starts at y=92,
matching the settings `User` section at y=92. This confirms there is no
collapsed `Opened` spacer pushing the main drawer content down.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22222?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-26 15:58:57 +02:00
Félix Malfait da6a2ee300 fix(ai-chat): keep streams alive on silent SSE death + make the stream job idempotent (#22201)
## Problem

In production, an AI-chat assistant response sometimes freezes
mid-stream (partial text, looks hung), then "picks up again on its own"
later without the user resending and without a known worker restart.

Root cause: the **agent-chat SSE subscription has no keepalive and no
silent-death detection**.

- Delivery is fire-and-forget Redis pub/sub
(`SubscriptionService.publishToAgentChat`) and the resolver returns the
**raw** iterator — unlike `EventStreamResolver`, which heartbeats every
30s via `wrapAsyncIteratorWithLifecycle`.
- During a quiet model/tool gap the connection sends no bytes, so a
proxy/LB/NAT can silently drop it mid-stream. `graphql-sse` neither
surfaces an error nor resumes with `Last-Event-ID`, and **nothing
re-pulls the existing Redis chunk catch-up on reconnect** (it only runs
on thread (re)mount / `message-persisted` refetch).
- So the live view freezes; recovery only happens when the terminal
`message-persisted` fires a full refetch from the DB — the observed
"self-recovery".

This is the **same silent-SSE-death class fixed for the DB event stream
in #21061**, which was never applied to the agent-chat path. The symptom
also matches #21096 (worker logs the job finishing, client never
updates, reload shows the message).

It is **not** queue prioritization, and it is **not** addressed by
#22193 (which only stabilizes the assistant message id and removes
end-of-stream flicker).

A secondary, independent self-recovery path also existed: BullMQ
stalled-job re-run (default 30s `lockDuration`, no idempotency guard)
re-streaming the whole turn → duplicate assistant messages / double
billing.

## Changes

### Commit 1 — keepalive + silent-death recovery (ports the #21061
pattern to agent chat)
- **Shared:** new `keepalive` variant on `AgentChatSubscriptionEvent`.
- **Server:** wrap the agent-chat subscription iterator with
`wrapAsyncIteratorWithLifecycle` — emit a `keepalive` on connect and
every `APPLICATION_KEEPALIVE_INTERVAL_MS` (30s) so the connection keeps
flushing bytes and a dead connection becomes detectable.
- **Client:** track the last received event timestamp (refreshed on
every chunk/keepalive in the SSE `next` sink); new
`AgentChatStreamKeepAliveEffect` forces a resubscribe + messages refetch
after 90s of silence, so the durable Redis chunk list backfills the gap
(`firstLiveSeq` is reset on resubscribe).

### Commit 2 — stream-job idempotency + lockDuration
- Thread a `lockDuration` option through `MessageQueueWorkerOptions` +
the BullMQ driver; set `aiStreamQueue` to 10 min so long streams aren't
falsely stalled.
- Guard `StreamAgentChatJob.handle` with a `streamId`-scoped Redis lock
(`SET NX PX` + compare-and-delete release) so a stalled re-run is
skipped instead of double-processing.

## Verification

⚠️ I could **not run typecheck/lint locally** — `yarn install` could not
complete in this environment (transient registry network aborts before
the link step, so `node_modules` never populated). **Please rely on CI
for type/lint verification.** The changes are written to match existing
conventions; the points most worth a reviewer's eye are the resolver's
iterator typing and the ioredis `set(..., 'PX', ttl, 'NX')` overload.

How to confirm the root cause in prod: a frozen client with the worker
logging `StreamAgentChatJob processed in …ms` and no `[AI_CHAT_NO_TEXT]`
is the silent-death signature (check reverse-proxy idle/buffering). For
the secondary path, watch `aiStreamQueue` `stalled`/re-processed metrics
and duplicate turns around worker restarts.

## Notes / trade-offs
- The 10-min `lockDuration` means a genuinely crashed worker's job isn't
reclaimed for up to 10 min; the client-side keepalive/catch-up recovers
the view independently, and the idempotency lock prevents duplicates.
Faster dead-worker recovery could be a follow-up.
- Touches `useAgentChatSubscription.ts` / `AgentChatRuntimeEffects.tsx`
/ `stream-agent-chat.job.ts`, which #22193 also touches — trivial rebase
expected.

Opened as **draft** pending CI.

https://claude.ai/code/session_018dF82A1VcsuWMxPLmdY3dm

---
_Generated by [Claude
Code](https://claude.ai/code/session_018dF82A1VcsuWMxPLmdY3dm)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22201?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-26 15:19:34 +02:00
Raphaël Bosi 8f7d6c24dd Add v2 onboarding import contacts page and unify the onboarding v2 shell (#22212)
<img width="3024" height="1668" alt="CleanShot 2026-06-26 at 13 29
28@2x"
src="https://github.com/user-attachments/assets/9bdd0029-45eb-4ddb-859f-eaab9bb61406"
/>

Adds the new v2 onboarding **Import contacts** step (email + calendar
import), shown right after workspace creation in the v2 flow. The
presentational page was designed in a previous PR; this wires it in and
unifies the shell.

**What changed**
- Reuses and unifies the existing v2 onboarding shell: extracts
`OnboardingV2Layout` + `OnboardingV2Header` (the back + logo header, now
with the free-credits pill), and the `SignInUpV2` workspace-creation
step renders through it (old `SignInUpV2Header` removed).
- New `SyncEmailsV2` route (`/sync/emails-v2`) under `BlankLayout`,
wired to the same OAuth/skip hooks as v1 `SyncEmails`.
- The `SYNC_EMAIL` step routes to the new page only when
`isOnboardingV2` is set (mirrors the existing `WorkspaceActivation` →
`WorkspaceActivationV2` branch); the v1 modal is unchanged for the
non-v2 flow.
- No backend changes — reuses the `SYNC_EMAIL` status and
`skipSyncEmailOnboardingStep` mutation.

**Reviewer notes**
- Connect defaults to `METADATA` (private) visibility to match the "Only
you will be able to see your emails and events" note (v1 had a selector
defaulting to `SHARE_EVERYTHING`).
- The header free-credits pill shows `0` for now (no current-workspace
credits source on the frontend yet).
- The back button is hidden on the import page (no meaningful "back"
after workspace creation); unchanged on the workspace-creation step.
2026-06-26 12:54:52 +00:00
neo773 9747e3a7a3 feat(messaging): link emails by Reply-To as a REPLY_TO participant (#22216)
Relay senders (e.g. a website form sending as a shared address with the
real contact in Reply-To) never linked to the contact because matching
only used From/To/Cc/Bcc. Record Reply-To addresses under a new REPLY_TO
participant role across the Gmail, Microsoft and IMAP drivers, excluding
any that just repeat the sender.

Adds the REPLY_TO option to the messageParticipant role field and a 2.17
workspace command to backfill it for existing workspaces.

QAed with real test run

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22216?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-26 17:58:51 +05:30
Weiko ebe067f65c Fix favorite showing non-readable objects (#22217)
## Context
Navigation menu items backed by objects the user has no read permission
on were correctly hidden from the Workspace section, but Favorites still
showed them. Favorites are user-scoped nav items (tied to
workspaceMemberId), and FavoritesSection only filtered out folder
children (!item.folderId) without ever checking canReadObjectRecords.

The shared upstream filter (filterAndSortNavigationMenuItems)
intentionally does not apply read permissions, because its output also
drives drag-and-drop position math and layout-customization/edit mode,
which need the complete, unfiltered list. So read-permission filtering
belongs at the display layer.

## Fix
New shared hook useReadableNavigationMenuItems that centralizes the
read-permission filtering logic previously duplicated across sections:
wires up objectMetadataItems + views + object permissions around
isNavigationMenuItemReadable
filters folder children and top-level items (dropping folders whose
children are all unreadable)
exposes both raw filtered* outputs and
isLayoutCustomizationModeEnabled-aware display* outputs
FavoritesSection now applies the filter via the hook, so unreadable
favorites are hidden — while still showing everything in
layout-customization mode (consistent with the Workspace section).
WorkspaceSectionContainer refactored to consume the same hook, removing
its inline isItemReadable, the dual-map reduce, and inline filtering.

## Before
### With access
<img width="842" height="570" alt="Screenshot 2026-06-25 at 14 01 38"
src="https://github.com/user-attachments/assets/ae51f3c8-c178-4162-84ce-3fe49cf07987"
/>

### Without access
<img width="987" height="590" alt="Screenshot 2026-06-25 at 14 02 08"
src="https://github.com/user-attachments/assets/94dacea3-bd77-4fcb-868d-353ed513b28c"
/>

## After
### Without access
<img width="1001" height="615" alt="Screenshot 2026-06-25 at 14 02 46"
src="https://github.com/user-attachments/assets/e1ccd5d9-8583-4ff1-ab91-6f6d187925c5"
/>


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22217?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-26 11:55:09 +00:00
Thomas des Francs fdf9f543ae Fix rounded page layout tab edit outline (#22214)
## Summary
- Round the edited page layout tab outline to match the tab hover
radius.

## Test plan
- `npx oxfmt --check
packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableTab.tsx`
- `npx oxlint --type-aware -c packages/twenty-front/.oxlintrc.json
packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableTab.tsx`
- `npx nx run twenty-front:lint`
- Browser: verified the edited `Notes` tab outline before/after locally.

## Visual
<img width="1108" height="392" alt="clipboard"
src="https://github.com/user-attachments/assets/1cb63180-83a9-4ef8-9c55-2b475eaaa02b"
/>

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22214?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-26 10:45:51 +00:00
martmull a22eb5a591 Bump call-recorder and people-data-lab apps (#22213)
as title

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22213?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-26 12:21:43 +02:00
Thomas des Francs bea7857a1c Fix settings page root overflow (#22207)
## Summary

- Clamp the fixed app shell with `overflow: hidden` so long nested
settings scroll content no longer contributes to the document root
scroll range.
- Keeps the object permission page scrolling inside its existing
settings `ScrollWrapper` instead of letting the whole page slide under
the viewport.

## Screenshots

Before: root document can scroll under the bottom of the viewport and
exposes the gray app background.

![Before root
scroll](https://raw.githubusercontent.com/twentyhq/twenty/bonapara/pr-screenshots-settings-root-scroll-20260626/.github/pr-screenshots/fix-settings-root-scroll/before-object-permission-root-scroll.png)

After: the same root scroll attempt leaves the app shell fixed to the
viewport.

![After root
scroll](https://raw.githubusercontent.com/twentyhq/twenty/bonapara/pr-screenshots-settings-root-scroll-20260626/.github/pr-screenshots/fix-settings-root-scroll/after-object-permission-root-scroll.png)

## Browser Validation

Route tested:
`/settings/members/roles/78fa69cb-1237-43b5-bfa5-5a11a47bf781/object/5ab1a16b-7811-471f-ac53-940666c667dd`

- Before on `http://apple.localhost:3001`: `window.scrollTo(0, 9999)`
moved the root to `scrollY=244.5`; `htmlScrollHeight=1287`,
`htmlClientHeight=1043`.
- After on `http://apple.localhost:3002`: the same root scroll attempt
stayed at `scrollY=0`; `htmlScrollHeight=1043`, `htmlClientHeight=1043`.
- The settings content still scrolls internally: wrapper
`scrollHeight=1475`, `clientHeight=955`.

## Checks

- `git diff --check`
- `yarn oxfmt --check
packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx`
- `cd packages/twenty-front && npx oxlint --type-aware -c .oxlintrc.json
src/modules/ui/layout/page/components/DefaultLayout.tsx`


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22207?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-26 11:34:19 +02:00
martmull d6fca7a82e Bump call-recorder and people-data-lab apps (#22208)
as title

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22208?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-26 11:24:56 +02:00
Thomas des Francs 34dc681c0e Use settings icon in settings drawer tab (#22204)
## Summary

- Let the shared navigation drawer tab row accept a custom icon and
accessible label for its navigation tab.
- Use the Settings icon and Settings label when that tab row is rendered
inside the settings drawer.
- Keep the main navigation drawer defaulting to the Home icon.

## Screenshots

### Before

<img width="420" alt="Before: settings drawer navigation tab uses the
Home icon"
src="https://github.com/user-attachments/assets/03b684ba-0f62-4a96-a9f1-8c6138efd9cf"
/>

### After

<img width="420" alt="After: settings drawer navigation tab uses the
Settings icon"
src="https://github.com/user-attachments/assets/3e5f4669-ed7c-4355-be92-c9cfbf160959"
/>

## Validation

- `yarn oxfmt --check
packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx
packages/twenty-front/src/modules/navigation/components/SettingsNavigationDrawer.tsx`
- `git diff --check`
- `yarn nx typecheck twenty-front`

Note: `oxlint` could not run locally because the installed dependencies
are missing the native `@oxlint/binding-darwin-*` optional package.
2026-06-26 09:14:30 +00:00
Raphaël Bosi cb49a7a053 Add v2 onboarding loading screen while creating workspace (#22152)
https://github.com/user-attachments/assets/cc7b1d10-7495-4f21-9311-4c22c0f14771

Adds the full-screen loading screen shown while a new workspace is being
created in the v2 sign-up flow (`SignInUpV2`), building on the v2
"Create your workspace" step.

How it works:
- Submitting the v2 create-workspace form marks the flow as v2
(`isOnboardingV2State`) and creates the workspace. The flag is carried
across the cross-subdomain redirect with an `onboardingV2=true` URL
param, so v2 users land on a new `/workspace-activation-v2` route
instead of v1's `/workspace-activation`.
- `WorkspaceActivationV2` runs the real `activateWorkspace` mutation on
mount and renders the loader: a pulsing Twenty logomark above a stack of
status messages that shift up one at a time, cycling once per second.
There is no faked/minimum duration; it advances to the next onboarding
step as soon as the workspace is activated.
- On activation failure it shows a "Workspace creation failed" screen
with a Retry button.

v1 onboarding is unchanged. Storybook:
`Modules/Auth/SignInUpWorkspaceActivationV2`.

Note: The flashes will be fixed in later PRs

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22152?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-26 08:46:34 +00:00
Akash! ce4d0f3447 fix: hide "Create Workspace" button when multi-workspace is disabled (#22202)
## Description

This PR fixes a bug where the "Create Workspace" button was
unconditionally rendered in the workspace switcher dropdown, even when
single-workspace mode was active (`IS_MULTIWORKSPACE_ENABLED=false`).

This created a confusing "dead-end" action for users, as clicking the
button would do nothing (because the backend correctly blocks workspace
creation in this mode, and the frontend skips the redirect).

### Changes made
- Imported the `isMultiWorkspaceEnabledState` atom from client-config.
- Evaluated `isMultiWorkspaceEnabled` inside
`MultiWorkspaceDropdownDefaultComponents`.
- Conditionally rendered the "Create Workspace" `<MenuItem>` only if
multi-workspace is enabled.

Closes #22139


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22202?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-26 10:42:10 +02:00
Etienne b625bd1995 fix(ai-chat) - improvements (#22193)
- remove flickering at assistant message streamed end
- add copy code
- leave chat history when navigating to settings

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22193?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-26 10:26:40 +02:00
github-actions[bot] e60d790990 i18n - website translations (#22203)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22203?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-26 10:25:37 +02:00
Thomas des Francs 9b57c5dfac Set record card header height and calendar spacing (#22187)
## Summary

Sets the shared `RecordCardHeaderContainer` height to `32px`, so board
and calendar card headers use the same common header size.

Updates the board fetch-more card-height estimate to use the same `32px`
header value.

Reduces the calendar card header/content gap by removing the body top
padding while keeping the existing side and bottom padding.

## Screenshots

Before:


![Before](https://raw.githubusercontent.com/twentyhq/twenty/bonapara/pr-assets-kanban-card-header-auto-height/.github/pr-screenshots/kanban-card-header-auto-height/before.jpg)

After:


![After](https://raw.githubusercontent.com/twentyhq/twenty/bonapara/pr-assets-kanban-card-header-auto-height/.github/pr-screenshots/kanban-card-header-auto-height/after.jpg)

## Validation

- Browser verification on
`http://apple.localhost:3001/objects/opportunities?viewId=0433d066-dda7-4b2c-89e5-04d4792d193c`:
visible calendar card body padding computes to `0px 4px 4px`
- Browser verification on board view: visible board card headers compute
to `32px`
- Browser console errors: none
- `git diff --check`
- `prettier --write` on changed files
- `oxlint --type-aware` on changed files in the running checkout
2026-06-26 10:19:24 +02:00
Abdullah. 85f64abb28 Frameless ProductStepper with twenty-front-faithful scenes (#22197)
## What

Reworks the website's **ProductStepper** (the scroll-driven *Data model
/ Automation / Layout* section) to be frameless and to render all three
scenes faithfully to twenty-front — real icons, labels, colors,
structure, and connectors.

## Changes

**Frameless + scaling**
- Removed the shared white card/header; the three scenes now sit
directly on the dark dot-grid stage.
- Unified every scene on one `StageFit` primitive (fixed design box →
scaled to fit), pixel-identical at full width and uniform on smaller
screens.

**Data model scene**
- Real object schema (3 Standard + 2 Custom objects, real relation
fields); replaced an invented "Investors" object with the real
**Employment History** custom object (`IconBriefcase`, Company + Person
relations) from the server seed.
- Clean spanning-tree connections; removed the Standard/Custom badge;
fixed card sizing + edge centering.

**Workflow scene**
- Real action labels + a logical flow: *Record is Created → Filter →
Search Records → AI Agent → (Update Record · Send Email · Create
Record)*. Dropped the iterator (a loop construct shown without a loop
body).
- Per-action icon colors matching twenty-front (trigger blue, flow
green, record gray, send-email red, AI agent pink) on a gray tile.
- Rebuilt the node to twenty-front's real anatomy and the connectors
(source circle → `getBezierPath` → arrow marker) verbatim.

**Layout scene**
- Real workspace sidebar: real Tabler object icons, exact labels, the
true default sidebar (6 objects + Workflows folder),
`getIconTileColorShades` tile colors; dropped invented entries.
- Record overview + Fields editor with correct field-type labels (Links,
True/False, Date and Time); legibility + spacing tuning.

**Icons** — replaced every hand-drawn approximation with real
`@tabler/icons-react` / twenty-front object icons.

**Misc** — smoother step-to-step transitions (translate + easing
tokens).

## Testing
Marketing visual; `lint` / `typecheck` / `build` green, and each scene
reviewed visually against twenty-front.




https://github.com/user-attachments/assets/997e1b95-55c0-401a-93a4-c70545577057
2026-06-26 10:17:44 +02:00
martmull b49225df4b Reorder validation execution to match migration action order (#22200)
## Summary
Reorders the validation execution sequence in the workspace entity
migration builder to match the actual execution order of migration
actions (delete → create → update). This ensures that optimistic entity
maps accurately simulate the post-migration state during validation.

## Key Changes
- **Moved creation validation before update validation** in
`WorkspaceEntityMigrationBuilderService`: Creation validation now
executes immediately after deletion validation, allowing updates to
reference entities created in the same migration without validators
needing to peek into to-be-created maps.

- **Removed `remainingFlatEntityMapsToValidate` parameter from update
validation**: Since creation validation now completes before update
validation begins, the optimistic maps already contain all created
entities. Updates can safely reference newly created entities through
the optimistic maps without needing access to remaining-to-create maps.

- **Simplified `FlatNavigationMenuItemValidatorService`**: Removed the
logic that combined remaining-to-create maps with optimistic maps, now
relying solely on the optimistic maps which contain all previously
validated creations.

- **Updated type definition**: Modified `FlatEntityUpdateValidationArgs`
type to exclude `remainingFlatEntityMapsToValidate` since it's no longer
needed.

## Implementation Details
This change enables a more intuitive validation flow where:
1. Deletions are validated first
2. Creations are validated next (in topological order for
self-referential FKs)
3. Updates are validated last (can safely reference newly created
entities)

The optimistic maps are progressively built during creation validation,
so by the time update validation runs, they faithfully represent the
post-migration state, eliminating the need for validators to access
separate remaining-to-create maps.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22200?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-26 10:17:00 +02:00
Mani bharadwaj 7e48d36c98 fix(twenty-front): apply object type translations to details panel relation labels (#22090)
## Problem

When users customize or translate object type names (e.g. "Company" →
"Unternehmen" in German), the translated/customized names do **not**
appear in the details panel. The default English names still show
instead.

This is because the frontend's relation metadata only carried
`nameSingular`/`namePlural` (internal API identifiers), not
`labelSingular`/`labelPlural` (user-facing display labels). Components
that display relation names had no choice but to use the internal
identifiers.

Fixes #19790

## Changes

### Data layer — add labels to the pipeline
- **GraphQL fragment** (`fragment.ts`): Added
`labelSingular`/`labelPlural` to `sourceObjectMetadata` and
`targetObjectMetadata` in both `relation` and `morphRelations`
- **Type** (`FieldMetadataItemRelation.ts`): Extended the `Pick` type to
include `labelSingular`/`labelPlural`
- **Field metadata type** (`FieldMetadata.ts`): Added
`relationObjectMetadataLabelSingular`/`LabelPlural` to
`FieldRelationMetadata`
- **Mapping** (`formatFieldMetadataItemAsFieldDefinition.ts`): Maps the
new label fields with `label ?? name` fallback for backwards
compatibility

### Display layer — use labels for user-facing text
- **RecordDetailRelationRecordsListItem**: Uses
`relationObjectMetadataLabelSingular` for delete confirmation dialog
title, subtitle, and button text (falls back to `nameSingular`)
- **RecordDetailRelationRecordsList**: Threads `objectLabelSingular`
prop through
- **RecordDetailRelationSection**: Passes `labelSingular ??
nameSingular` from the looked-up object metadata
- **FieldWidgetRelationCard**: Passes
`relationObjectMetadataLabelSingular` from field metadata
- **FieldWidgetJunctionRelationCard**: Passes `labelSingular ??
nameSingular` from object metadata lookup
- **FieldWidgetMorphRelationCard**: Passes label from morph relation
hook result
- **useGetMorphRelationRelatedRecordsWithObjectNameSingular**: Carries
`labelSingular` from matched morph relation

### Test data
- Updated story/mock files with
`relationObjectMetadataLabelSingular`/`LabelPlural` fields
- Updated `SettingsDataModelRelationFieldPreview` with label fields in
morph relation objects

## Design decisions

- **Backwards compatible**: All new props are optional. Every display
usage uses `label ?? name` fallback, so if `labelSingular` isn't
available yet (e.g. before GraphQL regeneration), it falls back to the
old behavior
- **Lookup vs display separation**: `nameSingular` continues to be used
for lookups, routing, and GraphQL queries (it's the identifier).
`labelSingular` is only used for user-facing display text
- **Minimal scope**: Only changes the display paths identified in the
bug report — confirmation dialogs and relation labels in the details
panel

## Test plan

1. Set workspace language to a non-English locale (e.g. German)
2. Navigate to a record with relation fields
3. Verify relation section titles and labels show translated names
4. Try to delete a related record — verify the confirmation dialog uses
the translated name
5. Switch language back to English — verify everything still works
correctly

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22090?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-26 10:10:55 +02:00
Parship Chowdhury b3e39e2198 fix: relative date picker calendar display (#21895)
Part of
https://github.com/twentyhq/twenty/issues/19739#issuecomment-4652034526
(Bug 1-3). Maybe it feels like theses bugs are not actually bugs, but we
can maybe say it as UX improvements: specially needed in case when an
user will choose any past options.

### Bug 1: calendar open on wrong month
With Is Relative (e.g. Past 1 Quarter), the calendar opened on today’s
month instead of the range start. After the fix, it now opens on the
first month of the filtered range.

**Testing:**
View filter → Date field → Is Relative → Past 1 Quarter. Calendar opens
on January (range start), not today’s month


https://github.com/user-attachments/assets/8849d00a-4d5c-4f8a-8d31-3a62535eb311


### Bug 2: Dates not highlighted
Ranges older than ~2 months (e.g. Q1 when today is June) showed no
highlighted days. Highlighting now covers the full resolved range.

**Testing:**
Same setup: past 1 Quarter on a date when Q1 is outside the old 2‑month
window. Jan 1 - Mar 31 will highlight.


https://github.com/user-attachments/assets/d21e2272-c923-4493-80ff-bdf4228842b1


### Bug 3: No month navigation
Relative mode only showed Past - 1 - Quarter controls with no way to
browse months. Now see the new arrows move through months without
changing the filter.

<img width="377" height="455" alt="Screenshot 2026-06-20 181107"
src="https://github.com/user-attachments/assets/eb51feb9-af10-489a-b166-8b8d6c642e05"
/>


> [!NOTE]
> 1. We can't do the fixes by one by one, i have to fix them within one
PR because all the fixes are inter-related, like we can't test the bug 1
fix alone without implementing bug 3.
> 2. Bug 4 will be done in a separate PR which is actually the issue
#19739. See
https://github.com/twentyhq/twenty/issues/19739#issuecomment-4652034526
for better understanding.
> 3. If you see the screen recordings, they are actually done with the
alignment fixes from #21881 . So without that changes you will see the
alignmemt issues in the calendar grid in your local.

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2026-06-26 10:03:21 +02:00
Marie 6e2df0654b [Workflows] Allow iterator to take whole item as variable (#22031)
**Select the whole item in iterator loops, and iterate over a step's
array output**
## Summary
Two related improvements to working with lists in workflows:
- Pick the current item as a whole inside an iterator loop. Previously,
in a node inside the loop, you could only reference individual fields of
the Iterator's current item. Now you can select the whole item (e.g. a
full record) — useful for passing it straight into a downstream step.
<img width="1270" height="744" alt="Screenshot 2026-06-23 at 17 02 47"
src="https://github.com/user-attachments/assets/6b92e72e-ec25-4c1a-9841-3a438210e753"
/>
- Iterate over a step's array output. A Code / Logic Function step that
returns a top-level array couldn't be fed to the Iterator: its output
was flattened into indexed entries (0, 1, …) with no way to select the
array as a whole. A new "Whole list" option selects the step's entire
output, and the Iterator infers the per-iteration item shape from it.
<img width="1026" height="728" alt="Screenshot 2026-06-23 at 17 17 53"
src="https://github.com/user-attachments/assets/db07dcd8-4fb8-4db9-8b45-aa56051d9f3b"
/>


Together these complete the loop ergonomics: select a list → iterate →
reference the current item (whole or by field) downstream — matching the
model used by tools like Windmill.

## What changed
- The variable picker offers a "Use the whole item" option when viewing
an iterator's current item, and a "Whole list" option when a step
returns a top-level array.
- The Iterator's current-item schema can now be inferred from a variable
pointing at a step's whole output.

## Risks for existing workflows
None expected. The change is purely additive:
- No DB migration and no change to how output schemas are stored or read
— existing schemas, variables, and iterators behave identically.
- No change to runtime variable resolution; existing {{step.field}} and
current-item references are untouched.
- The new options only apply to new selections (whole item / whole
list); all existing paths take the unchanged code path.
- The only edge case: array detection is heuristic (an output whose keys
are exactly 0…n-1), so an object that happens to have those keys would
also show "Whole list". This is rare for real outputs, affects nothing
unless a user selects it, and fails safe — the Iterator validates its
input and throws a clear "items must be an array" error if a non-array
is passed.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22031?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-26 09:26:11 +02:00
Félix Malfait ea9e11581c feat(billing): replace Stripe trial emails with fair, well-timed reminders (#22186)
## Why

We currently rely on Stripe's automated trial-ending email. It misfires:
the global "remind 7 days before trial ends" setting lands the reminder
on **signup day** for the 7‑day no‑card trial, and the "your card will
be charged" copy makes no sense for a trial with no card. This replaces
it with our own honest, well‑timed, Twenty‑branded emails.

## 🔒 Safety — these emails are OFF by default

Because these reach real customers, the whole feature is gated behind a
kill‑switch that **defaults to `false`**:

- **`BILLING_REMINDER_EMAILS_ENABLED` (default `false`)** — checked
**both** at cron registration **and** on every job run (defense in
depth), so the emails can never be sent inadvertently (not on deploy,
not in staging, not via a stray trigger). They only go out once an
operator explicitly opts in.
- Also gated on `IS_BILLING_ENABLED` (cloud‑only; self‑hosters
unaffected).
- In non‑prod the email driver is typically `logger`, so even if enabled
there, nothing is actually sent.

A unit test asserts that with the switch off, **zero** emails are
produced.

## What it does

A daily cron (`0 8 * * *`) sends three honest, Twenty‑branded emails:

| Plan | Email | When |
|---|---|---|
| No‑card trial (7d) | "Add a card to keep your data" | **1 day before**
trial ends |
| Card‑on‑file trial (30d) | Upcoming‑charge heads‑up (cancel in one
click) | **7 days before** first charge |
| Yearly subscription | Renewal reminder (no surprise) | **7 days
before** each renewal |

- **Monthly renewals get no reminder** (avoids noise) — only the first
charge and annual renewals do.
- Branches no‑card vs with‑card on the customer's payment‑method flag
(with a trial‑duration fallback), so someone who adds a card mid‑trial
correctly gets the charge heads‑up instead of the add‑a‑card one.
- **Idempotent** per `(workspace, boundary date)` via workspace‑level
user vars — yearly reminders re‑fire each period, but the daily cron
never double‑sends.
- Offsets are configurable via new `BILLING_*_REMINDER_DAYS_BEFORE`
variables.

Also **warms up the tone** of the existing suspended / deleted workspace
emails (less robotic, fair, loss‑aversion framing) — these already act
as the "come back or lose your data" win‑back, so no extra win‑back
email was added.

## Rollout

1. Merge.
2. Disable Stripe's automated trial/renewal customer emails in the
Stripe dashboard.
3. Review copy/timing, then set `BILLING_REMINDER_EMAILS_ENABLED=true`
to turn the cron on.

## Notes for reviewers

- **i18n:** new English strings render via Lingui's msgid fallback;
translation catalogs are intentionally **not** included to keep the diff
focused (the repo extracts translations via its standard periodic
`lingui extract` sync — `main` already carries catalog drift). Diff is
18 code files.
- **Recipients:** reminders go to all workspace members, consistent with
the existing suspension emails. Happy to scope the charge‑related ones
to billing admins if preferred.
- **Follow‑ups discussed:** in‑app trial banner, loss‑aversion with real
record counts, and failed‑payment dunning are the higher‑leverage
conversion levers beyond this.

## Test plan

- [x] `typecheck` (twenty-server, twenty-emails)
- [x] oxlint type‑aware + oxfmt
- [x] Unit tests: no‑card path, with‑card path, idempotency, yearly
renewal, billing‑disabled, **kill‑switch off → no send** (6/6 green)
- [ ] Manual: set the flag on a staging instance with `logger` driver
and confirm the right email is logged at each boundary

https://claude.ai/code/session_0147ujzHv1X4vzimf4iGbnT4

---
_Generated by [Claude
Code](https://claude.ai/code/session_0147ujzHv1X4vzimf4iGbnT4)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22186?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-26 08:18:34 +02:00
Parship Chowdhury c635a191bf fix: uneven spacing in date picker calendar grid (#21881)
### Summary
While working on #19739, I found that in the date filter calendar
dropdown, day cells and highlighted dates looked misaligned i.e. tighter
on the right side. The solution is to apply a uniform margin in
`DatePicker.tsx` and `DateTimePicker.tsx`.

### Before:
<img width="377" height="436" alt="Screenshot 2026-06-20 021856"
src="https://github.com/user-attachments/assets/4ef62a48-6b99-4647-95f6-bd39f43eaa26"
/>
<img width="442" height="518" alt="Screenshot 2026-06-20 021932"
src="https://github.com/user-attachments/assets/e6e1e8f8-257a-41e5-825f-bd2fe91e372a"
/>


### After:
<img width="346" height="380" alt="Screenshot 2026-06-20 021813"
src="https://github.com/user-attachments/assets/5fdd03fe-e61b-4fec-a0f3-f6ac4ee9effb"
/>
<img width="322" height="457" alt="Screenshot 2026-06-20 022013"
src="https://github.com/user-attachments/assets/a33c6439-f2d5-43e7-a043-f9cf4fec770a"
/>

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

---------

Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-06-26 07:51:37 +02:00
martmull 05df528fd5 Add logging on sync catalog job (#22192) 2026-06-25 19:03:48 +00:00
Thomas des Francs eedd838189 Fix threaded draft email replies (#22175)
## Summary

Fixes Gmail and Microsoft draft replies so workflow-created drafts stay
attached to the existing provider thread.

Fixes twentyhq/core-team-issues#2597.

## Root cause

The email composer already resolved `threadExternalId` and `references`
from `inReplyTo`, but `DraftEmailTool` only forwarded `inReplyTo` to the
outbound draft service. Gmail therefore created a raw draft without
`message.threadId`, which lets the draft appear as a standalone compose
instead of an inline thread reply.

For Microsoft, the draft path used Graph `createReply`, but parent
lookup filtered on a URL-encoded `internetMessageId`. That can miss the
parent message and fall back to creating a new draft message instead of
a reply draft.

## Changes

- Forward `threadExternalId` and `references` from `DraftEmailTool` to
outbound draft creation.
- Set Gmail draft `message.threadId` when `threadExternalId` is
available.
- Make Microsoft parent lookup use Graph request query builders with
OData string escaping, so `createReply` is reached reliably.
- Add targeted Jest coverage for the Draft Email tool, Gmail draft
threading, and Microsoft reply-draft creation.

## Validation

- `NX_DAEMON=false
/Users/thomascolasdesfrancs/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node
../../node_modules/nx/dist/bin/nx.js jest twenty-server --
--runTestsByPath
src/engine/core-modules/tool/tools/email-tool/__tests__/draft-email-tool.spec.ts
src/modules/messaging/message-outbound-manager/drivers/gmail/services/__tests__/gmail-message-outbound.service.spec.ts
src/modules/messaging/message-outbound-manager/drivers/microsoft/services/__tests__/microsoft-message-outbound.service.spec.ts
--runInBand`
- `NX_DAEMON=false
/Users/thomascolasdesfrancs/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node
./node_modules/nx/dist/bin/nx.js lint:diff-with-main twenty-server`

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22175?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[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Félix Malfait <FelixMalfait@users.noreply.github.com>
2026-06-25 18:39:01 +02:00
Parship Chowdhury 1076866820 fix(server): preserve anyFieldFilterValue in view manifest sync (#22004)
### Summary
- Fixes #19978 
- `shouldHideEmptyGroups` was already wired up in the type and
converter; this PR only closes the remaining gap for
`anyFieldFilterValue`.

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

---------

Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-06-25 18:11:35 +02:00
alibildir 35d64ac7f2 fix(server): wrap file storage upsert and read in transaction (#21924)
## Summary
This PR fixes a race condition occurring during file and image uploads
in environments that use Pgpool-II or similar connection poolers with
read-replica scaling.

Currently, in `FileStorageService.writeFile`, `fileRepository.upsert`
writes the file record to the primary database node. However, the
immediate subsequent call to `fileRepository.findOneOrFail` is executed
outside of a transaction. Consequently, connection poolers like Pgpool
can route this `SELECT` query to a read-replica. Due to replication lag,
the replica may not yet reflect the newly inserted record, throwing an
`EntityNotFoundError` and failing the upload process (even though the
file is successfully saved in S3 and the database).

This PR wraps both operations in a TypeORM database transaction when
`queryRunner` is not provided. This ensures that the `SELECT` query
correctly targets the primary node, guaranteeing immediate
read-after-write consistency.

## Affected version
- Twenty Self-hosted (e.g. `v2.14.x`) configured with
Pgpool/read-replicas.

## Changes Made
- **`file-storage.service.ts`**: Wrapped `transactionalFileRepo.upsert`
and `transactionalFileRepo.findOneOrFail` within
`this.applicationRepository.manager.transaction` to ensure
read-after-write consistency.

## How to Test
1. Set up Twenty in a self-hosted environment using Pgpool configured
with load balancing / read-replicas.
2. Attempt to upload a file to a `Files` custom field or upload an image
as an organization logo.
3. Observe that the upload completes successfully without throwing an
`EntityNotFoundError` in the server logs.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21924?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: Ali Bildir <[alibildir@gmail.com]>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-06-25 18:10:07 +02:00
github-actions[bot] ce9ca1184c i18n - docs translations (#22189)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22189?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-25 17:25:58 +02:00
martmull 1cf1c33899 Add command menu items to people data labs (#22180)
- Adds command menu items to enrich people and companies
- Improve readme

## After

<img width="1039" height="584" alt="image"
src="https://github.com/user-attachments/assets/9c6c3967-e905-4bf2-a3ef-307ae5bdca92"
/>


<img width="2560" height="332" alt="image"
src="https://github.com/user-attachments/assets/4ab4ffb8-fe52-4612-99c7-282d0ffa2c94"
/>
<img width="2560" height="478" alt="image"
src="https://github.com/user-attachments/assets/d496e21b-de18-4c52-acc7-6106afca0bb7"
/>
<img width="2560" height="452" alt="image"
src="https://github.com/user-attachments/assets/caea4de3-5bee-4de7-a347-c5d8540fd179"
/>
<img width="2560" height="335" alt="image"
src="https://github.com/user-attachments/assets/92bd17e3-f4b6-4fcb-84ae-d8ebc7ccce1e"
/>


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22180?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-25 15:12:30 +00:00