Commit Graph

13502 Commits

Author SHA1 Message Date
martmull 0970f85cd2 docs(apps): align project structure and testing pages with the actual scaffold (#22690)
Part 3 of the app-docs audit series (after #22688 and #22689). Verified
by scaffolding a fresh app with `create-twenty-app` and diffing the docs
against the generated files and the template in
`packages/create-twenty-app/src/constants/template`.

## What this fixes

**getting-started/project-structure.mdx**
- The documented tree was missing most of what the scaffolder actually
generates: the starter welcome page (`front-components/`,
`navigation-menu-items/`, `page-layouts/`), the real test files
(`global-setup.ts`, `application-config.test.ts`,
`schema.integration-test.ts` — not `setup-test.ts` /
`app-install.integration-test.ts`), `cd.yml`, `vitest.unit.config.ts`,
and `AGENTS.md`/`CLAUDE.md` (the docs said `LLMS.md`, which isn't
generated).
- Dependency snippet showed `^2.13.0`; the scaffolder pins its own
version (currently 2.20.0) and also adds `twenty-ui`.
- `twenty build` → `twenty dev:build`.

**operations/testing.mdx**
- The Vitest setup section described a config that diverges from the
scaffold (uses `setupFiles` instead of `globalSetup`, writes the SDK
config to `os.tmpdir()/.twenty-sdk-test/config.json` — a path the CLI
never reads). Replaced with the actual pattern: `globalSetup` +
`~/.twenty/config.test.json` (what the CLI reads under `NODE_ENV=test`)
+ `appDevOnce` sync and uninstall-teardown.
- The CI section described a `spawn-twenty-docker-image` action and a
4-step workflow; the scaffolded `ci.yml` uses
`spawn-twenty-app-dev-test` and also runs lint, typecheck, and unit
tests. This section previously contradicted `operations/publishing.mdx`
— it now gives a short accurate summary and links to Publishing for the
full walkthrough of both workflows (de-duplicating the two pages).
- Added `appDevOnce` to the programmatic API table (used by the
scaffolded global setup).

**getting-started/troubleshooting.mdx**
- Node requirement made precise (`^24.5.0`), `twenty build` → `twenty
dev:build`.

Only English sources were touched; `l/<locale>` copies come from
Crowdin.

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

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22690?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: Martin <martin@twenty.com>
2026-07-09 10:28:34 +02:00
martmull 2781a06025 docs(apps): fix nonexistent SDK import paths and unsupported config in layout pages (#22689)
Part 2 of the app-docs audit series (after #22688). Every fix below was
verified against the `twenty-sdk` source and its `exports` map.

## What this fixes

**Broken import paths (copy-paste would not compile)**
- `twenty-sdk/command` and `twenty-sdk/clients` are not export subpaths
of `twenty-sdk` — 9 code samples across `front-components.mdx` and
`command-menu-items.mdx` used them. `Command`, `CommandModal`,
`CommandLink`, `CommandOpenSidePanelPage` actually live in
`twenty-sdk/front-component`, and `CoreApiClient` in
`twenty-client-sdk/core` (matching every example app in
`packages/twenty-apps`).

**Unsupported config**
- The bulk-export example passed an inline `command: {...}` to
`defineFrontComponent`, but `FrontComponentConfig` has no such property.
Replaced with a separate `defineCommandMenuItem` file, which is the
supported pairing.

**Deprecated API in examples**
- Three examples used `useRecordId()` even though the hooks table on the
same page marks it deprecated. Switched them to
`useSelectedRecordIds()`.
- `defineCommandMenuItem`'s `icon` is deprecated (the build warns "icon
will be ignored in favor of application icon") but the docs listed it as
a normal field and used it in examples. Marked it deprecated in the
table and removed it from examples.

**Missing enum value**
- `availabilityType` supports `'GLOBAL_OBJECT_CONTEXT'`
(`CommandMenuItemManifest` in `twenty-shared`), which the config table
omitted.

**Deduplication**
- The full run-action example (component + command, ~40 lines) appeared
verbatim on both layout pages. `command-menu-items.mdx` now keeps only
the command snippet and links to the component example on the Front
Components page.

Only English sources were touched; `l/<locale>` copies come from
Crowdin.

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

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22689?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: Martin <martin@twenty.com>
2026-07-09 10:26:12 +02:00
martmull 5c1e7dd559 docs(apps): fix stale CLI commands in getting-started and operations pages (#22688)
Part 1 of a series of small PRs from a full audit of the app-development
docs (every claim was cross-checked against `twenty-sdk`,
`create-twenty-app`, and a scaffolded app).

## What this fixes

**quick-start.mdx**
- `yarn twenty server` does not exist — replaced with `yarn twenty
docker:start` (the command every other page uses, and what the CLI
actually ships).
- The scaffolder is non-interactive since create-twenty-app 2.x: there
is no "name and description" prompt and no "Would you like to set up a
local Twenty instance?" prompt (that screenshot was removed). It
auto-starts the local Docker server and authenticates with the
pre-seeded dev key; OAuth (browser sign-in + Authorize) only happens for
remote `--url` targets or `--authentication-method oauth`.
- `--debounceMs` default is `1000`, not `2000` (see `twenty dev
--help`).
- The one-shot section now teaches `twenty plan` / `twenty apply`; `dev
--once` / `--dry-run` are marked as the deprecated aliases they are in
the CLI help.
- Node prerequisite tightened to 24.5+ to match `engines.node: ^24.5.0`.

**operations/sync-and-recovery.mdx**
- Command matrix, previewing section, and recovery ladder switched from
the deprecated `dev --once [--dry-run]` to `plan` / `apply` (heading
anchor updated accordingly).

**operations/cli.mdx**
- Added a complete command overview table (the page previously omitted
`plan`, `apply`, `dev:translations-extract`, `dev:catalog-sync`, and the
whole `docker:*` group without pointing anywhere).
- Added `remote:status` and `remote:remove`, and the `--preInstall` exec
flag.

**tutorials/document-generator/publishing.mdx**
- Pre-publish check now uses `yarn twenty plan`.

Only English sources were touched; `l/<locale>` copies come from
Crowdin.

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

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22688?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: Martin <martin@twenty.com>
Co-authored-by: Weiko <corentin@twenty.com>
2026-07-09 10:25:09 +02:00
Marie cdabef6429 fix(sdk): report the connected server's real version in dev version check (#22670)
## Summary

The `yarn twenty app dev` version row was reading the "local server"
version from the `twenty-app-dev` Docker container's baked-in
`APP_VERSION` env var. When the CLI is actually pointed at a separately
running instance (or a stale `twenty-app-dev` container is lying
around), this reported a version unrelated to the server serving
requests — e.g. showing `Server v2.5.3` and a bogus "days behind"
warning while the real instance was on `2.16.1`.

This changes the version resolution to ask the server the CLI is
connected to for its real version over HTTP, falling back to Docker
inspection only when the server can't be reached.

- Add `getServerVersionFromApi`, which reads the version from the
public, no-auth `/.well-known/mcp/server-card.json` endpoint (its
`version` is the server's `APP_VERSION`). Returns `null` gracefully on
timeout, non-OK responses, or missing/`0.0.0`/non-semver values.
- `getVersionInfo` now uses `getServerVersionFromApi() ??
getLocalServerVersion(containerName)`, running the API call in parallel
with the Docker Hub published-versions fetch. All downstream logic
(`isMinorOrMajorBehind`, `daysBehind`, the dev UI row, and the headless
warning) now operates on the real version.
2026-07-09 10:24:35 +02:00
Etienne a51c37dae5 feat(ai) - add light AI chat turn instrumentation (metrics + Sentry correlation) (#22692)
## Summary

Adds minimal server-side observability for AI chat turns: lifecycle
counters to measure success/failure rates, Sentry scope tags to
correlate API and worker traces, and per-LLM-call telemetry metadata for
turn/stream correlation.

- Add turn lifecycle metrics: `ai-chat/turn-started`,
`ai-chat/turn-completed`, `ai-chat/turn-failed` (with `failure_phase`
and `error_code` attributes)
- Emit counters at key points: job start, clean completion, execution
failures, enqueue failures, interrupted streams, and empty completions
- Tag Sentry scope with `streamId`, `turnId`, `threadId`, and
`workspaceId` at API entry points (`sendChatMessage`,
`retryChatMessage`, `answerAgentChatQuestion`) and worker entry
(`StreamAgentChatJob`)
- Enrich LLM `experimental_telemetry` metadata with
stream/turn/thread/workspace IDs
- Return `turnId` from streaming service methods so resolvers can tag
the scope
- Remove granular tool-learned/skill-loaded metrics in favor of the
turn-level counters

## Test plan

- [ ] Send a chat message and verify `ai-chat/turn-started` and
`ai-chat/turn-completed` increment
- [ ] Trigger a stream failure (e.g. interrupted/dead stream) and verify
`ai-chat/turn-failed` with correct `failure_phase`
- [ ] Retry a failed turn and confirm a new `turn-started` is emitted
for the retry attempt
- [ ] Answer an `ask_questions` prompt and confirm Sentry tags include
`streamId` and `turnId`
- [ ] Check Sentry spans for LLM calls include `streamId`, `turnId`,
`threadId`, `workspaceId` in telemetry metadata
- [ ] Run unit tests:
- `npx jest
packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/jobs/__tests__/stream-agent-chat.job.spec.ts`
- `npx jest
packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/__tests__/agent-chat-streaming.service.claim.spec.ts`
- `npx jest
packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/__tests__/agent-chat-streaming.service.retry.spec.ts`

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22692?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-07-09 08:24:27 +00:00
Etienne f2d0a1b90a fix(billing): only reactivate suspended workspaces when subscription is in good standing (#22687)
## Context

When a subscription's trial ends without a valid payment method, Stripe
emits
`customer.subscription.updated` (`active → past_due`) within seconds of
`trial_end`.
Our webhook handler correctly suspends the workspace for this event,
because it lands
inside the 24h "trial just ended" window checked by
`shouldSuspendWorkspace`.

However, the workspace does not *stay* suspended if an other
subscription update event arrived.

## Problem

Reactivation was gated only on the negation of the suspend heuristic:

```ts
} else if (workspace.activationStatus === WorkspaceActivationStatus.SUSPENDED) {
  await this.workspaceService.reactivateWorkspace(workspaceId);
}

<!-- This is an auto-generated description by cubic. -->
<a href="https://cubic.dev/pr/twentyhq/twenty/pull/22687?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-07-09 09:38:30 +02:00
github-actions[bot] 9da5383289 i18n - website translations (#22683)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22683?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-07-09 09:12:46 +02:00
Félix Malfait 0755855768 fix(billing) - skip paying upgrade invoices already settled at finalization (#22705)
## Problem

Upgrading resource credits fails with `Invoice is already paid` whenever
the one-off upgrade invoice resolves to a $0 amount due. That is the
case for internal workspaces on the `Twenty Internal - 100% FREE`
coupon, and for customers whose credit balance covers the price
difference.

Reproduced on an internal workspace (5 to 20 credits upgrade):

1. `createImmediateUpgradeInvoice` creates the invoice for the $20 diff
and finalizes it with `auto_advance: true`
2. The 100% coupon brings the amount due to $0, and Stripe settles
zero-due invoices at finalization ("Invoice was finalised and
automatically marked as paid because the amount due was US$0.00")
3. The explicit `stripe.invoices.pay()` that follows is rejected with a
400 `invalid_request_error`: "Invoice is already paid"
4. The error propagates, so the mutation aborts before
`runSubscriptionUpdate`: the Stripe subscription item stays on the old 5
credits price while the upgrade invoice already exists
5. Every retry creates a new invoice item + invoice and fails the same
way, leaving stray $0 invoices on the customer

## History

Third pass on this code path:

- #21097 treated it as a race with `auto_advance` and switched
finalization to `auto_advance: false`. Zero-due invoices are settled at
finalization regardless of that flag, so the failure remained.
- #21450 restored `auto_advance: true` and swallowed the error when
`error.code === 'invoice_already_paid'`. Stripe does not send that code
for this failure (it is not in its documented error codes; the response
only carries the message), so the guard never matched and the error was
always rethrown.

## Fix

Rely on invoice status instead of error codes:

- `finalizeInvoice` returns the finalized invoice; when it comes back
`paid` (the zero-due case), skip `pay` entirely
- if `pay` still fails (the genuine auto_advance race from #21097),
re-retrieve the invoice and only rethrow when it is actually unpaid

## Tests

Unit tests for `createImmediateUpgradeInvoice`:

- open invoice after finalization gets paid
- invoice settled at finalization skips `pay`
- `pay` failure with a meanwhile-paid invoice is swallowed
- `pay` failure with an unpaid invoice is rethrown

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

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22705?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-07-09 09:09:58 +02:00
github-actions[bot] 1129cc7ae0 chore: sync AI model catalog from models.dev (#22710)
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/22710?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-07-09 09:08:22 +02:00
Etienne cc7b41db0e feat(ai-chat): inject current date & per-message timestamps into agent context (#22632)
## Summary

Gives the AI chat agent temporal awareness by injecting the current date
into
the system prompt and a per-message "sent at" timestamp into each user
message,
formatted in the member's timezone. Also hardens all timezone formatting
against
the `"system"` sentinel value, which was crashing the stream job.

## What changed

**Message timestamps (new)**
- Added `injectMessageTimestamps` util: prepends a
`<message_timestamp>Sent: …</message_timestamp>`
text part to each user message before it's sent to the model, so the
agent can
  reason about "yesterday", "last week", etc.
- `loadMessagesFromDB` now stores the message time in the canonical
`metadata.createdAt` slot (ISO string, JSON-serializable for the BullMQ
job
payload) instead of a non-typed top-level `createdAt` field that nothing
read.
- Migrated the AI chat message pipeline from the generic `UIMessage` to
the
typed `ExtendedUIMessage` (`chat-execution.service`,
`extract-code-interpreter-files`,
`replace-unsupported-file-parts`, and related types), since
`metadata.createdAt`
  is declared on `ExtendedUIMessage`.

**Current date in context**
- System prompt now includes `Current date: …` formatted in the member's
timezone
  (`system-prompt-builder.service`).
- Settings › AI prompt preview mirrors the same `Current date` line.

**Timezone safety (bug fix)**
- Workspace members default `timeZone` to the `"system"` sentinel, which
is only
  resolvable client-side. Passing it (or any invalid IANA zone) to
`Intl.DateTimeFormat` throws `RangeError: Invalid time zone specified:
system`,
  which was failing the stream job.
- Added `getValidTimeZoneOrUndefined`, which returns a valid IANA zone
or
`undefined` (letting the runtime fall back to its default). Used in both
`injectMessageTimestamps` and `formatCurrentDate`. This mirrors the
existing
  `isValidTimeZone` convention in the calendar module.

## Notes / follow-ups

- For members who never changed `timeZone` from `"system"`, timestamps
fall back
to the server's default zone (UTC). To honor their real local time, the
frontend would need to send the browser-detected zone with the chat
request
(the same way calendar/charts already pass a resolved zone). Not
included here.

## Test plan

- [x] `inject-message-timestamps.util.spec.ts` — covers timestamp
injection,
assistant messages untouched, invalid `createdAt`, and the `"system"`
      timezone no longer throwing.
- [ ] Send a chat message and confirm the agent sees the correct
date/time.
- [ ] Verify a member with `timeZone = "system"` no longer crashes the
stream job.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22632?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-07-09 07:02:56 +00:00
neo773 3a5545c753 chore: remove IS_MESSAGING_CALENDAR_WEBHOOK_ENABLED flag (#22680)
Messaging/calendar webhook subscriptions are now always on; drop the
feature flag gate and its enum/public-flag registration.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22680?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-07-08 23:35:35 +02:00
Weiko 97e21c3403 feat(ai): optionally preselect fast or smart model when opening Ask AI (#22679)
Allow front components to preselect the AI model when opening the Ask AI
side panel with a preprompt.

- Add an optional `model: 'FAST' | 'SMART'` to the preprompt in the
  openSidePanelPage AskAI params of the front-component SDK.
- openAskAiPageWithPreprompt resolves FAST to the workspace fast model
  and SMART to the workspace default (null selection = pinned default
  model), writing to agentChatUserSelectedModelState.
2026-07-08 22:52:38 +02:00
nitin 78a0f9ea77 feat(call-recorder): type application and server variables, make summary prompt rich text (#22685) 2026-07-08 22:39:47 +02:00
nitin 545de99476 [Twenty Fireflies] Add calendar event summary and transcript UI components (#22667)
## Summary
Add comprehensive UI components and hooks for displaying call recording
summaries and transcripts on calendar event pages. This includes
markdown parsing for summaries, diarized transcript rendering, and data
fetching hooks integrated with the Twenty SDK.

## Key Changes

### New Hooks
- `useCalendarEventSummary`: Fetches and manages summary markdown for a
calendar event's call recordings
- `useCalendarEventTranscript`: Fetches and manages transcript data for
a calendar event's call recordings

### Summary Components
- `CalendarEventSummary`: Top-level component that displays summary for
selected calendar event
- `CalendarEventSummaryContent`: Container with header and content frame
- `CalendarEventSummaryBody`: Handles loading, error, and empty states
- `SummaryMarkdown`: Renders parsed markdown with support for headings,
lists, and paragraphs
- `SummaryInlineSegments`: Renders inline text with bold formatting
support

### Transcript Components
- `CalendarEventTranscript`: Top-level component that displays
transcript for selected calendar event
- `CalendarEventTranscriptContent`: Container with header and scrollable
content frame
- `CalendarEventTranscriptBody`: Handles loading, error, and empty
states
- `TranscriptEntryList`: Renders list of transcript entries
- `TranscriptEntryListItem`: Individual transcript entry with speaker
avatar, timestamp, and text
- `TranscriptErrorBox`: Styled error state display

### Utilities
- `parseSummaryMarkdownBlocks`: Parses markdown into structured blocks
(headings, lists, paragraphs)
- `parseSummaryInlineSegments`: Parses inline markdown for bold text
formatting
- `parseTranscriptEntries`: Parses diarized transcript format into
structured entries with speaker info and timestamps
- `formatSecondsAsClockTimestamp`: Formats seconds into HH:MM:SS or
MM:SS format
- `asRecord`: Type guard utility for converting values to records

### Page Layout Configuration
- `calendar-event-summary-tab.ts`: Defines Summary tab for calendar
event page layout
- `calendar-event-transcript-tab.ts`: Defines Transcript tab for
calendar event page layout
- Front component definitions for both summary and transcript

### Types
- `TranscriptEntry` and `TranscriptWord`: Structured transcript data
types
- `SummaryMarkdownBlock`: Block-level markdown structure
- `SummaryInlineSegment`: Inline text segment with formatting

## Implementation Details

- Uses `CoreApiClient` from Twenty SDK to query call recordings filtered
by calendar event ID
- Implements proper cleanup with cancellation tokens to prevent state
updates on unmounted components
- Supports both loading and error states with user-friendly messaging
- Markdown parser handles headings (h1-h6), bullet lists, and paragraphs
with inline bold formatting
- Transcript parser validates diarized format and gracefully handles
malformed entries
- Styled with emotion and theme constants from twenty-ui for consistent
design
- Integrates with `useSelectedRecordIds` hook to track selected calendar
event



https://github.com/user-attachments/assets/0bd63590-bb1e-4f89-8b96-e3fbb659473e


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22667?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-07-09 00:52:20 +05:30
martmull 16e5d2b9d0 feat(website): add public apps marketplace with one-click install (#22611)
<img width="1335" height="570" alt="image"
src="https://github.com/user-attachments/assets/85bbe656-7525-4800-9ae6-bc59445ccf48"
/>
<img width="1512" height="723" alt="image"
src="https://github.com/user-attachments/assets/6494c539-a3e0-4e95-9df5-ee7559c098ab"
/>

<img width="1512" height="813" alt="image"
src="https://github.com/user-attachments/assets/4cb6adea-d35b-470a-9bfd-d47b2a514e8c"
/>
<img width="1512" height="739" alt="image"
src="https://github.com/user-attachments/assets/757ea3e0-bff9-4482-9a71-3e75949fd7e7"
/>
<img width="1468" height="790" alt="image"
src="https://github.com/user-attachments/assets/37365c56-47ae-4f08-a40f-32e5600a6c0f"
/>


## What

Adds a public `/apps` marketplace on twenty-website listing the vetted,
Twenty-built apps. Each app card and detail page has a one-click Install
button that deep-links to the in-app available-application page
(`app.twenty.com/settings/applications/available/:universalIdentifier`),
where authentication and permission consent are handled before
installing.

- New apps directory (`/apps`) and per-app detail pages
(`/apps/[slug]`), with a category filter
- Routing/sitemap entry for `/apps` and an Apps link in the Resources
menu
- The catalog is fetched from the Twenty GraphQL API (public
`publicMarketplaceApps` / `publicMarketplaceAppDetail` queries),
defaulting to `api.twenty.com`, and degrades to an empty state on
failure. No app info is duplicated in the website; logos and screenshots
come from the catalog's CDN URLs.

## Dependency

This is the website half of the split. It consumes the public queries
added in the server PR #22647, which should merge first.

---------

Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com>
Co-authored-by: martmull <martin@twenty.com>
Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Co-authored-by: nitin <142569587+ehconitin@users.noreply.github.com>
Co-authored-by: Aressand <97886962+Aressand@users.noreply.github.com>
Co-authored-by: Brahm Lower <bplower@gmail.com>
Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Co-authored-by: Parship Chowdhury <parshipchowdhury@gmail.com>
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Pratik Mahajan <Pratik@mahajan.xyz>
Co-authored-by: neo773 <neo773@protonmail.com>
Co-authored-by: Deepak kumar maharana <100968930+deep231w@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
Co-authored-by: Marie <51697796+ijreilly@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: neo773 <62795688+neo773@users.noreply.github.com>
2026-07-08 19:12:59 +00:00
Marie 5f3f734b34 fix(front): page header title overlap, Cmd+K on page layout pages & stable tooltip id (#22678)
## Summary

Three independent front-end fixes.

### 1. Settings page header title overlaps the breadcrumb

On settings detail pages (e.g. an app's logic function), a long centered
title visually overlapped the breadcrumb.

`PageCardHeader` renders the header as a CSS grid (`minmax(0, 1fr) auto
minmax(0, 1fr)`) and the centered title used `justify-self: center`.
With grid, `justify-self: center` sizes the item to its own content
width (up to its `max-width`) instead of to its grid track, so a long
title grew wider than the center track and spilled sideways over the
breadcrumb column — its `overflow: hidden` only clipped its own children
to that oversized box, not to the track.

Fix: let the centered title fill and shrink to its grid track so it
clips (with ellipsis) inside its own column instead of overflowing into
the breadcrumb.
- Center column: `auto` → `minmax(0, auto)` so it can shrink when space
is tight.
- Centered title: `justify-self: center` → `justify-self: stretch` +
`min-width: 0`.

### 2. Cmd+K does nothing on standalone page layout pages

On standalone page layout pages (`/page/:pageLayoutId`, used to render
app front components), the command menu shortcut (Cmd+K) did nothing.

Cmd+K is a global hotkey with a modifier, so it only runs when the
active focus-stack config has `enableGlobalHotkeysWithModifiers: true`.
`RecordIndexPage` and `RecordShowPage` explicitly reset the focus stack
to enable it, but `PageChangeEffect` had no case for
`AppPath.PageLayoutPage`, so the stack kept a stale config (commonly the
Settings config, which disables modifier hotkeys) and swallowed the
shortcut.

Fix: add a `PageLayoutPage` case in `PageChangeEffect` that resets the
focus stack with modifier hotkeys enabled (mirroring `RecordShowPage`),
plus a new `PageFocusId.PageLayoutPage` value.

### 3. Ever-changing / unstable tooltip element id

`OverflowingTextWithTooltip` built its element id from `title-id-${+new
Date()}`, so a new id (the current epoch time in ms) was generated on
every render — the id visibly kept increasing in the DOM. This is
unstable (the tooltip anchor `#id` churns on each render) and
collision-prone (two tooltips rendering in the same millisecond get the
same id, producing duplicate DOM ids and an ambiguous anchor).

Fix: derive the id from React's `useId()` so it is stable per instance
and unique. The colons `useId()` produces are stripped, since the id is
used inside a CSS selector (`anchorSelect={#${id}}`) where colons are
invalid.

## Test plan

- [ ] Open a settings detail page with a long title (e.g. an app logic
function named `maintain-account-team-member-name-on-created`) and
confirm the title no longer overlaps the breadcrumb, and truncates with
an ellipsis when space is tight.
- [ ] Navigate to a standalone page layout page (an app's
front-component page) and confirm Cmd+K opens the command menu,
including after coming from Settings.
- [ ] Inspect an overflowing title/tooltip in DevTools and confirm its
`id` stays stable across re-renders (no longer increments), and tooltips
still show on hover of truncated text.
2026-07-08 18:51:03 +02:00
Thomas Trompette ca90a9358f feat(workflow): backfill workspace workflowVersion into core (phase A) (#22663)
## workflowVersion -> core, Phase A

Follows #21674 (Phase 0, merged). Base: `main`.

Populates core `workflowVersion` and keeps it in sync with the workspace
object, so a later phase can switch reads to core. Reads stay on the
workspace object in this PR.

### 1. Backfill (upgrade command)
`BackfillWorkflowVersionToCoreCommand`, a
`@RegisteredWorkspaceCommand('2.20.0', ...)`. Per workspace, reads all
workspace `workflowVersion` records and upserts them into core,
preserving ids (idempotent), dry-run aware.

### 2. Dual-write (always on, not flag-gated)
`WorkflowVersionCoreDualWriteListener` hooks
`@OnDatabaseBatchEvent('workflowVersion', CREATED/UPDATED/DELETED)`
(same mechanism as the existing workflow-version status listener) and
mirrors every mutation into core. Sync failures are logged, never break
the user's write; drift is repaired by re-running the backfill command.

Dual-write is deliberately not behind a flag: reading from core (next
phase) is only safe if core has been continuously in sync since the
backfill. An always-on mirror makes "core is fresh" an invariant, so the
read switch becomes a plain flag flip. The cost is one extra upsert on
infrequent workflowVersion writes.

`IS_WORKFLOW_VERSION_IN_CORE_ENABLED` is reserved for the read switch
(Phase B): dispatch from the `workflowAutomatedTriggerMaps` cache,
runner and builder reading trigger/steps from core. Until then it gates
nothing.

Both the backfill and the listener go through a single
`WorkflowVersionCoreSyncService` (`upsertToCore`/`deleteFromCore`): the
workspace-to-core mapping (`trigger` -> `triggers[]`, plus `steps`,
`status`, `workflowId`) and `workflowAutomatedTriggerMaps` invalidation
live in one place.

### Rollout plan (following phases)
- **B, read switch (flag per workspace):** reads move to core; writes
keep flowing workspace -> listener -> core. Rollback = flip the flag
back, workspace never stopped being source of truth.
- **C, contract (code change):** write paths write trigger/steps to core
directly; workspace `workflowVersion` stays as a thin shell
(nav/relations/search) but drops the trigger/steps columns; listener and
flag removed.

### Not in this PR
- Reconciliation tooling beyond re-running the backfill.
- The read switch (Phase B).
2026-07-08 18:45:57 +02:00
martmull 0ea0c23556 refactor(app-marketplace): rename featured to vetted (#22674)
## What

Renames the application-registration "featured" flag to "vetted" across
the backend, frontend, GraphQL schema/DTOs, and the marketplace UI.

"Vetted" better describes what the flag actually does today: it marks an
app as reviewed and approved by the Twenty team (a trust signal), rather
than "featured" which reads as spotlighting/promotion. The admin toggle
description was already "Mark this app as reviewed and approved".

## How

- Renamed `isFeatured` -> `isVetted` on the `ApplicationRegistration`
entity, DTOs (`MarketplaceApp`, `MarketplaceAppDetail`,
`UpdateApplicationRegistrationPayload`), services, GraphQL fragments,
and the settings/admin UI (labels: "Featured" -> "Vetted", "Featured
only" -> "Vetted only", etc.).
- Renamed the `MARKETPLACE_FEATURED_APPLICATIONS` constant/file to
`MARKETPLACE_VETTED_APPLICATIONS`.
- Regenerated GraphQL client artifacts (`generated-metadata`,
`generated-admin`, `twenty-client-sdk`).

### Database

The `isFeatured` column is renamed in place to `isVetted` via a single
2.20 fast instance command (`ALTER TABLE ... RENAME COLUMN`). No new
column, no data-copy backfill.

- Since all 2.19 commands (including the existing `isFeatured` backfill)
complete before any 2.20 command runs, the rename carries over the
values that backfill set.
- The entity uses `@WasRenamedInUpgrade` so the upgrade-aware layer
queries the old column name until the rename step runs during an
upgrade.

## Testing

- `nx typecheck` and `nx lint:diff-with-main` pass for twenty-server and
twenty-front.
- Ran `database:reset` on a fresh dev DB: the 2.19 `isFeatured` backfill
runs first, then the 2.20 rename; the column ends up as `isVetted` (and
`isFeatured` no longer exists), values preserved.
- Booted the server: the `@WasRenamedInUpgrade` decorator validates
against the upgrade sequence, and GraphQL introspection confirms all
four types expose `isVetted` and none expose `isFeatured`.
- Ran the three `graphql:generate` configs and the SDK metadata client
generator so the committed generated files match the generator output
(field ordering included).

## Notes

- The `api-breaking-changes` check flags the removal of the `isFeatured`
GraphQL field — that is expected and inherent to this rename.
- Translation catalogs (`locales/`) are intentionally not touched here
since they are managed via Crowdin; new English strings render via
Lingui's default-message fallback until translated.
2026-07-08 16:41:58 +00:00
Paul Rastoin 163c96c2e5 Validate range version app dev sync (#22625)
# Introduction
Also now validating the workspace version when running a sync manifest

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22625?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-07-08 16:37:42 +00:00
neo773 b5a73ad86a Chore/remove messaging mock specs (#22665)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22665?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-07-08 18:37:06 +02:00
neo773 674de0056b feat(messaging): message campaign delivery stats + views (#22661)
Re-land of #22452 (reverted in #22627). Rebuilt on fresh main with
upgrade commands isolated to 2-20 only; no other version's commands
touched.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22661?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-07-08 15:55:20 +00:00
Marie e751c589ca [Billing for self hosts] Add info in description (#22660)
Clarify how the same enterprise key is reused for one prod + one dev
instance.
Suggest to save / store enterprise key in clear somewhere.

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

---------

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-07-08 15:54:09 +00:00
Charles Bochet 89b6037db2 fix(docker): bump node base to 24.18.0 (all 24.17.0 CVE fixes + http.Agent premature-close fix) (#22677)
## Context

Follow-up to #22673, which pinned the base image back to
`node:24.16.0-alpine` to stop the prod flood of `Invalid response body
while trying to fetch …: Premature close` failures on Gmail/Calendar
sync and Cloudflare checks introduced by the 24.17.0 bump (#22529).

The regression is confirmed upstream: 24.17.0's response-queue-poisoning
fix (CVE-2026-48931) attaches a public `'data'` listener on idle
keep-alive sockets in the `http.Agent` pool, which false-triggers
node-fetch@2's premature-close detection whenever a server abruptly
resets a keep-alive socket right after a **complete** response —
standard behavior for Google's front end. Reported the day 24.17.0
shipped (nodejs/node#63989, #64098) and fixed by nodejs/node#64004,
released in **Node 24.18.0 (2026-06-23)**.

## What this PR does

Bumps all four stages to `node:24.18.0-alpine3.23` (digest-pinned).
24.18.0 is the current 24 LTS and contains:
- everything from 24.17.0: OpenSSL 3.5.7, CVE-2026-48930 (CVSS 9.8), and
the response-queue-poisoning guard itself — reimplemented via the
socket's internal `onread` hook instead of a public stream listener
(nodejs/node#64004)
- so we get the full security posture back **and** the regression fix.

## Verification

Deterministic repro (complete chunked response over keep-alive, then
abrupt socket destroy — per nodejs/node#64098), run against all three
images with node-fetch v2 and v3:

| Node | node-fetch@2 | node-fetch@3 |
|------|--------------|--------------|
| 24.16.0 | OK | OK |
| 24.17.0 | **`ERR_STREAM_PREMATURE_CLOSE: Invalid response body …
Premature close`** (byte-for-byte the prod Sentry error) | OK |
| 24.18.0 | OK | OK |

node-fetch@2 is what the Gmail batch layer
(`@jrmdayn/googleapis-batcher`) and the Cloudflare client resolve to,
matching the affected prod paths.

## Related

- #22673 — interim rollback to 24.16.0 (shipped as twenty/v2.19.1); this
PR supersedes it
- #22671 — classifies `ERR_STREAM_PREMATURE_CLOSE` as a transient
retryable network error; still worth landing since servers legitimately
reset keep-alive sockets

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22677?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-07-08 17:44:04 +02:00
martmull 9423af7f67 feat(server): add public marketplace resolver for vetted app catalog (#22647)
## What

Adds a public GraphQL resolver so unauthenticated clients (the public
website) can read the listed/vetted marketplace catalog without a
workspace token.

- `MarketplacePublicResolver` (metadata schema) exposes two public
queries guarded by `PublicEndpointGuard` + `NoPermissionGuard`:
  - `publicMarketplaceApps`
  - `publicMarketplaceAppDetail(universalIdentifier)`
  
Both delegate to the existing `MarketplaceQueryService` (no new logic,
no new REST routing). The existing workspace-guarded
`findManyMarketplaceApps` / `findMarketplaceAppDetail` queries are
untouched.
- Adds a shared `ApplicationCategory` type in `twenty-shared` (known
values plus `string` for backward compatibility) used to type
`ApplicationManifest.category`. A warning is logged server-side when an
app declares a category outside the known set.

## Why

This is the backend half of the public apps marketplace on the website.
Splitting it out so the server-side catalog exposure can be reviewed
independently from the website UI.

## Follow-up

The website PR (the `/apps` marketplace UI) consumes
`publicMarketplaceApps` and should merge after this one.

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

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

---------

Co-authored-by: martmull <martin@twenty.com>
2026-07-08 15:43:17 +00:00
github-actions[bot] b1dee7cf26 i18n - docs translations (#22676)
Created by Github action

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22676?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-07-08 17:24:00 +02:00
Charles Bochet 840c41e6b7 fix(docker): pin node base back to 24.16.0 to stop premature-close fetch failures (#22673)
## Context

Since v2.19.0 rolled out to prod (2026-07-07), workers are flooded with
mid-body fetch failures — `Invalid response body while trying to fetch
…: Premature close` — on Gmail message import (Sentry TWENTY-SERVER-D3X,
~22k events/day, ~1k users) and, simultaneously, on Cloudflare
custom-domain checks (TWENTY-SERVER-HXW/HXT). Both code paths were
unchanged between v2.18.5 and v2.19.0; their only shared layer is the
runtime HTTP stack.

The one relevant change in v2.19.0 is #22529: the base image bump
`node:24.16.0-alpine` → `node:24.17.0-alpine`. Node 24.17.0 patched
exactly the components under these fetches:
- `http`: CVE-2026-48931 — idle keep-alive sockets in the Agent pool now
get `socket.resume()` + a destroy-on-data guard on every free→reuse
cycle
- `deps`: llhttp 9.4.2 (security bump of the HTTP parser that decides
when a chunked body is complete)

The messaging import loop cycles the same keep-alive socket through the
pool once per message fetched, so any per-cycle failure probability is
amplified by prod volume. (Note: undici's equivalent CVE fix needed two
follow-up commits for races of this exact kind — sockets destroyed while
freshly handed to a request.)

## What this PR does

Pins the base image back to `node:24.16.0-alpine3.23` (same digest
v2.18.5 shipped with) on all four stages, and updates the security note
accordingly.

This doubles as the definitive root-cause test: if the premature-close
rate drops back to its historical baseline on the rebuilt image, the
24.17.0 HTTP-stack change is confirmed and we can file a solid upstream
report to nodejs/node.

## Trade-off — please weigh in

This re-exposes what #22529 fixed: 24.16.0 statically links OpenSSL
3.5.6, so the scanner will re-flag CVE-2026-48930 (TLS embedded-nul
hostname authority rebinding, CVSS 9.8) on the node binary. There is no
24.x release newer than 24.17.0 yet. The Dockerfile carries a TODO to
re-bump as soon as a fixed 24.x ships.

## Related

- #22671 classifies `ERR_STREAM_PREMATURE_CLOSE` as a transient
(retryable) network error at the application level — worth landing
regardless of this rollback, since sync channels currently hard-fail to
`FAILED_UNKNOWN` on what is a plain network race.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22673?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-07-08 17:19:09 +02:00
Deepak kumar maharana 34c5054bac Fix email validation for over-length inline edits (#22426)
## Summary

This PR addresses the inconsistency reported in #22406 where over-length
email values were accepted by the inline editor, optimistically shown as
saved, and then rejected by the backend.

### Changes

- Await `updateOneRecord` before updating the local record store, so the
UI is only updated after a successful mutation. This prevents the
optimistic state from showing values that failed to persist.
- Add a client-side maximum length validation (`255`) to `emailSchema`
so over-length email values are rejected before the GraphQL mutation is
sent.
- Propagate the client-side validation message through
`MultiItemFieldInput` so validation failures are surfaced immediately
instead of silently preventing the save.

### Verification

- Valid email addresses continue to save successfully.
- Over-length email values are rejected on the client without sending a
GraphQL request.

Related to #22406.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22426?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-07-08 17:01:56 +02:00
Pratik Mahajan 72d728ea8e fix(messaging): add sender name to IMAP/SMTP From headers (#22603)
Manual IMAP/SMTP outbound emails were being composed with a
bare email address in the From header, so recipients did not
see the sender's display name.
Gmail already built a proper sender header from Google profile
data, but manually configured IMAP/SMTP accounts had no
equivalent path and fell back to the raw address only.

Fix this by storing the optional sender display name in the
IMAP/SMTP/CALDAV connection parameters, exposing it through
the settings flow and metadata API, and reusing a shared
From-header formatter when composing outbound messages.
The formatter now builds a properly encoded sender header when
a name is available and falls back to the bare email address
when it is not, keeping the behavior safe for blank or missing names.
Gmail keeps using its existing Google-derived display name source;
this change only brings manual accounts up to the same header
formatting standard and removes duplicated formatting logic between
outbound drivers.

After this change, manual SMTP sends and IMAP draft creation include
the configured sender name in the From header, while blank names are
normalized away instead of producing malformed headers.
Existing manual account names are preserved when updates omit the
field, and edited accounts can still explicitly clear it through the
settings flow.

Add focused utility coverage for the shared From-header formatter.

Fixes: #22608

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22603?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-07-08 20:29:25 +05:30
Félix Malfait b5b6e110b0 Merge app Public URL and Public Domains settings into one App URL section (#22666)
## Context

The app settings tab showed two sections for the same concept: a "Public
URL" block with a very technical description (Permissions-Policy, COOP,
COEP), a blue info banner about the legacy `/s/` endpoint, and a
separate "Public Domains" block. Confusing, and scary-sounding for apps
whose routes are not really "public".

## Changes

**One merged "App URL" section** (still only rendered for apps that
actually expose HTTP-triggered functions):
- Title renamed to the neutral "App URL" with a one-line description:
"This app's routes are served from this URL. Add a custom domain to use
your own."
- Read-only copyable base URL input, custom domain list card right below
it.
- Removed the info banner and all header/CORS jargon.

**Always show the real URL**: `getFunctionsBaseUrl` now takes
`serverBaseUrl` and falls back to `${serverBaseUrl}/s` instead of
returning `undefined`, so self-hosted instances (no dedicated function
domain) see their actual base URL instead of nothing. This centralizes
the fallback previously duplicated in `FrontComponentRenderer` and
`getLogicFunctionHttpUrl`.

**"Public Domain" renamed to "Custom Domain"** in all user-facing
strings (add card, footer button, detail page, snackbars). Internal
identifiers, GraphQL types and routes keep the `PublicDomain` name to
match the backend entity.

**Polish**:
- Domain rows and the add card use a world icon instead of the mail
icon.
- Row description shows "Added x days ago" instead of a raw ISO
timestamp, via a new shared `useGetAddedRelativeDateDescription` hook
also adopted by the approved access domains card that had the same
inline helper.
- `FrontComponentRenderer` consumes `functionsBaseUrl` from
`useGetLogicFunctionHttpUrl` instead of re-deriving it from the same
atoms.
- User docs updated accordingly.

## Testing

- Ran the app locally (Postgres/Redis + server + front), synced a
fixture app with an HTTP-triggered logic function, and verified the
merged section renders correctly with the copyable URL and Add Custom
Domain card, no banner, no duplicate section.
- `getLogicFunctionHttpUrl` unit tests updated and passing (7/7), `npx
nx typecheck twenty-front` and `lint:diff-with-main` green.
- Locale catalogs are untouched; Crowdin sync regenerates them from the
new source strings.

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

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22666?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-07-08 16:44:26 +02:00
Charles Bochet d746909184 feat(sdk): validate graph page-layout widgets at build time (#22559)
When an app defines a graph widget (aggregate, pie, bar or line chart),
the built manifest can carry the wrong key and the server rejects it at
sync time with a confusing "aggregate field is required" error.

The SDK type already requires
`aggregateFieldMetadataUniversalIdentifier` and renames the raw
`aggregateFieldMetadataId` at compile time. But the manifest build runs
esbuild with no type checking, so a wrong or missing key slips through
and only fails later on the server.

This adds a build-time check that mirrors the server validator, with a
hint pointing at the right key when the raw one was used. It is
non-breaking since correctly authored apps already use the universal
key.

Tests: unit tests on the validator, plus a real graph widget added to
the rich-app fixture so the integration and e2e suites cover the happy
path.
2026-07-08 16:25:47 +02:00
Raphaël Bosi ad74f75e6a Make onboarding steps responsive on mobile (#22659)
https://github.com/user-attachments/assets/5840936c-d9b8-412f-bfec-2d5af768660c



The onboarding flow was built for a fixed 440px design with no
responsive handling, so on a phone the steps were cramped: oversized
padding/gaps, a decorative import-preview illustration that clipped, and
side-by-side name fields.

Adds targeted `@media (max-width: 768px)` rules (using the existing
`MOBILE_VIEWPORT`):
- Shared step page: smaller padding and gap on mobile (cascades to every
step).
- `UpgradeFreeTrial`: its own mobile padding (it overrides the base
padding).
- Header: reduced side padding.
- Import preview: hide the floating calendar cards on mobile (their
fixed offsets are tuned for the 440px card).
- Create profile: stack the avatar + name fields vertically on mobile.

Verified each step at 375px via Storybook; desktop is unchanged (media
queries gated to <=768px).

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22659?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-07-08 16:05:26 +02:00
nitin 640e6b8b33 fix(call-recorder): stream Recall media to storage to fix OOM (#22652)
## Summary

Fixes Call Recorder media ingestion OOMs by streaming Recall media into
Twenty direct uploads instead of buffering the full file in memory.

## Changes

- Opens the Recall media download stream and uses its `Content-Length`
as the direct upload size.
- Creates a Twenty direct upload target, streams the media body to it
with Node `http`/`https` backpressure, then completes the upload.
- Cleans up download/upload streams on target creation, upload, and
storage response failures.
- Keeps the media size cap for now while making it no longer required
for memory safety.
- Bumps `twenty-client-sdk` and `twenty-sdk` to `2.19.0`.

## Tests

- `yarn test:unit
src/logic-functions/flows/__tests__/ingest-call-recording-media.test.ts
src/logic-functions/flows/__tests__/put-media-download-body-to-upload-target.test.ts`
- `yarn typecheck`
2026-07-08 15:48:49 +02:00
Thomas Trompette 48730df0d2 feat(workflow): scaffold core workflowVersion entity + trigger cache (phase 0) (#21674)
## What

**Phase 0 (scaffold)** of migrating `workflowVersion` data to **core**.
Gated by `IS_WORKFLOW_VERSION_IN_CORE_ENABLED` with **no behavior
change** — nothing reads or writes the new core entity yet.

## Plan

`workflowVersion` becomes a thin **workspace shell** over a core entity
(the `dashboard`/`pageLayout` pattern), so navigation, the metadata
relations, and the record UI keep working while the heavy data
(`triggers`, `steps`) lives in core. Trigger dispatch will derive from
active core versions via a per-workspace cache, letting us **eliminate**
the denormalized `workflowAutomatedTrigger` object. `workflow` and
`workflowRun` stay as workspace objects.

Phases: **0 — scaffold (this PR)** → A — backfill + dual-write → B —
switch reads to core → C — drop the workspace `trigger`/`steps` columns
+ the `workflowAutomatedTrigger` object.

## Included
- **Core `WorkflowVersionEntity`** (`extends WorkspaceRelatedEntity`) —
stores version data, with triggers as an **array** (`triggers:
WorkflowTrigger[]`), a long-due shape change. Storage only: dispatch
reads the primary trigger, so behavior stays single-trigger for now.
- **Fast create-table instance command** for `core."workflowVersion"`
(v2.19.0).
- **`IS_WORKFLOW_VERSION_IN_CORE_ENABLED`** feature flag.
- **Per-workspace automated-trigger cache provider** deriving
CRON/DATABASE_EVENT dispatch from the active version's trigger —
groundwork for removing `workflowAutomatedTrigger`.

## Notes
- `WorkspaceRelatedEntity`, **not** `SyncableEntity`: this is user
runtime data (like `connectedAccount`/`apiKey`/`file`), not
application-manifest metadata.
- No frontend behavior; the generated `FeatureFlagKey` enums are updated
to include the new flag.
2026-07-08 12:50:03 +00:00
Parship Chowdhury 6554440bb1 fix: dropping favorites to the last position in a open folder (#22360)
Fixes #9213 

Before:


https://github.com/user-attachments/assets/0855f9c1-07c7-4080-8b5f-94fbe3c8b505



After:


https://github.com/user-attachments/assets/6ae26e89-9fe5-4ebe-b512-d8287ed2c070



<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22360?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>
2026-07-08 14:28:15 +02:00
Etienne 4b9f393167 fix: add workspace command to backfill missing AGENT source enum values (#22593)
**What**
Adds a 2.19 workspace upgrade command that backfills missing
FieldActorSource enum values (AGENT) into the Postgres enums backing
every ACTOR composite field (createdBy, updatedBy) across all existing
workspaces.

Each ACTOR field stores its source sub-field as a Postgres enum scoped
to its own table and schema (e.g.
workspace_abc.company_createdBySource_enum). Workspaces created before
these enum values were introduced are missing them, which causes runtime
errors when those sources are used.

**How**
buildActorSourceEnumBackfillTargets collects all ACTOR fields from the
workspace metadata cache and maps each enum sub-property to its
(tableName, columnName, enumName, expectedValues) tuple.
Before iterating over all targets, the command performs a single fast
pg_catalog.pg_enum lookup on company.createdBySource as a representative
sentinel. If AGENT is already present there, the workspace is skipped
entirely (idempotency fast-path).
For each remaining target, ALTER TYPE … ADD VALUE IF NOT EXISTS is
issued per missing value via
WorkspaceSchemaEnumManagerService.addEnumValue, making the command fully
idempotent and safe to re-run.

Fixes
https://discord.com/channels/1130383047699738754/1522507190949118003

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22593?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-07-08 12:05:15 +00:00
Thomas Trompette 6bc4d8efac fix(workflow): batch staled run reset to avoid Postgres param limit (#22654)
## Problem

Self-hosters with a large backlog of workflow runs stuck in `ENQUEUED`
see the recovery job (`WorkflowHandleStaledRunsJob`) fail with:

```
Error: Data validation error.
    at computeTwentyORMException ...
    at WorkspaceSelectQueryBuilder.getMany ...
    at WorkspaceUpdateQueryBuilder.execute ...
    at WorkflowHandleStaledRunsWorkspaceService.handleStaledRunsForWorkspace ...
```

So the very job meant to unblock enqueued runs can never complete, and
runs stay stuck.

## Root cause

`handleStaledRunsForWorkspace` fetched **every** staled run unbounded,
then called `repository.update(allIds, ...)`. That builds a `WHERE id IN
($1, $2, ... $N)`. Inside `WorkspaceUpdateQueryBuilder.execute`, a
"before" `SELECT` runs with that same huge `IN` list; with a big enough
backlog the bind-parameter count exceeds Postgres' limit, the `getMany`
throws a `QueryFailedError`, and `computeTwentyORMException` maps the
resulting PG error code to the generic `PostgresException('Data
validation error.')`.

There's also a secondary `before.length > QUERY_MAX_RECORDS` (200) guard
in the update path that would reject anything over 200 rows even if the
param limit weren't hit.

## Fix

Process staled runs in batches of `QUERY_MAX_RECORDS` (200), looping
until a pass finds none left — the same batching pattern the sibling
clean-runs job already uses. Each update flips the batch from `ENQUEUED`
to `NOT_STARTED`, so the find criteria stops matching them and the loop
terminates. The throttling recompute now runs once at the end, and only
if at least one batch was reset.

## Tests

New unit spec covering:
- no staled runs -> no update, no recompute
- single batch -> correct ids/payload, recompute once
- exactly 200 -> `take: 200`, 200 ids per update
- 450 backlog -> 3 update calls (200/200/50), loops until empty,
recompute exactly once

All 4 pass locally.

## Note

This fixes the recovery job. If runs keep re-accumulating as `ENQUEUED`,
there may be a separate producer-side issue worth investigating.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22654?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-07-08 13:53:26 +02:00
Thomas Trompette bfeaaa56a3 fix(workflow): handle IS/IS_NOT operand in text and array filters (#22640)
## Problem

Sentry `TWENTY-SERVER-G4F` — `Error: Operand IS not supported for this
filter type` (30k+ occurrences, 15 workspaces, ongoing).

A workflow **Filter** step throws when a step filter carries an
`IS`/`IS_NOT` operand on a text/array field type (`TEXT`,
`MULTI_SELECT`, `EMAILS`, `PHONES`, `ADDRESS`, `LINKS`, `FULL_NAME`,
`ARRAY`, `RAW_JSON`). `evaluateTextAndArrayFilter` only handled
`CONTAINS`/`DOES_NOT_CONTAIN`/`IS_EMPTY`/`IS_NOT_EMPTY` and hit
`default:` → `throw`. The throw propagates out of `FilterWorkflowAction`
and **fails the entire workflow run**.

The current frontend no longer offers `IS`/`IS_NOT` for these types, so
these are **legacy persisted step filters** in older (immutable)
workflow versions that keep executing.

## Fix

Handle `IS`/`IS_NOT` in `evaluateTextAndArrayFilter` as
`contains`/`!contains`, consistent with `evaluateSelectFilter` (chosen
over strict equality because the routed types include arrays/composites
where `==` would silently never match). No existing operand behavior
changes.

## Tests

Added coverage for legacy `IS`/`IS_NOT` on `TEXT` and `MULTI_SELECT`.
Note: the pre-existing `date operands` test failures are
timezone-dependent and unrelated to this change (they fail on `main`
too).

Fixes TWENTY-SERVER-G4F

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22640?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-07-08 13:12:08 +02:00
Brahm Lower be68c0cbab fix: corrected grammar in approved domain message (#22646)
title says it all- quick grammar fix in the workspace approved access
domains form

<img width="1484" height="610" alt="image"
src="https://github.com/user-attachments/assets/bb4ce7a5-6ba4-40a7-a1f9-57c3e8d2dde0"
/>


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22646?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-07-08 13:05:33 +02:00
Aressand 14e4b5bd31 fix(rls): prefill RLS predicate fields when creating related records (#22620)
## What / Why

Creating a record from a relation section (e.g. adding a child record
from its
parent's record page) fails with **"Record does not satisfy security
constraints"** for any role restricted by row-level permissions.

Root cause: `useAddNewRecordAndOpenSidePanel` builds the create payload
with only
the label field and the parent FK. Fields required by the role's RLS
predicates
(e.g. `owner = current workspace member`) are missing, so the server
rejects the
insert in `validateRLSPredicatesForRecords` with
`RLS_VALIDATION_FAILED`.

`useCreateNewIndexRecord` (the record table "+ New" path) already
handles this via
`buildRecordInputFromRLSPredicates()`. The relation-section creation
path was
simply never updated — same bug class, different entry point.

## How

Spread `buildRecordInputFromRLSPredicates()` into the create payload in
`useAddNewRecordAndOpenSidePanel`, mirroring `useCreateNewIndexRecord`.
The record
is then created with the RLS-required fields prefilled (e.g. owner =
current
member), so it passes server-side validation.

No behavior change for roles without RLS predicates:
`buildRecordInputFromRLSPredicates()`
returns an empty object when there are none.

## Test plan

Requires row-level permissions (Enterprise) enabled.

1. Create a role with an RLS predicate `owner IS current workspace
member`.
2. Assign it to a non-admin user; create a parent record owned by that
user.
3. As that user, open the parent record and add a child record from a
relation
   section (the "+" on a one-to-many / many-to-one relation field).
4. **Before:** "Record does not satisfy security constraints".
**After:** the child record is created, with owner prefilled to the
current member.

Also verified via REST against a self-hosted instance: inserting the
child record
without the owner field is rejected (HTTP 400, RLS_VALIDATION_FAILED);
inserting it
with `ownerId = current member` succeeds (HTTP 201).

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22620?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-07-08 13:03:45 +02:00
martmull b733a79821 feat(server): support server-scoped files via nullable workspaceId on file table (#22587)
Part of the app settings architecture cleanup
(twentyhq/core-team-issues#2456) — PR 1 of the server-level documents
plan, reworked after the revert of #22560 (#22579). Same capability,
different shape: **no new entity** — server-level documents live in the
existing `file` table with a nullable `workspaceId`.

## Problem

All file storage is workspace-scoped (`FileEntity.workspaceId NOT NULL`,
`{workspaceId}/{app}/…` storage keys). Server-level data like
application-registration manifests and tarballs for ownerless catalog
registrations has no first-class home, forcing raw-driver bypasses
(`DefaultAiCatalogService`, prototype #22556).

## Changes (core storage layer only — no HTTP serving, no GraphQL
exposure)

**`FileEntity` gains server scope** (mirrors `KeyValuePairEntity`, which
already supports both instance-level and per-workspace rows):
- `workspaceId` uuid becomes **nullable** — NULL means server-scoped;
the entity no longer extends `WorkspaceRelatedEntity` and declares its
columns directly
- `applicationRegistrationId` nullable FK (`onDelete: CASCADE`) —
registration-owned documents follow their registration
- ownership checks: `workspaceId IS NOT NULL OR
applicationRegistrationId IS NOT NULL` and `workspaceId IS NULL OR
applicationRegistrationId IS NULL` — every row has exactly one owner
- `IDX_FILE_APPLICATION_REGISTRATION_ID_PATH_UNIQUE` UNIQUE
(`applicationRegistrationId`, `path`) — mirrors the workspace
unique-constraint pattern; workspace rows are exempt via their NULL
`applicationRegistrationId`

**New `ServerFileStorageService`** (`file-storage/services/`, exported
from the global `FileStorageModule`; `FileStorageService` moved
alongside it):
- storage keys
`server/{fileFolder}/{applicationRegistrationId}/{resourcePath}` — the
registration segment is injected by the service itself, so paths cannot
collide across registrations; scope-validation util mirroring
`validateStoragePathIsWithinWorkspaceOrThrow`; new `ServerFileFolder`
enum in twenty-shared
- `writeServerFile` (upsert on (`applicationRegistrationId`, `path`) +
driver write; throws on failure), `readServerFile`/`readServerFileById`
(missing row or bytes surfaces `FILE_NOT_FOUND`),
`checkServerFileExists`, `deleteServerFile`/`deleteByServerFileId`
(bytes best-effort, row authoritative),
`deleteByApplicationRegistrationId`
- rows are accessed through a plain repository pinned to `workspaceId:
IsNull()` on every query; workspace-file code paths still go through
`WorkspaceScopedRepository`, which never sees NULL rows

**Null-safety ripples** (workspaceId is now `string | null`):
- `WorkspaceScopedEntity` bound widened to `workspaceId: string | null`
(the wrapper always filters with a concrete id)
- `list-and-delete-orphaned-workspace-entities` now skips `workspaceId
IS NULL` rows — previously `NOT EXISTS` would have flagged server rows
as orphans and deleted them
- `PendingFileCleanupService` sweeps only `workspaceId IS NOT NULL`
rows; `application-package-fetcher` pins its tarball lookup to workspace
rows (tarball migration to server scope is a follow-up PR)

**Migration**: `allow-server-scoped-file` ships as a **2-20 fast
instance command** (2.20.0 is current since #22639; re-slotted from 2-19
per review). Command runs are tracked by name, so instances that already
executed the 2-20 `standardOverrides` drop command still pick this one
up. Its realistic timestamp sorts before that drop command's fabricated
`1825000000000`, which the
`ci:allow-upgrade-command-timestamp-exception` label covers.

## Next PRs in the plan

- PR 2: HTTP serving + token type for server files
- PR 3: application-registration manifests stored as versioned server
files (rework of draft #22556)
- PR 4 (optional): registration tarballs migrate to server scope

## Verification

- New spec `server-file-storage.service.spec.ts` (traversal table,
upsert conflict semantics, row-before-bytes reads, best-effort byte
deletion, registration cascade) + scope-validation util spec; affected
suites all green
- Typecheck (server + shared), `lint:diff-with-main`, full `oxfmt
--check src/` on both packages clean
- Fresh `database:reset` on the re-slotted branch: the 2-20 command
executes, generator then reports **no schema drift**; both ownership
checks and the composite unique verified live (dual-owner insert and
duplicate registration+path both rejected)
2026-07-08 11:56:24 +02:00
martmull 2f5e9d47ef Suppress Claude session URL in generated PR bodies and commits (#22651)
## Context

PRs generated by Claude Code web sessions (e.g. #22648) still carried a
bare `claude.ai/code/session_...` link and a "Generated by Claude Code"
footer, which do not comply with the project's committed Claude
settings.

The existing `.claude/settings.json` already set `attribution.commit`
and `attribution.pr` to empty strings, which suppresses the generic
"Generated with Claude Code" footer. But the web session URL is a
separate feature: web sessions add the session link to PR bodies and a
`Claude-Session` git trailer to commits, controlled by
`attribution.sessionUrl` (available from Claude Code v2.1.182), not by
the `commit`/`pr` attribution text. Since that field was unset, the
session link leaked through.

## Changes

- `.claude/settings.json`:
- Add `"sessionUrl": false` to the `attribution` object so the session
URL is omitted from both PR bodies and commit trailers.
- Extend the `SessionStart` hook instructions to explicitly cover GitHub
PR descriptions, PR/issue comments and reviews (not just commits and
files), and to discourage code comments unless strictly necessary
(TypeScript directives or short "why" comments for non-obvious business
logic).

## Notes

The public schemastore schema referenced by `$schema` lags behind the
CLI and does not yet list `sessionUrl`, so editors may show a harmless
validation hint until it updates. The Claude Code parser supports the
field, per the on-the-web docs.

Co-authored-by: martmull <martin@twenty.com>
2026-07-08 10:51:48 +02:00
nitin 525ed74b2f Fireflies: port app to the CallRecording standard object (#22642)
## Context

The Fireflies app predates the core `CallRecording` standard object: it
wrote transcripts and summaries as markdown into two rich-text field
extensions on `CalendarEvent`. That model has no per-call record,
silently drops orphan calls, and diverges from how the Call Recorder app
stores recordings. This is the first step of moving Fireflies onto the
CallRecording architecture; media ingestion, retry markers, and a
dedicated tab/front component iterate on top of this.

## What this PR does

Replaces the CalendarEvent field extensions with upserts into the core
`CallRecording` object:

- **One CallRecording per Fireflies call** via a deterministic UUID
derived from the Fireflies meeting id — the `meeting.transcribed`
webhook, the `meeting.summarized` webhook, and manual **Sync Fireflies
Call** runs all converge on the same row regardless of order, with a
create-race fallback to update
- **Transcript as diarized JSON** (participant + sentence-level relative
timestamps) instead of markdown, matching the entry shape the
CallRecording `transcript` field holds for other recording apps;
fetchers now request `date` and sentence `end_time`
- **Summary stays rich text** in `CallRecording.summary`, composed from
the Fireflies overview / action items / topics / keywords
- **Call metadata filled** — title, `startedAt`/`endedAt` (from `date` +
`duration`), `externalRecordingId`; transcript sync marks the row
`COMPLETED`, a summary-first sync creates it as `PROCESSING`
- **Orphan calls are kept**: when no CalendarEvent matches by
`eventExternalId` / `iCalUid`, the CallRecording is created without a
calendar event link instead of being dropped
- **Role**: now reads/writes `callRecording`; `calendarEvent` drops to
read-only; the two schema field extensions and the markdown transcript
formatter are removed

### Housekeeping

- App version bumped to **0.2.0** so installed workspaces pick up the
upgrade
- `twenty-sdk` / `twenty-client-sdk` aligned on `^2.18.0` (matches
call-recorder)
- Gallery screenshots depicting the removed CalendarEvent fields dropped
from the marketplace config
- README: "Upgrading from 0.1.x" section documenting the field removal
and the backfill path
- Integration test guarding the mirrored status constants against the
server's CallRecording select options

## Upgrade note

Upgrading removes the two app-owned CalendarEvent fields and their
stored content (app upgrades infer deletions from the manifest diff).
That data is a cache of Fireflies content and is re-derivable: any call
still in Fireflies can be re-ingested as a CallRecording via **Sync
Fireflies Call**.

A follow-up PR adds a post-install/upgrade sweep (same pattern as the
Call Recorder sweep in #22552) that pages through Fireflies history and
replays each call through the same sync flow, so history backfills
automatically on install and upgrade — the deterministic ids make
re-sweeping idempotent.



<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22642?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-07-08 14:11:49 +05:30
nitin 4b127451b4 Fix root domain /authorize rendering workspace-scoped consent page (#22641)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22641?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-07-08 14:11:24 +05:30
Paul Rastoin 07b9d855b7 2.20 fieldMetadata and objectMetadata standardOverrides deprecation (#22650)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22650?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-07-08 10:33:03 +02:00
martmull 1914b11de2 Unify featured/vetted app terminology to featured (#22648) 2026-07-08 09:56:24 +02:00
twenty-pr[bot] 3b1a0ef3e6 chore: bump version to 2.20.0 (#22639)
## Summary

- Moves current version to previous versions array
- Sets TWENTY_CURRENT_VERSION to the new version
- Updates TWENTY_NEXT_VERSIONS with the next minor version
- Bumps twenty-client-sdk, twenty-sdk, and create-twenty-app to the same
version

## Checklist

- [ ] Verify version constants are correct
- [ ] Verify npm package versions match

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

Co-authored-by: Github Action Deploy <github-action-deploy@twenty.com>
2026-07-07 17:40:56 +02:00
martmull 435073e9c5 Display featured applications in marketplace (#22635)
## After
<img width="1060" height="589" alt="image"
src="https://github.com/user-attachments/assets/74dfadcf-8698-4404-81c6-b309cc4cbf79"
/>
<img width="732" alt="image"
src="https://github.com/user-attachments/assets/0e1a3644-04bc-4208-aa77-3842d9db9cc8"
/>
<img width="797" alt="image"
src="https://github.com/user-attachments/assets/0456ecce-607a-4705-8a89-c77029bfb6ac"
/>

- Remove IS_MARKETPLACE_SETTING_TAB_VISIBLE feature flag
- add vetted toggle in admin app tab
- added people data labs, last contact and call recorder to default
vetted applications

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22635?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: prastoin <paul@twenty.com>
2026-07-07 17:05:54 +02:00
Raphaël Bosi 2c0e0b2eac Create billing customer at signup so onboarding rewards are credited (#22633)
## Problem

Onboarding credit rewards (install apps, import contacts, invite team)
were silently dropped. They credit the workspace balance via
`billingCustomer.increment(...)`, but no `billingCustomer` row exists
until the plan step (it's created lazily when the first subscription is
set up, which is after those steps). So the increment affected 0 rows
and the credit was lost. A user installing 3 apps saw only the trial
grant, not the expected +1.5 credits.

## Fix

Create the Stripe customer + `billingCustomer` row eagerly at signup via
a new `BillingCreditService.ensureBillingCustomer`, called from
`signUpOnNewWorkspace` after the workspace transaction commits. It is
idempotent, guarded by `IS_BILLING_ENABLED`, and non-blocking (failures
are logged, not thrown). The later subscription flow reuses this
customer (no duplicate Stripe customer), and trial eligibility is
unchanged since the customer has no subscriptions yet.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22633?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-07-07 14:51:53 +00:00
Thomas Trompette 35d3f9b89d fix(ai-chat): sort message parts by orderIndex on reload (#22629)
## Problem

When an AI chat conversation is reloaded from the DB (page refresh or
initial load), message parts are returned without guaranteed ordering.
The renderer groups reasoning/thinking steps only when they are
**contiguous** — so if `reasoning` parts land after the `text` part,
thinking blocks appear below the final answer, and can appear duplicated
or split.

This only surfaces with reasoning models (OpenRouter, etc.) because
those produce multiple reasoning/tool/text parts per message, making
ordering observable. Simple text-only messages aren't affected.

## Root cause

`AgentChatService.getMessagesForThread()` fetches `parts` via a TypeORM
relation with no ORDER BY on `orderIndex`. The DB can return parts in
any order.

`mapDBMessagesToUIMessages()` then calls `dbMessage.parts.map(...)`
directly, without sorting.

A parallel server-side utility (`mapDBPartsToUIMessageParts.ts`) already
sorts by `orderIndex` — this fix makes the frontend fetch path
consistent with it.

## Fix

Sort parts by `orderIndex` before mapping to UI parts in
`mapDBMessagesToUIMessages.ts`.

```ts
parts: [...dbMessage.parts]
  .sort((a, b) => a.orderIndex - b.orderIndex)
  .map(mapDBPartToUIMessagePart),
```

`orderIndex` is already included in `GetChatMessagesDocument` — no
schema or query changes needed.

## Test

1. Open Ask AI with a reasoning model (e.g. via OpenRouter).
2. Run a prompt that produces thinking steps.
3. Hard-refresh the page.
4. Thinking blocks should appear collapsed above the final answer, not
below it.

Closes #22386

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22629?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-07-07 15:52:54 +02:00
nitin 18c10f4632 Call recorder: sweep upcoming calendar events for recording bots on install (#22552)
## Context

The Call Recorder schedules Recall bots reactively — a database event
trigger reconciles a calendar event when it is created or updated. That
misses meetings that already existed before the app was installed, and
meetings created far ahead that are never edited as they approach.
Neither gets a bot, though recording is on by default.

## What this PR does

Moves to the rolling near-term window Recall recommends for [your own
calendar
integration](https://docs.recall.ai/docs/creating-and-scheduling-bots#scheduling-bots-with-your-own-calendar-integration)
— "a daily sync of the next 7 days". Bots are scheduled only for
meetings starting within a **7-day horizon**, kept complete by three
mechanisms:

- **Horizon (policy).** `resolveCallRecorderPolicyResult` caps
scheduling at 7 days from now (`EVENT_BEYOND_SCHEDULING_HORIZON`),
measured from `startsAt` (the bot's join time). The existing reactive
trigger inherits this — far-future creates no longer schedule, and a
meeting moved out of the window has its bot canceled.
- **Daily sweep (cron).** New `sweep-upcoming-calendar-events`
reconciles the 7-day window each day, so a meeting that ages into it
without being edited still gets a bot.
- **Fresh-install seed (post-install).** The app's single post-install
hook (`start-post-install-backfills`) runs the sweep once on a fresh
install so a new workspace is covered right away instead of waiting for
the first cron; on an upgrade it relies on the cron and backfills
missing summaries instead.

The sweep runs through the authenticated
`reconcile-upcoming-calendar-events` route, which batches ids through
the existing reconciliation flow and re-invokes itself near the 900s
timeout. Deterministic recording ids keep it idempotent. App self-calls
go through a shared `postToOwnRoute` util targeting the server-injected
`TWENTY_FUNCTIONS_URL`; a failed kickoff throws so the async hook
retries instead of going silently green.

Also: fallback titles for call recordings whose calendar event is
visibility-restricted; app version → 1.0.7.

## Deferred

- Far-future bots already scheduled by the previous no-cap behavior
aren't proactively canceled — they fire naturally, or cancel if their
event is edited out of the window.
- Recall rejects an in-place `join_at` update under 10 min out; today
that logs a warning rather than delete-and-recreate.

## Test plan

- `yarn test:unit`: 407 tests / 65 files pass — new coverage for the
horizon (including a meeting that starts in-window but ends beyond it),
the 7-day query filter, the cron handler, the post-install hook's
fresh-install vs upgrade branches, and the batch/continuation flow.
- `yarn typecheck`, `yarn lint`, and `yarn twenty dev:build` (manifest
build) pass.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22552?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-07-07 19:03:28 +05:30