ea9e11581cd2fc730ffe059ddbf34217153e7fae
13158 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ea9e11581c |
feat(billing): replace Stripe trial emails with fair, well-timed reminders (#22186)
## Why We currently rely on Stripe's automated trial-ending email. It misfires: the global "remind 7 days before trial ends" setting lands the reminder on **signup day** for the 7‑day no‑card trial, and the "your card will be charged" copy makes no sense for a trial with no card. This replaces it with our own honest, well‑timed, Twenty‑branded emails. ## 🔒 Safety — these emails are OFF by default Because these reach real customers, the whole feature is gated behind a kill‑switch that **defaults to `false`**: - **`BILLING_REMINDER_EMAILS_ENABLED` (default `false`)** — checked **both** at cron registration **and** on every job run (defense in depth), so the emails can never be sent inadvertently (not on deploy, not in staging, not via a stray trigger). They only go out once an operator explicitly opts in. - Also gated on `IS_BILLING_ENABLED` (cloud‑only; self‑hosters unaffected). - In non‑prod the email driver is typically `logger`, so even if enabled there, nothing is actually sent. A unit test asserts that with the switch off, **zero** emails are produced. ## What it does A daily cron (`0 8 * * *`) sends three honest, Twenty‑branded emails: | Plan | Email | When | |---|---|---| | No‑card trial (7d) | "Add a card to keep your data" | **1 day before** trial ends | | Card‑on‑file trial (30d) | Upcoming‑charge heads‑up (cancel in one click) | **7 days before** first charge | | Yearly subscription | Renewal reminder (no surprise) | **7 days before** each renewal | - **Monthly renewals get no reminder** (avoids noise) — only the first charge and annual renewals do. - Branches no‑card vs with‑card on the customer's payment‑method flag (with a trial‑duration fallback), so someone who adds a card mid‑trial correctly gets the charge heads‑up instead of the add‑a‑card one. - **Idempotent** per `(workspace, boundary date)` via workspace‑level user vars — yearly reminders re‑fire each period, but the daily cron never double‑sends. - Offsets are configurable via new `BILLING_*_REMINDER_DAYS_BEFORE` variables. Also **warms up the tone** of the existing suspended / deleted workspace emails (less robotic, fair, loss‑aversion framing) — these already act as the "come back or lose your data" win‑back, so no extra win‑back email was added. ## Rollout 1. Merge. 2. Disable Stripe's automated trial/renewal customer emails in the Stripe dashboard. 3. Review copy/timing, then set `BILLING_REMINDER_EMAILS_ENABLED=true` to turn the cron on. ## Notes for reviewers - **i18n:** new English strings render via Lingui's msgid fallback; translation catalogs are intentionally **not** included to keep the diff focused (the repo extracts translations via its standard periodic `lingui extract` sync — `main` already carries catalog drift). Diff is 18 code files. - **Recipients:** reminders go to all workspace members, consistent with the existing suspension emails. Happy to scope the charge‑related ones to billing admins if preferred. - **Follow‑ups discussed:** in‑app trial banner, loss‑aversion with real record counts, and failed‑payment dunning are the higher‑leverage conversion levers beyond this. ## Test plan - [x] `typecheck` (twenty-server, twenty-emails) - [x] oxlint type‑aware + oxfmt - [x] Unit tests: no‑card path, with‑card path, idempotency, yearly renewal, billing‑disabled, **kill‑switch off → no send** (6/6 green) - [ ] Manual: set the flag on a staging instance with `logger` driver and confirm the right email is logged at each boundary https://claude.ai/code/session_0147ujzHv1X4vzimf4iGbnT4 --- _Generated by [Claude Code](https://claude.ai/code/session_0147ujzHv1X4vzimf4iGbnT4)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22186?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
c635a191bf |
fix: uneven spacing in date picker calendar grid (#21881)
### Summary While working on #19739, I found that in the date filter calendar dropdown, day cells and highlighted dates looked misaligned i.e. tighter on the right side. The solution is to apply a uniform margin in `DatePicker.tsx` and `DateTimePicker.tsx`. ### Before: <img width="377" height="436" alt="Screenshot 2026-06-20 021856" src="https://github.com/user-attachments/assets/4ef62a48-6b99-4647-95f6-bd39f43eaa26" /> <img width="442" height="518" alt="Screenshot 2026-06-20 021932" src="https://github.com/user-attachments/assets/e6e1e8f8-257a-41e5-825f-bd2fe91e372a" /> ### After: <img width="346" height="380" alt="Screenshot 2026-06-20 021813" src="https://github.com/user-attachments/assets/5fdd03fe-e61b-4fec-a0f3-f6ac4ee9effb" /> <img width="322" height="457" alt="Screenshot 2026-06-20 022013" src="https://github.com/user-attachments/assets/a33c6439-f2d5-43e7-a043-f9cf4fec770a" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21881?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
05df528fd5 | Add logging on sync catalog job (#22192) | ||
|
|
eedd838189 |
Fix threaded draft email replies (#22175)
## Summary Fixes Gmail and Microsoft draft replies so workflow-created drafts stay attached to the existing provider thread. Fixes twentyhq/core-team-issues#2597. ## Root cause The email composer already resolved `threadExternalId` and `references` from `inReplyTo`, but `DraftEmailTool` only forwarded `inReplyTo` to the outbound draft service. Gmail therefore created a raw draft without `message.threadId`, which lets the draft appear as a standalone compose instead of an inline thread reply. For Microsoft, the draft path used Graph `createReply`, but parent lookup filtered on a URL-encoded `internetMessageId`. That can miss the parent message and fall back to creating a new draft message instead of a reply draft. ## Changes - Forward `threadExternalId` and `references` from `DraftEmailTool` to outbound draft creation. - Set Gmail draft `message.threadId` when `threadExternalId` is available. - Make Microsoft parent lookup use Graph request query builders with OData string escaping, so `createReply` is reached reliably. - Add targeted Jest coverage for the Draft Email tool, Gmail draft threading, and Microsoft reply-draft creation. ## Validation - `NX_DAEMON=false /Users/thomascolasdesfrancs/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node ../../node_modules/nx/dist/bin/nx.js jest twenty-server -- --runTestsByPath src/engine/core-modules/tool/tools/email-tool/__tests__/draft-email-tool.spec.ts src/modules/messaging/message-outbound-manager/drivers/gmail/services/__tests__/gmail-message-outbound.service.spec.ts src/modules/messaging/message-outbound-manager/drivers/microsoft/services/__tests__/microsoft-message-outbound.service.spec.ts --runInBand` - `NX_DAEMON=false /Users/thomascolasdesfrancs/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node ./node_modules/nx/dist/bin/nx.js lint:diff-with-main twenty-server` <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22175?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Félix Malfait <FelixMalfait@users.noreply.github.com> |
||
|
|
1076866820 |
fix(server): preserve anyFieldFilterValue in view manifest sync (#22004)
### Summary - Fixes #19978 - `shouldHideEmptyGroups` was already wired up in the type and converter; this PR only closes the remaining gap for `anyFieldFilterValue`. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22004?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Signed-off-by: Parship Chowdhury <parshipchowdhury@gmail.com> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
35d64ac7f2 |
fix(server): wrap file storage upsert and read in transaction (#21924)
## Summary This PR fixes a race condition occurring during file and image uploads in environments that use Pgpool-II or similar connection poolers with read-replica scaling. Currently, in `FileStorageService.writeFile`, `fileRepository.upsert` writes the file record to the primary database node. However, the immediate subsequent call to `fileRepository.findOneOrFail` is executed outside of a transaction. Consequently, connection poolers like Pgpool can route this `SELECT` query to a read-replica. Due to replication lag, the replica may not yet reflect the newly inserted record, throwing an `EntityNotFoundError` and failing the upload process (even though the file is successfully saved in S3 and the database). This PR wraps both operations in a TypeORM database transaction when `queryRunner` is not provided. This ensures that the `SELECT` query correctly targets the primary node, guaranteeing immediate read-after-write consistency. ## Affected version - Twenty Self-hosted (e.g. `v2.14.x`) configured with Pgpool/read-replicas. ## Changes Made - **`file-storage.service.ts`**: Wrapped `transactionalFileRepo.upsert` and `transactionalFileRepo.findOneOrFail` within `this.applicationRepository.manager.transaction` to ensure read-after-write consistency. ## How to Test 1. Set up Twenty in a self-hosted environment using Pgpool configured with load balancing / read-replicas. 2. Attempt to upload a file to a `Files` custom field or upload an image as an organization logo. 3. Observe that the upload completes successfully without throwing an `EntityNotFoundError` in the server logs. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21924?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Ali Bildir <[alibildir@gmail.com]> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
ce9ca1184c |
i18n - docs translations (#22189)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22189?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
1cf1c33899 |
Add command menu items to people data labs (#22180)
- Adds command menu items to enrich people and companies - Improve readme ## After <img width="1039" height="584" alt="image" src="https://github.com/user-attachments/assets/9c6c3967-e905-4bf2-a3ef-307ae5bdca92" /> <img width="2560" height="332" alt="image" src="https://github.com/user-attachments/assets/4ab4ffb8-fe52-4612-99c7-282d0ffa2c94" /> <img width="2560" height="478" alt="image" src="https://github.com/user-attachments/assets/d496e21b-de18-4c52-acc7-6106afca0bb7" /> <img width="2560" height="452" alt="image" src="https://github.com/user-attachments/assets/caea4de3-5bee-4de7-a347-c5d8540fd179" /> <img width="2560" height="335" alt="image" src="https://github.com/user-attachments/assets/92bd17e3-f4b6-4fcb-84ae-d8ebc7ccce1e" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22180?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
aabffc427a |
Call recorder readme nitpick (#22185)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22185?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. --> |
||
|
|
24c042f0ef |
feat(messaging): skip webhook-active channels in list-fetch crons until sync is stale (#22183)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22183?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. --> |
||
|
|
f270fb25c8 |
call recorder readme billing update (#22184)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22184?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. --> |
||
|
|
0b31d2a6a0 |
fix(front): wrap long note/markdown text on mobile browsers (#21909)
Fixes #21929 ### Issue When viewing/reading a note's body (BlockNote rich-text / markdown content) on Android, long text overflows horizontally off the viewport in **Chrome** and **Firefox**, while it wraps correctly on **iOS Safari**. ### Root cause The note body is rendered by the BlockNote editor (`StyledEditor` in `BlockEditor.tsx`). The block/inline content had no `overflow-wrap`, and the flex-based `.bn-block-content` had no `min-width` constraint. WebKit (iOS Safari) breaks the content, but Blink (Android Chrome) and Gecko (Android Firefox) keep the intrinsic *min-content* width of the flex children, so the container expands past the viewport instead of wrapping. ### Fix Add wrapping/sizing rules to the editor container so text breaks and wraps consistently across browsers, without changing the desktop layout: - `overflow-wrap: anywhere` on `.bn-block-content` / `.bn-inline-content` — unlike `break-word`, this reduces the min-content size so flex children can actually shrink. - `min-width: 0` on `.bn-block-content` and on the editor wrapper, plus `max-width: 100%` on the wrapper. ### Test plan - Open a Note containing a very long word / URL or a long paragraph. - Android Chrome & Firefox: text now wraps within the viewport (no horizontal overflow). - iOS Safari: unchanged (still wraps). - Desktop: layout unchanged. ### Screenshots Android <img width="1080" height="1949" alt="Screenshot_20260620_231356_Chrome(1)" src="https://github.com/user-attachments/assets/bb1e38cf-198a-4ded-9dde-e0a26e637ed8" /> iPhone <img width="686" height="1280" alt="IMG_20260620_231841_096" src="https://github.com/user-attachments/assets/9cee7557-bb69-453d-bea4-a5c37de220af" /> --- ### ✅ Verified on a real Android device Reproduced and validated on a **Samsung Galaxy A71 (Android, Chrome / Blink)** using the exact note show-page DOM/CSS chain (`ScrollWrapper` → full-width container → `StyledEditor` → `.bn-mantine` `container-type: inline-size` → flex `.bn-block-content` → ProseMirror `word-wrap: break-word`): - **Without the fix:** a long unbreakable string stays on one line and overflows horizontally off the viewport (`scrollWidth ≈ 1336px` in a ~360px viewport, with a horizontal scrollbar) — matching the reported bug. - **With the fix:** the same string wraps within the viewport. Notably this does **not** reproduce on desktop Chromium — only on the mobile engine — which matches the original report (Android Chrome/Firefox broken, iOS Safari fine). The flex `.bn-block-content` (`min-width: auto`) resolves to the unbreakable token's intrinsic width on Android Blink; `min-width: 0` + `overflow-wrap: anywhere` lets it shrink and wrap. Screenshot <img width="1080" height="2400" alt="image" src="https://github.com/user-attachments/assets/827dc579-4b1e-43ea-8b28-0ca781b12d88" /> |
||
|
|
9870d1e6a9 |
Fix source icon SVG ID collisions (#22177)
## Summary Fixes the Gmail and Google Calendar source icons by replacing document-global generic SVG IDs (`a`, `b`, `c`, etc.) with icon-specific IDs. This prevents inline SVG gradients, masks, and filters from resolving against another icon instance when the icons render together in the record table actor/source column. ## Root cause The Gmail and Google Calendar SVG assets both used generic IDs. Browser SVG fragment references are document-global for inline SVG, so whichever icon appears first can hijack the other icon's `url(#...)` references. That made the Calendar icon pick up Gmail gradients, and could also affect Gmail depending on DOM order. ## Before <img width="1280" height="720" alt="Before icon collision screenshot" src="https://github.com/user-attachments/assets/82ce20a8-47c7-4b70-8af2-e134cf88c0b8" /> ## After <img width="1280" height="720" alt="After icon collision screenshot" src="https://github.com/user-attachments/assets/240120a6-b84e-4682-a116-0ccb48192543" /> ## Validation - Rendered Gmail + Calendar in both DOM orders via a local browser fixture. - Verified the before fixture had 14 generic SVG IDs and the after fixture had 0. - Verified 4 SVGs rendered, no cross-icon URL reference problems, and no browser console warnings/errors. - Ran `xmllint --noout packages/twenty-ui/src/assets/icons/gmail.svg packages/twenty-ui/src/assets/icons/google-calendar.svg`. - Ran `rg -n "id=\"[a-z]\"|url\(#[a-z]\)|mask=\"url\(#[a-z]\)\"|filter=\"url\(#[a-z]\)\"" packages/twenty-ui/src/assets/icons -S` and confirmed no matches. `yarn nx build twenty-ui` could not run in this worktree because `node_modules` is missing, and Yarn reports: `Couldn't find the node_modules state file`. |
||
|
|
f86bf637d1 |
[BREAKING CHANGE] remove call recording feature flag and backfill upgrade command for existing command menu items navigation command (#22176)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22176?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. --> |
||
|
|
407273cff2 |
fix(docs): correct typos in multiple files (#22173)
Corrects spelling errors across three documentation files. - `indivual` -> `individual` in `./CLAUDE.md` - `accesible` -> `accessible` in `./packages/twenty-docs/user-guide/getting-started/how-tos/navigate-around-twenty.mdx` - `editting` -> `editing` in `packages/twenty-docs/user-guide/views-pipelines/overview.mdx` <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22173?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> |
||
|
|
87329c8810 |
fix(ask-ai): resolve stream subscription race condition on new thread… (#21916)
## Description Resolves a race condition in the Ask AI feature where the first assistant reply in a newly created thread does not stream into the UI and only appears after sending a second message. ### What's Changed - **Immediate Thread Subscription:** Updated `useAgentChat.ts` to immediately set `currentAiChatThread` to the newly generated `threadId` instead of deferring it until after the `SEND_CHAT_MESSAGE` mutation finishes. - **The Bug:** Previously, the backend worker processed the AI chat job so quickly that the stream completed and fired the `message-persisted` event *before* the frontend established the SSE subscription. - **The Fix:** By setting the thread ID immediately, the `useAgentChatSubscription` hook now properly connects and listens to the SSE stream before the backend begins emitting chunks, guaranteeing the first message streams seamlessly. ### How to Test 1. Open the Ask AI panel and start a completely new thread. 2. Send an initial message (e.g., "Hello!"). 3. Observe that the AI's response successfully streams into the chat without needing a workaround or page refresh. Closes #21694 --------- Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> |
||
|
|
c3183f7828 |
Forward parent commits to Argos visual regression dispatch (#22174)
Part of the Argos orphan-build fix. The dispatch now lists the merge-base plus its ancestors (up to 100) and forwards them as `parent_commits`, so the self-hosted Argos can walk back to the nearest commit with a reference build instead of orphaning when the exact merge-base lacks one. Companion to twentyhq/twenty-argos#11 (deploy that first) and the ci-privileged change that passes the input through to build creation. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22174?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. --> |
||
|
|
4f429565e1 |
call recorder polishes (#22170)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22170?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. --> |
||
|
|
864ea452b4 |
fix mobile side panel close (#22169)
The side panel close (X) button was hidden on all mobile views, while the back button only renders when there is navigation history. When the side panel is opened at the root (e.g. viewing a record directly with a single-item navigation stack), neither button was shown, leaving no way to dismiss the panel on mobile. Keep the close button available on mobile whenever there is no back button to fall back on, so the panel is always dismissable. ## Before https://github.com/user-attachments/assets/61891d25-26b8-4ba4-8b05-73fd44f92d89 ## After https://github.com/user-attachments/assets/41342722-bcaf-420b-83bb-3cafaec49516 |
||
|
|
d8cb4aa15b |
fix(filter): resolve filter-derived date defaults with Temporal (fixes create/update crash in date-filtered views) (#22124)
## Symptom
Creating or updating an Opportunity on a board view crashes with:
```
Uncaught TypeError: e.split is not a function
at splitDateString (date-fns) → parseISO → isMatchingDateFilter
→ isRecordMatchingFilter → <group-by optimistic effect> → createOneRecord
```
`e` is a non-string (a `Date` object, shown as `{}` in the debugger).
Distinct from the `null` case fixed in #22029.
## Root cause
Reproduced on a live board filtered by **Close date — Is relative —
"This quarter"** (a `DATE_TIME` field).
When you create a record in a filtered view, `useCreateNewIndexRecord`
derives default field values from the view's filters via
`buildRecordInputFromFilter` → `buildValueFromFilter`. For a date field,
`computeValueFromFilterDate` returned a JS **`Date` object** (`new
Date()` / `new Date(value)`), assigned to the new record verbatim. The
optimistic record's `closeDate` was then a `Date`, not an ISO string,
and the group-by optimistic effect matched it via `parseISO(dateObject)`
→ `dateString.split is not a function`, crashing every create/update in
the view.
## Fix
`computeValueFromFilterDate` now returns timezone-aware **ISO strings**
via Temporal, mirroring what `turnRecordFilterIntoGqlOperationFilter`
produces for the same filters — so a record created in a date-filtered
view actually satisfies its own filter:
- **`DATE_TIME`** → an instant. Date-only filter values (a `DATE_TIME`
"is" filter stores `yyyy-MM-dd`, no time) are resolved to the start of
day in the user's time zone, matching how the filter operands are built
— `Temporal.Instant.from()` alone would `RangeError` on them.
- **`DATE`** → a plain date `yyyy-MM-dd` resolved in the **user's time
zone** (`Temporal.Now.plainDateISO(timeZone)`). A bare `new
Date().toISOString()` would use the UTC date, which near midnight is the
wrong calendar day for non-UTC users, so the new record could miss its
own `IS_TODAY`/relative filter. The time zone is threaded from
`useUserTimezone` (same source the filter side uses).
- `IS_BEFORE` subtracts 1 day for `DATE` / 1 minute for `DATE_TIME` (the
`-1 minute` special-case moved out of `buildRecordInputFromFilter`,
which now just assigns the string).
No `Date` object ever reaches `parseISO`, and the value matches the
filter operand, so the optimistic card lands in the right place.
## Tests
- `buildValueFromFilter.spec.ts`: every date operator returns an ISO
**string** (round-tripped to the expected instant); a `DATE` block
asserts date-only `yyyy-MM-dd` output and that `IS_TODAY` resolves to
the correct calendar day **per time zone** at a UTC day boundary
(`2024-03-20` UTC vs `2024-03-21` Asia/Tokyo); a date-only `DATE_TIME`
`IS` value resolves to start-of-day in the user time zone (UTC vs
America/New_York) without throwing.
- `buildRecordInputFromFilter.test.ts`: filter-derived date values are
ISO strings, not `Date` objects.
## Verified locally (Chrome)
Reproduced the exact prod scenario on the **By Stage Opportunities
Kanban board** (the group-by optimistic effect) with a **Close date — Is
relative** filter:
- Created a card in a column → **no `split is not a function` crash**;
the card got a valid Close date (`now` for the relative filter, e.g. `25
Jun 2026 13:10`). Console clean.
- Also verified a **table view + Close date — Is** filter: created
record gets a valid start-of-day value (`25 Jun 2026 00:00`).
Both `IS_RELATIVE` (→ now instant) and the date-only `IS` (→
start-of-day in the user tz) paths produce string values that the
optimistic matcher handles without throwing.
|
||
|
|
fe1a8ad5f0 |
fix(ci): patch danger to decline gzip, fixing ERR_STREAM_PREMATURE_CLOSE on Node 24 (#22171)
## Problem The `danger-js` check (`twenty-utils:danger:ci`) started failing intermittently with: ``` FetchError: Invalid response body while trying to fetch https://api.github.com/repos/twentyhq/twenty/pulls/<n>/files: Premature close errno: 'ERR_STREAM_PREMATURE_CLOSE' ``` It fails before the Dangerfile even runs, while fetching PR files / diff / commits. The existing retry wrapper ([#22151](https://github.com/twentyhq/twenty/pull/22151)) reduced it but can't absorb longer GitHub-API windows, so checks still go red. ## Root cause Not "node-fetch is old" generically — a specific recent regression: - Node **22.23.0 / 24.17.0** shipped a security fix for CVE-2026-48931 (http.Agent response-queue poisoning) that attaches a `'data'` listener to idle keep-alive sockets. - `node-fetch@2` misreads that listener as an unclean connection close — but only on **gzip-encoded responses without `Content-Length`**, which is exactly what `api.github.com` returns. - The GitHub-hosted runners rolling into the patched Node 24.17.x in recent weeks is why this surfaced now. See [danger/danger-js#1515](https://github.com/danger/danger-js/issues/1515), [nodejs/node#63989](https://github.com/nodejs/node/issues/63989). ## Why this approach - `node-fetch@2` can't be removed downstream — Danger imports it directly, and it's pervasive transitively (gaxios/googleapis). Dropping it is an upstream migration. - We don't want to pin an old Node version. So: bump `danger` 13.0.4 → 13.0.8 and backport [danger/danger-js#1516](https://github.com/danger/danger-js/pull/1516) via a yarn patch — set `compress: false` on Danger's shared `api()` wrapper. GitHub then returns identity-encoded responses with `Content-Length`, and node-fetch's faulty premature-close detector never fires. Negligible bandwidth cost on these small JSON payloads; explicit caller overrides are preserved via an `=== undefined` guard. ## Changes - `packages/twenty-utils/package.json` — `danger` → patched 13.0.8 - `yarn.lock` — registers the `danger@patch:` resolution - `.yarn/patches/danger-npm-13.0.8-48aba2788c.patch` — the `compress: false` fix ## Verification - Patch dry-run applies cleanly against pristine danger 13.0.8 source. - Inspected yarn's materialized patched cache package — the `compress` fix is present in the linked `distribution/api/fetch.js`. - Confirmed the failing calls (`getPullRequestInfo` / `getPullRequestCommits` / `getPullRequestDiff`) all route through `this.api` → the patched wrapper. ## Lifecycle Temporary backport. When #1516 ships in a Danger release, drop the patch and bump to that version (flagged in a comment inside the patch). The existing CI retry wrapper stays as defense-in-depth. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22171?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. --> |
||
|
|
2aeacf341f |
Flatten system object pickers (#22161)
## Summary - Flatten system object entries into the first-level workflow object pickers. - Flatten dashboard Source and record-page Field picker advanced/system entries into the main searchable list. - Keep regular entries first, place system/advanced entries at the bottom, and cap page-layout picker height at 340px. - Add keyboard selection support to workflow object pickers through `SelectableList`. ## Review notes - Removed now-unused Advanced submenu state, submenu headers, and duplicated filtering paths. - Kept the width behavior scoped to each existing dropdown; the shared page-layout wrapper only controls height/scrolling. - No blocking issues found in the final reviewed diff. ## Screenshots ### Workflow record type picker | Before | After | | --- | --- | | <img src="https://gist.githubusercontent.com/Bonapara/d2b6754b3f1927b3d755b5f260e8b12f/raw/dc3f7ef933bdca6c39599c45c827db0f27f01e7d/before-workflow-record-type-advanced.png" width="320" /> | <img src="https://gist.githubusercontent.com/Bonapara/d2b6754b3f1927b3d755b5f260e8b12f/raw/2ee195a8562e13d0f4307e7891ab60cfaecae8cf/after-workflow-record-type-flat.png" width="320" /> | ### Dashboard Source picker | Before | After | | --- | --- | | <img src="https://gist.githubusercontent.com/Bonapara/d2b6754b3f1927b3d755b5f260e8b12f/raw/45f57728ec7643c7048e857829e977d49b9b365c/before-dashboard-source-tall.png" width="420" /> | <img src="https://gist.githubusercontent.com/Bonapara/d2b6754b3f1927b3d755b5f260e8b12f/raw/f8b864ea5e3d04a00b9b6a613e8bf97dd545933c/after-dashboard-source-340px.png" width="420" /> | ### Record page Field picker | Before | After | | --- | --- | | <img src="https://gist.githubusercontent.com/Bonapara/d2b6754b3f1927b3d755b5f260e8b12f/raw/ea5f14ccac6c0d9b3d953b4047dc7491702f756a/before-record-field-advanced.png" width="320" /> | <img src="https://gist.githubusercontent.com/Bonapara/d2b6754b3f1927b3d755b5f260e8b12f/raw/9d0717cf3cfcb47ed17d595ba83b19a37718c3eb/after-record-field-flat.png" width="320" /> | ## Checks - `npx oxfmt --check` on touched files - `git diff --check` - `npx tsc -p tsconfig.json --noEmit --pretty false --noErrorTruncation | rg "(ChartDataSourceDropdownContent|FieldWidgetFieldDropdownContent|PageLayoutDropdownContentContainer|WorkflowObjectDropdownContent|WorkflowEditTriggerDatabaseEventForm|WorkflowEditActionFindRecords|WorkflowEditActionPickRecord|ChartSettingItem)"` returned no touched-file diagnostics - Browser verification: dashboard Source top/bottom, record-page Field top/bottom, workflow Record Type top/bottom, and ArrowDown selection in workflow Record Type menu <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22161?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. --> |
||
|
|
db5338cf32 |
Feat: group records by many to one relation (#22123)
## Group records by relation (Kanban + Table)
Adds grouping by `MANY_TO_ONE` relation fields on both board and table
views, reusing the existing `ViewGroup` storage (`fieldValue = related
record id`).
- **New group** record picker to create relation-backed groups (board
column + table row)
- Relation-aware group headers (name/avatar), filtering, and drag-drop
(writes the FK join column) — all using one canonical `${name}Id` column
- Sort menu hides alphabetical options when grouping by a relation (no
comparable title)
- A group whose backing record no longer exists renders a "Deleted" chip
instead of a blank header
- **Backend:** allow `MANY_TO_ONE` relations as the Kanban
`mainGroupByField` in the flat-view validator
https://github.com/user-attachments/assets/267077a6-2667-4506-b178-eee420a16f20
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22123?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. -->
|
||
|
|
108cc5b6a5 |
API key creation triggers unnecessary ORMEntityMetadatas cache recomputation (#22168)
## Context
Creating an API key only changes role/apiKey-related data, but the
workspace migration runner was also invalidating and recomputing the
metadata caches (`ORMEntityMetadatas` and `graphQLResolverNameMap`) on
it.
The root cause is a single `if` block in
`getLegacyCacheInvalidationPromises` that gated **all** caches with an
`||` condition combining the metadata and role/permission conditions:
```ts
if (
shouldIncrementMetadataGraphqlSchemaVersion ||
shouldInvalidateRoleMapCache ||
shouldInvalidateRolesPermissionsCache
) {
// recomputes role caches AND ORMEntityMetadatas + graphQLResolverNameMap
}
```
So any role-only change (such as API key creation, which sets
`shouldInvalidateRoleMapCache`) also recomputed `ORMEntityMetadatas` —
an expensive recomputation that does not depend on role data.
## Fix
Split the combined block into two independent blocks, each gated by its
own condition:
- `shouldIncrementMetadataGraphqlSchemaVersion` → invalidate/recompute
only the metadata-derived caches: `ORMEntityMetadatas` and
`graphQLResolverNameMap`
- `shouldInvalidateRoleMapCache ||
shouldInvalidateRolesPermissionsCache` → invalidate/recompute only the
role/permissions caches
`graphQLResolverNameMap` is built from `flatObjectMetadataMaps` (see
`WorkspaceResolverNameMapCacheService`), so it is grouped with
`ORMEntityMetadatas` in the metadata block rather than the role block.
## Result
- Role-only changes (e.g. API key creation) no longer trigger
unnecessary `ORMEntityMetadatas` / `graphQLResolverNameMap`
recomputation.
- Metadata-only changes no longer recompute the role/permissions caches.
|
||
|
|
19bbd59b53 |
Support CSS imports in front-components via runtime style injection (#22150)
Front-components compile to a remote-dom worker, so a CSS import like `import 'twenty-ui/style.css'` can't load a stylesheet and was breaking the build at the manifest step. This makes the build inline an imported CSS file as a runtime `<style>` injection that flows through the existing style bridge into the host. Because the CSS is bundled alongside that same build's hashed class names, an app's styling matches its own twenty-ui version regardless of which version the host ships — no server, manifest, or host changes needed. The manifest extractor keeps the no-op CSS loader (it executes the bundle in Node, where `document` is undefined); the inject plugin runs only in the real build and the dev watcher. |
||
|
|
2af5370749 |
v1.1.16 — Restore Partner slug in side panel and Notes tab (#22165)
## Summary **Package version:** `1.1.16` - Adds **slug** to the Partner record-page `FIELDS_WIDGET` view so it appears in the side panel for admins and partners (partners remain update-locked on slug via `partner.role.ts`). - Restores the **Notes** tab on the custom Partner `RECORD_PAGE` layout — the marketplace v2 layout replaced the platform default but only included Home + Timeline. ## Test plan - [ ] `yarn lint` in `packages/twenty-apps/internal/twenty-partners` — 0 errors - [ ] `yarn twenty dev --once` on a local partners workspace — sync succeeds - [ ] Admin: open a Partner record full page → slug visible under Name in side panel; **Notes** tab present and can create a linked note - [ ] Partner role (My Profile): slug visible, not editable; Notes tab works - [ ] After merge: `deploy` + `install` on prod partners workspace <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22165?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. --> |
||
|
|
1284099940 |
i18n - website translations (#22162)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22162?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> |
||
|
|
981ee6a2d7 |
fix: stabilize tooltip anchor ID with useRef to prevent hover glitch (#21888)
## Context Tooltip components were generating a new anchor ID on every render. This caused a flicker/glitch when hovering: the tooltip would briefly disappear and reappear because React reconciled the changed ID as a different element. ## Solution Changed the anchor ID generation from inline (re-created every render) to `useRef` (stable across renders). The ID is now created once on mount and stays the same for the lifetime of the component. ## Test plan - [x] Hover over any element with a tooltip — no flicker or disappear/reappear behavior - [x] Multiple tooltips on the same page still work independently 🤖 Generated with [Claude Code](https://claude.ai/claude-code) <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21888?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: Emmanuel Hernandez <emmanuel.hernandez@clickbalance.com> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
ee71a382de |
IMAP support non RFC compliant servers (#22153)
Some non complaint IMAP server don't send `UIDNEXT` UIDNEXT is the next message id you subtract with 1 to get total current messages This does a fallback to searching all UIDs and taking the highest <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22153?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. --> |
||
|
|
a87bc4bed6 |
i18n - website translations (#22160)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22160?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> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
a20ebfa880 |
feat(applications): remove the application custom settings tab (#22156)
## Summary Removes the application **custom settings tab** feature. This is one half of #22059, split out so it can be reviewed/merged independently from the variable-types enrichment. ## Changes - Remove the `SettingsApplicationCustomTab` component and its tab entry/rendering in `SettingsApplicationDetails`. - Stop syncing `settingsCustomTabFrontComponent` from application manifests — `ApplicationManifestMigrationService` now only syncs the default role. - Deprecate the now-unused fields (kept for backward compatibility, no longer read or synced): - `ApplicationDTO.settingsCustomTabFrontComponentId` (GraphQL `@deprecated`) - `ApplicationManifest.settingsCustomTabFrontComponentUniversalIdentifier` - the `settingsCustomTabFrontComponentId` column comment on `ApplicationEntity` The DB column is intentionally **not dropped**, so existing installations upgrade cleanly. |
||
|
|
5a657129f0 |
Split website stepper into home-stepper and product-stepper sections (#22149)
Reorganizes the flat `sections/stepper/` (30 files — two distinct steppers plus shared code) into two product-feature-style sections, and moves the shared code to the shared layers. - **`sections/home-stepper/`** — the home-page stepper. Renamed `Stepper` → `HomeStepper` (and the home components → `HomeStepperLottie` / `HomeStepperSteps` / `HomeStepperVisualFrame`) for symmetry with `ProductStepper`. Shell at the root + `components/`/`data/`/`utils/` + barrel. - **`sections/product-stepper/`** — the product-page stepper, same structure. The 3 files both steppers shared can't live in a shared *section* — `check-conventions` forbids a section importing another section. So they moved to the shared layers: - `StepperProgressRail`, `StepperSwipeDeck` → `ui/` - `useBreakpointStepSync` → `platform/motion` Both consumer pages repointed (`@/sections/home-stepper`, `@/sections/product-stepper`); the row-gap allowlist in `check-conventions.mjs` updated to the new paths; explanatory comments stripped across the moved files (CSS-in-template comments and `'use client'` kept). Pure reorganization — no behavior change. typecheck + lint + build all green. |
||
|
|
904d3d6bff |
i18n - website translations (#22158)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22158?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> |
||
|
|
551a792291 |
Rework website TasksVisual to match twenty-front tasks tab (#22143)
- Rework `TasksVisual` to match twenty-front's tasks tab: per-group bordered cards with row dividers, `TODO`/`DONE` headers, the `+ Add task` button, and twenty-front's checkbox, row, and target-chip styling. - Enable the checkbox to toggle a task between `TODO` and `DONE`. Marketing data kept intuitive (single assignee, full dates, action-led titles). <img width="859" height="608" alt="image" src="https://github.com/user-attachments/assets/085c225f-b121-44e6-aaec-553c02fd2367" /> |
||
|
|
d6556221d9 |
Tidy website ImportVisual markup and CSS (#22148)
Markup/CSS tidy-up of `ImportVisual` — no visual change. - Collapse the redundant `Grid` wrapper into `Root` (now `flex-direction: column`) and drop the no-op `justify-content: center` (the child was already full-width). - Hoist the duplicated `font-size: previewFontSize(md)` to the mapping `Row` (the `sm` example keeps its override). <img width="574" height="604" alt="image" src="https://github.com/user-attachments/assets/dcc72567-5c13-4d54-884a-f3120cc5a345" /> |
||
|
|
29e0327063 |
fix(server): allow moving menu items into a folder created in the same sync (#22130)
## Context Fixes [core-team-issues#2593](https://github.com/twentyhq/core-team-issues/issues/2593). When reorganizing navigation menu items by moving existing items into a **newly created folder** within a single deploy, the sync failed with `Parent navigation menu item not found`, forcing a two-step deploy (create the folder first, then move the items into it). ## Root cause Migration entities are validated in the fixed order **delete → update → create** (`workspace-entity-migration-builder.service.ts`). When items are moved into a new folder in one sync, the items are *updated* (adding `folderUniversalIdentifier`) while the folder is *created* — but the update phase runs before the create phase, so the folder isn't yet in the optimistic maps. The **creation** validator already handles "parent doesn't exist yet" by also checking `remainingFlatEntityMapsToValidate`. The **update** validator couldn't: `FlatEntityUpdateValidationArgs` explicitly omitted that field, so it only looked at the optimistic maps and threw. ## Changes - `universal-flat-entity-update-validation-args.type.ts` — stop omitting `remainingFlatEntityMapsToValidate` from the update args. - `workspace-entity-migration-builder.service.ts` — pass `createdFlatEntityMaps` (entities being created in the same migration) into update validation. - `flat-navigation-menu-item-validator.service.ts` — resolve the parent folder against both the optimistic maps and the to-be-created entities, mirroring the creation validator. - Integration test — sync an item, then in a second sync create a folder and move the item into it, asserting it succeeds in a single deploy. The change is generic and type-safe: all other update validators receive the new field and simply ignore it. `createdFlatEntityMaps` is `MetadataUniversalFlatEntityMaps<T>`, matching the field's type. ## Test plan - [x] Added integration test `should move existing menu items into a folder created in the same sync` - [ ] CI green https://claude.ai/code/session_017pmBkho9Fh6Vjv8WA4m9YE --- _Generated by [Claude Code](https://claude.ai/code/session_017pmBkho9Fh6Vjv8WA4m9YE)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22130?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. --> |
||
|
|
491b9e954a |
Rework website EmailsVisual to match twenty-front emails tab (#22146)
- Rework `EmailsVisual` to match twenty-front's emails tab: bordered thread card with dividers, `Inbox` header sizing, and twenty-front's row/sender/date colors and hard-clipped sender names (kept `+ Compose`). - Replace the "not shared" row with four fully-populated shared threads (subjects/bodies sourced from twenty-server's dev seed), two participants each, full `MMM D, YYYY` dates. <img width="575" height="607" alt="image" src="https://github.com/user-attachments/assets/68d1fd6d-e030-4f3c-b775-687f7e1ab173" /> |
||
|
|
cdd1aba3e0 |
i18n - docs translations (#22154)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22154?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> |
||
|
|
f416f81548 |
fix(ci): retry Danger.js on transient GitHub API fetch errors (#22151)
## Problem The `danger-js` job in **CI Utils** has been failing across most PRs. The failure is not a real Danger violation — it's a transient network error fetching the PR diff/commits from the GitHub API: ``` Failed to fetch GitHub pull request files: FetchError: Invalid response body while trying to fetch https://api.github.com/repos/twentyhq/twenty/pulls/XXXXX/files?page=1&per_page=100: Premature close at Gunzip.<anonymous> (.../node_modules/node-fetch/lib/index.js:400:12) errno: 'ERR_STREAM_PREMATURE_CLOSE', code: 'ERR_STREAM_PREMATURE_CLOSE' ``` GitHub closes the gzipped HTTP response mid-stream, and Danger's bundled `node-fetch` has **no retry** on a dropped connection — so any single blip fails the whole check. ## Why is this happening now? Nothing on our side changed at the boundary where failures started. The Node 24.16 bump landed Jun 8 (the job stayed green for 2+ weeks after), Danger has been pinned at `13.0.4` for months, and there are **zero commits** to `.nvmrc`, `twenty-utils/package.json`, or the `yarn-install` action since Jun 22. What changed is GitHub's API reset rate, and it changed abruptly: | Day | Failures | Successes | Failure rate | |-----|----------|-----------|--------------| | Jun 23 | 1 | 48 | ~2% (green) | | Jun 24 | 38 | 204 | ~16% | | Jun 25 | 23 | 25 | **~48%** | The same PR passes on one run and fails on the next (e.g. one PR shows up as both pass and fail; another failed 3 runs in a row) — a code bug can't flip outcomes on identical input, only an infrastructure flake can. When GitHub's connection-reset rate was ~0% we never noticed; now that it's in the tens of percent, roughly half of all PRs trip it. ## Fix Wrap the Danger invocation in a small retry loop (3 attempts, 5s backoff) so the check absorbs these transient fetch errors instead of red-flagging the PR. Applied to both the `danger-js` and `congratulate` jobs since they share the same failure mode. This is the correct mitigation rather than a code revert — there's no change on our side to revert. 3 attempts drop a ~48% single-shot failure rate to ~11%, and a less-degraded ~16% rate to well under 1%. If GitHub's reliability recovers, the retries simply stop firing and cost nothing. This is a CI-only change — no application code is touched. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22151?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-light.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
6a36fc3ba8 |
fix(sdk): evict compiled manifest modules from require cache in dev mode (#22129)
## Problem
`yarn twenty dev` crashes after running for a while with:
```
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
```
The crash happens during the **Manifest Build** phase, after the process
has been up for a long time (~35 min in the reported case) — the
signature of a slow memory leak in the long-running watch process, not a
single oversized operation.
Fixes the issue described in `core-team-issues#2560`.
## Root cause
`manifest-extract-config-from-file.ts``loadModule()` compiles each
entity file with esbuild (`bundle: true`, so the whole dependency graph
is inlined), writes it to a unique `mkdtemp` directory, and loads it
with `appRequire(tempFile)`.
`createRequire` shares Node's global module cache (`Module._cache`), so
**every loaded temp module is retained in the require cache forever**.
Two things made this unbounded:
1. `mkdtemp` generates a fresh random directory each call, so cache keys
never collide — entries purely accumulate.
2. The `finally` block removed the temp dir **from disk** but never
evicted the `require.cache` entry, so the evaluated, fully-bundled
module object stayed pinned in the JS heap.
In dev mode the orchestrator re-runs `buildManifest` on **every** file
change and watcher rebuild (`scheduleSync()`), and each rebuild compiles
& `require()`s ~one module per entity file (53 in the reported case).
Over a session of editing, thousands of large bundled module objects
pile up in `require.cache` until V8's heap is exhausted → FATAL OOM.
## Fix
Delete the temp module from the require cache after loading it, so
memory stays bounded to a single rebuild. Because `bundle: true` inlines
the whole graph, the temp module is the only cache entry per load, so
deleting it lets the bundled object be GC'd.
```ts
} finally {
delete appRequire.cache[tempFile];
await remove(tempDir);
}
```
## Test
Adds a regression test (`manifest-extract-config-from-file.spec.ts`)
that runs `extractManifestFromFile` repeatedly and asserts no
`twenty-manifest` temp modules accumulate in the require cache.
https://claude.ai/code/session_0168hN9yEYvjZbqdz7PckKn8
---
_Generated by [Claude
Code](https://claude.ai/code/session_0168hN9yEYvjZbqdz7PckKn8)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22129?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. -->
|
||
|
|
db687c7407 |
chore(call-recorder): drop FAILED status schema bridge (#22134)
Cleanup of the call-recorder data layer + the SDK 2.16 bump and its fallout, now that the `FAILED_UNKNOWN → FAILED` rename shipped in `twenty/v2.16.0` (#22062). - **Drop the schema bridge.** `executeCurrentSchemaMutation` and the integration-test compatibility filter existed only to work around servers exposing `FAILED_UNKNOWN`. Deleted the bridge; `updateCallRecording` / `completeCallRecordingIngestion` call `client.mutation(...)` directly. Integration test iterates all `CallRecordingStatus` values. - **Bump SDK** `twenty-sdk` / `twenty-client-sdk` to `2.16.0`. - **One export per file.** Move `CallRecordingUpdateFields` to its own type file; extract the duplicated media-file shape into `CallRecordingMediaFile`. - **Migrate the Recall webhook to `serverRouteTriggerSettings`** (2.16 dropped `serverWebhookTriggerSettings` + its declarative `workspaceIdResolver`). The webhook is now a **resolver** (`recall-webhook`) that verifies the Svix signature, reads `twentyWorkspaceId` from the Recall bot metadata, and returns `{ workspaceId, targetLogicFunctionUniversalIdentifier, payload }`; the platform dispatches to a new **target** function (`process-recall-webhook`) in the resolved workspace, where `CoreApiClient` is workspace-scoped. Resolver UID/route unchanged, so the registered Recall endpoint URL stays valid. Failures now throw → HTTP 500 (Svix retries) instead of returning 401/400. Verified: typecheck, 213 unit tests, oxlint, oxfmt all green. **Not yet verified end-to-end against a live server** — call-recorder is the first app on `serverRouteTriggerSettings`, so a real Recall webhook should be tested through the resolver→target path before relying on it. |
||
|
|
dc371ef6e7 |
rename sync-completion methods to avoid confusion with stage setters (#22138)
`markAsCompletedAndMarkAsCalendarEventListFetchPending` was just `markAsCalendarEventListFetchPending` with a prefix, so dropping the prefix silently turned a sync-completion into a plain stage reset Renamed to markAsCalendarEventSyncCompleted / markAsMessageSyncCompleted so they no longer share a tail with the stage setters. Mirrors the existing markAsFailed naming. No behavior change. Sanity check: replayed the original #22015 diff through two isolated review agents, identical prompt, only the names differing. With the old names the reviewer explicitly cleared the branch as safe; with the new names it flagged the missing completion as high severity. The rename makes the mistake visible. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22138?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. --> |
||
|
|
5242ddf458 |
feat(apps): let front components open a record in the side panel (#22140)
## Why Front components (apps) could `navigate()` to a record's **full page**, but there was no way to open a specific record in the **side panel**. More generally, `openSidePanelPage` could navigate to a `SidePanelPages` enum page but couldn't pass the context most pages need. ## What `openSidePanelPage`'s params are now a **discriminated union keyed on `page`**, so each page declares its own typed payload (instead of a flat bag of optionals whose validity silently depends on `page`). This is also safer: pages that can't render without context can't be "opened" into a broken panel. Wired the param-bearing pages host-side, each bridging to its existing internal hook: | `page` | Params | Bridges to | |---|---|---| | `ViewRecord` | `recordId`, `objectNameSingular`, `resetNavigationStack?` | `useOpenRecordInSidePanel` (full-page fallback on mobile / unsupported objects) | | `EditRichText` | `recordId`, `objectNameSingular`, `fieldName?` | `useOpenRichTextInSidePanel` | | `ComposeEmail` | `connectedAccountId`, `threadId?`, `defaultTo?`, `defaultSubject?`, `defaultInReplyTo?`, `pageTitle?`, `pageIcon?` | `useOpenComposeEmailInSidePanel` | | `ViewFrontComponent` | `frontComponentId`, optional `recordId`+`objectNameSingular`, `pageTitle`, `pageIcon?`, `resetNavigationStack?` | `useOpenFrontComponentInSidePanel` | | *(any other page)* | `pageTitle`, `pageIcon?`, `shouldResetSearchState?` | `navigateSidePanel` | `CommandOpenSidePanelPage` now takes the union directly, so headless command-menu items can open any of these. Threaded through `twenty-sdk` → `twenty-front-component-renderer` → host (`useFrontComponentExecutionContext`), with unit tests per page and the mobile/unsupported fallbacks. ## Deliberately deferred: `MergeRecords` `useOpenMergeRecordsPageInSidePanel` takes `objectNameSingular` / `objectRecordIds` at **hook-init** (it calls `useObjectMetadataItem` / `useLazyFindManyRecords` at render), so it can't be driven by runtime app params without refactoring that hook + its current caller. Left out of this PR — better as its own change. ## Worth a second look (reviewers) - **`ViewFrontComponent`** lets an app open a front component by id. Within an app that's clean composition; whether an app should be able to target *another* app's component is a scoping/security question. The render still runs under the app's access token, so cross-app fetches would fail auth — but flagging it explicitly. ## Security note Side-panel record/page views render natively under the **user's** session/Apollo client, not the app's scoped token — RLS/field permissions are enforced as if the user opened it themselves. Same trust model as `navigate(AppPath.RecordShowPage, …)`. ## Follow-up A separate PR will centralize the mobile + `canOpenObjectInSidePanel` guard inside `useOpenRecordInSidePanel` (currently duplicated across callers, missing in others). ## Validation > [!NOTE] > Dependencies wouldn't install in this environment (flaky network during `yarn install`), so lint / typecheck / jest weren't run locally — relying on CI. The diff was reviewed manually for type-consistency, including the discriminated-union narrowing in the host switch. https://claude.ai/code/session_01AAJFXzsCeoj6BeP3ofiTKQ |
||
|
|
885effb3d8 |
fix(calendar): mark channel completed on no-events fetch (#22137)
No-events branch left channels stuck on syncStatus=ONGOING and stopped bumping the active metric (flatlined dashboard), since markAsCalendarEventListFetchPending only resets the stage. Switched it to markAsCompletedAndMarkAsCalendarEventListFetchPending so status, syncedAt, throttle and the metric reset properly, matching the messaging side. Regression from #22015. |
||
|
|
20f8321dca |
Rework website FilesVisual to match twenty-front files tab (#22147)
- Rework `FilesVisual` to match twenty-front's files tab: bordered file card with dividers, `All` header sizing, and twenty-front's row background and date color (kept the bordered `+ Add file`). - Four files, one per category (pdf/xlsx/pptx/png), with full `MMM D, YYYY` dates. File-icon colors already matched `useFileIconColors`. |
||
|
|
fa008b4728 |
chore(create-twenty-app): align scaffold with unified app test/lint/typecheck configs (#22128)
## What We have unified the unit + integration test, lint, and typecheck configuration across `packages/twenty-apps/internal` and `packages/twenty-apps/public`. This PR updates the `create-twenty-app` scaffolded project template so a freshly generated app complies with that same setup. ### Scaffold template changes (`packages/create-twenty-app/src/constants/template/`) - **`package.json`**: add `typecheck` (`tsgo --noEmit -p tsconfig.spec.json`) and `test:unit` scripts; add `@typescript/native-preview` + `vite-tsconfig-paths` devDeps; bump `vitest` → `^4.0.0` and `packageManager` → `yarn@4.13.0`. - **`vitest.config.ts`**: resolve path aliases via the `vite-tsconfig-paths` plugin (matching the unified apps) instead of the ad-hoc `resolve.tsconfigPaths`. - **`vitest.unit.config.ts`** (new): unit test config targeting `src/**/*.test.ts`, identical to existing apps. - **`tsconfig.json`**: `moduleResolution: node → bundler`, `target: es2018 → es2020`. - **`src/__tests__/application-config.test.ts`** (new): a sample unit test so a fresh scaffold's `test:unit` passes out of the box. - **`.github/workflows/ci.yml`**: run Lint → Typecheck → Unit tests alongside the integration tests, mirroring the central `ci-twenty-apps.yaml` pipeline. - **`README.md`**: document the new `lint` / `typecheck` / `test:unit` commands. ### CLI package changes - **`tsconfig.json` / `tsconfig.lib.json`**: exclude the new `template/vitest.unit.config.ts` from the CLI's own typecheck/build (just like the existing `vitest.config.ts` exclusion) — its `vitest` / `vite-tsconfig-paths` imports aren't deps of the CLI package. ## Notes - The template's committed `yarn.lock` is an intentional empty seed; the scaffold runs `yarn install` at creation time, so the new deps regenerate cleanly — no lockfile change needed. - The sample unit test asserts `typeof APP_DESCRIPTION === 'string'` (not truthiness) because the description is optional at scaffold time and can be empty. https://claude.ai/code/session_01XiJpkiQG3TiDjKncx2XSQq --- _Generated by [Claude Code](https://claude.ai/code/session_01XiJpkiQG3TiDjKncx2XSQq)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22128?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. --> |
||
|
|
f04db9751f |
fix(client-sdk): bundle metadata client into a single self-contained file (#22085)
## Problem A front component that imports `MetadataApiClient` from `twenty-client-sdk/metadata` crashes at render time: ``` FrontComponent error: Failed to resolve module specifier "./chunk-Dqa2HsxW.mjs". Invalid relative url or base scheme isn't hierarchical. ``` (hash differs per build). The equivalent component using `CoreApiClient` from `twenty-client-sdk/core` works fine. ## Root cause The front-component renderer loads each SDK client as a **single in-memory blob-URL module** and only rewrites the two bare specifiers it knows (`twenty-client-sdk/core`, `twenty-client-sdk/metadata`). A blob-URL module cannot resolve a **relative** `import … from "./chunk-*.mjs"` (blob URLs aren't hierarchical), and that chunk isn't served anyway. Only two entrypoints are externalized by the front-component build (`FRONT_COMPONENT_EXTERNAL_MODULES`) and thus served as blob modules: `core` and `metadata`. Everything else (`rest`, `generate`) is bundled into the component and is unaffected. Of those two: | client | how `dist/*.mjs` is produced | self-contained? | |---|---|---| | **core** | esbuild single-file bundle (`compileGeneratedClient`), re-run per workspace at server runtime by `replaceCoreClient` | ✅ | | **metadata** | the shared multi-entry Vite build, which hoists shared code into a relative `chunk-*.mjs` | ❌ | The metadata client is built once at package-build time (it is not workspace-specific) and was shipped straight from the multi-entry Vite output, keeping the unresolvable relative chunk import. ## Regression trace This was **not** broken on arrival — it regressed via a transitive bundler swap: | Date | Commit | Event | |---|---|---| | 2026-05-20 | `a26fe3bb65` | Metadata-client-in-front-components shipped; `twenty-client-sdk` on **Vite 7 (Rollup)** | | 2026-06-08 | `d2e7dc0e74` (#21309, *"security: bump vulnerable direct dependencies"*) | Bumped **Vite 7 → 8**, introducing **Rolldown 1.0.3** (no rolldown entries in the lockfile before this commit) | Vite 7 is Rollup-based; Vite 8 uses Rolldown. The breaking artifact is literally a `\0rolldown/runtime.js` shared chunk — a Rolldown construct that could not have existed before the bump. So the metadata front-component path worked from 2026-05-20 until the 2026-06-08 security dependency bump silently changed the bundler and split out the shared runtime chunk. ## Fix Build the metadata client as its **own single-entry Vite library** (`vite.metadata.config.ts`) so its output is a single self-contained file with no shared chunk. `core` / `rest` / `generate` stay in the main multi-entry build (`vite.config.ts`); shared config (`isExternal`, `entryFileNames`) is factored into `vite.shared.ts`. The build pipeline runs `vite build && vite build -c vite.metadata.config.ts`. The server picks this up automatically: `SdkClientGenerationService` ships the pre-built package `dist/` and only regenerates the **core** client; it never regenerates metadata. No server-side change required. ## Regression guard (e2e) The postcard example's `card.front-component.tsx` previously used `CoreApiClient` only, so this metadata-only regression had no e2e coverage. It now loads and round-trips all three SDK clients (`Core`, `Metadata`, `Rest`) via an SDK health panel, and the e2e asserts the blob-served `core` + `metadata` probes reach `ok` — which only happens if those bundles resolve and function. A future chunk-import regression in either blob module would crash the component on load and fail the test. ## Verification - `npx nx build twenty-client-sdk` succeeds. - `dist/metadata.mjs` / `dist/metadata.cjs`: **0** `chunk-*` imports, **0** relative imports; both load and export `MetadataApiClient` + `MetadataSchema`. - `dist/metadata/index.d.ts` types still emitted. - `npx nx typecheck` + `npx nx lint twenty-client-sdk` pass; postcard app typecheck + lint pass. ## Notes - `dist/` is not committed (CI builds it); a running server must rebuild `twenty-client-sdk` for the fix to take effect. - The e2e was validated statically (typecheck + lint); running it end-to-end requires a live stack with a seeded postcard record. |
||
|
|
cf91b87892 |
fix(server): skip defaultValue null check for relation/morph fields on update (#21875)
## Description
Updating any metadata property (e.g. `description`, `label`) of an
existing **non-nullable RELATION** field fails with:
```
INVALID_FIELD_INPUT: Default value cannot be null for non-nullable fields
```
A relation field has no literal `defaultValue` (it's always `null`), so
the update-path validator rejects every required relation. **Creating**
the same field is fine — only **updates** fail.
This also blocks any incremental app re-sync (`yarn twenty dev --once`)
whose diff touches a required relation field.
## Fix
Added a guard in
`FlatFieldMetadataValidatorService.validateFlatFieldMetadataUpdate()`
using the already-imported `isMorphOrRelationUniversalFlatFieldMetadata`
utility to skip the `defaultValue === null` check for relation/morph
field types:
```diff
if (
+ !isMorphOrRelationUniversalFlatFieldMetadata(
+ flatFieldMetadataToValidate,
+ ) &&
flatFieldMetadataToValidate.isNullable === false &&
flatFieldMetadataToValidate.defaultValue === null
) {
```
### Why this works:
- Relation fields represent foreign key relationships, not columns with
literal defaults
- The same guard is already used at line 144 in the same method for
relation-specific validation
- The create path (`validateFlatFieldMetadataCreation`) never had this
check, which is why creation always worked
- No new imports needed — `isMorphOrRelationUniversalFlatFieldMetadata`
is already imported on line 14
## Verification
- `npx nx build twenty-server` ✅ compiles successfully
Fixes #21751
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21875?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Co-authored-by: prastoin <paul@twenty.com>
|
||
|
|
feac2df216 |
chore(website): remove partners marketplace route lint guard (#22121)
## Summary Follow-up to #22120. Removes the `check-partners-marketplace-routes.mjs` lint guard and its `project.json` wiring — the profile fix is just `force-dynamic` on the page; the extra script is not needed. ## Changes - Delete `packages/twenty-website/scripts/check-partners-marketplace-routes.mjs` - Restore `project.json` lint command to run only `check-conventions.mjs` (as before #22120) ## Context The guard was added in #22120 but the removal commit did not land before merge. This PR cleans that up. No runtime behavior change. ## Test plan - [ ] `nx lint twenty-website` (or CI) passes without the removed script <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22121?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. --> |
||
|
|
b841b92cb5 |
fix(billing): widen embedded add-card modal and align its buttons (#22141)
## Context Follow-up to #22125. The embedded "Add your credit card" modal had two visual issues: 1. **Too narrow** — it used `narrowWidth` (~368px), which capped the Stripe Payment Element too tightly (cramped card fields, country dropdown, and the Google Pay / Cash App / bank-account accordions). 2. **Mismatched buttons** — the submit used `MainButton` (onboarding's filled CTA) while Cancel used `Button` (the settings/`ConfirmationModal` outlined style), so the two never matched. No new components or behavior — purely prop/component swaps within existing primitives. ## Changes - **`AddCreditCardModal`**: drop `narrowWidth`, use `size="medium"` so the Payment Element has room. - **`AddPaymentMethodForm`**: replace `MainButton` with `Button` (`variant="secondary"`, `accent="blue"`, `fullWidth`), using `Button`'s built-in `isLoading` spinner. Both buttons are now full-width outlined `Button`s, matching the sibling `StartSubscriptionConfirmationModal` (which renders `ConfirmationModal`'s blue-accented confirm + plain cancel). Removed the now-unused `MainButton`/`Loader` imports and `StyledButtonContainer`. ## Notes - Frontend-only; no GraphQL/schema/codegen impact. https://claude.ai/code/session_01VU7SfrSgaYWr2AhVL8DMfu --- _Generated by [Claude Code](https://claude.ai/code/session_01VU7SfrSgaYWr2AhVL8DMfu)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22141?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. --> |