fea2b8736fef4bbdfd095773ffab5e5e1351da45
10880 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
fea2b8736f |
feat(website): rebuild dashboard visual faithful to twenty-front (#22218)
Rebuilds the product-feature **DashboardVisual** to mirror
twenty-front's dashboard widgets, with colors traced to twenty-front's
actual source rather than eyeballed.
## Widgets
- **Bar — "Deals by month"**: single-series `blue8` (twenty-front's
`GRAPH_DEFAULT_COLOR`), dashed `4 4` gridlines, value labels, nice
rounded Y-ticks. Replaces the old stacked bar (stacked bars aren't used
in twenty-front).
- **Donut — "Deals by stage"**: the real opportunity pipeline
(New/Screening/Meeting/Proposal/Customer), each segment colored by that
stage option's own color from the metadata
(`red/purple/sky/turquoise/yellow`), with a center total and a paginated
horizontal legend.
- **KPIs**: big-number cards (Revenue YTD / Avg deal size / Win rate).
## Responsive — `mediaUp('md')`
The dashboard is the full-width spotlight tile, whose frame is short
below md and grows to 420px at md+. So the layout keys off md: below it
collapses to **2-up KPIs + a full-width bar** (donut and the 3rd KPI
hidden, smaller breadcrumb), and the spotlight frame's mobile min-height
is bumped so the bar has room; at md+ the full 3-KPI + side-by-side
layout returns. The donut caps at its size and shrinks with its
container.
## Notes
- Follows the `product-feature` conventions (per-visual folder,
one-export-per-file, split types).
- `Tiles.tsx`: one-line spotlight mobile min-height bump (only the
dashboard uses the spotlight tile).
- typecheck + lint + build all green.
<img width="1148" height="638" alt="image"
src="https://github.com/user-attachments/assets/1eb8f120-88c2-45d1-adf9-ec00abe11006"
/>
|
||
|
|
8a257e0de3 |
Update public app names (#22228)
- update names to comply with @twentyhq/ prefix standard - update versions - upgrade twenty sdks versions <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22228?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
e1120d38b6 |
fix: stamp MCP and AI Agent writes with FieldActorSource.AGENT (#22215)
## Description Fixes #21437 MCP and AI Agent writes now correctly stamped with ### Problem Records created through MCP server were stamped as `WORKFLOW`, making them indistinguishable from workflow-created records. This breaks loop-protection filters that skip workflow-originated records. ### Solution - MCP writes now correctly stamped with `createdBy.source = AGENT` - AI Agent execution now uses `AGENT` instead of `MANUAL` - Added `WorkspaceCacheModule` to MCP module - Updated tests to verify AGENT source ### Files Changed - `mcp.module.ts`: Added WorkspaceCacheModule import - `mcp-protocol.service.ts`: Set AGENT source in buildMcpToolSet - `mcp-protocol.service.spec.ts`: Updated tests - `agent-actor-context.service.ts`: Changed MANUAL → AGENT - ## Type of Change - [x] Bug fix (non-breaking change) ## Checklist - [x] Code follows project style - [x] Tests added/updated - [x] Issue linked Fixes #21437 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22215?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
c891258f34 |
Add v2 onboarding create profile page (#22221)
<img width="3024" height="1498" alt="CleanShot 2026-06-26 at 15 30 23@2x" src="https://github.com/user-attachments/assets/8b4863a9-66ed-4da1-851b-473cedf71511" /> <img width="3022" height="1500" alt="CleanShot 2026-06-26 at 15 29 43@2x" src="https://github.com/user-attachments/assets/22fc0e94-f670-4638-975c-f06b2b2e25e8" /> Adds the v2 onboarding **Create profile** page, shown right after the import-contacts step (`PROFILE_CREATION`) for the onboarding-v2 cohort. It renders full-screen under `BlankLayout` via the shared `OnboardingV2Layout`, matching the Figma (340px column, inline round avatar uploader + First/Last row, Job Title, dark Continue). The v1 modal flow is untouched and still used for non-v2 users. Job Title is wired end-to-end: it adds a real `jobTitle` field to the `WorkspaceMember` standard object (shared metadata constant + flat field metadata + entity property) and a `2-17` workspace upgrade command to backfill the field on existing workspaces. Continue persists name + jobTitle through the existing `updateWorkspaceMemberSettings` mutation, whose allow-list picks up the new standard field automatically. Routing mirrors `SyncEmailsV2`: new `AppPath.CreateProfileV2`, lazy route, and an `isOnboardingV2`-gated branch in `usePageChangeEffectNavigateLocation` (+ tests and a Storybook story). Reviewer notes: - `jobTitle` is **write-only** for now (no read-back path: core DTO/transpiler/fragment unchanged), and the field is `isSystem`/non-UI-editable to match its siblings. Easy to surface later if wanted. - New `OnboardingProfilePictureUploader` is a compact round avatar uploader reusing the same upload mutation flow as `WorkspaceMemberPictureUploader`. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22221?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
1bf0d06e18 |
Add author to twenty apps (#22226)
as title <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22226?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
7caf2a14c5 |
feat(website): show partner location instead of served regions in marketplace (#22224)
## Context On the partners marketplace, the eyebrow shown below each partner's name (on both the list card and the profile header) displayed the served regions (e.g. `APAC`, `EUROPE`). The partner's actual location was buried in the "Where & how" facts list. The location is more useful at a glance, so this swaps the two. ## Changes **List card (`PartnerCard`)** - The eyebrow below the name now shows the partner's city and country instead of the first served region. **Profile page** - `PartnerProfileHeader`: the eyebrow near the title now shows the real location (city, country). - `PartnerFactsList`: the served regions move into the "Where & how" section as a `Regions` row, taking the place of the now-redundant "Based in" row. The `Regions` chip rows on the card are unchanged. https://claude.ai/code/session_01FQRRfvPpmNsjAAnZeDc5sy --- _Generated by [Claude Code](https://claude.ai/code/session_01FQRRfvPpmNsjAAnZeDc5sy)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22224?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
1b4bddba6f |
fix(website): polish product hero AI transition (panel reveal timing + title wrapping) (#22223)
## Summary Two desktop/tablet polish refinements to the product page hero's hero→AI transition (`product-hero`). ### 1. Delay the Ask-AI panel reveal to after the wipe (`b638d600`) The Ask-AI side panel used to reveal *during* the dark "wipe up" (keyed to the morph `0.45 → 0.70`), so it competed with the rising black edge and piled onto the heaviest motion phase — rough on weaker laptops. It's now keyed to raw scroll progress over `[0.60, 0.70]` — the previously-idle hold after the wipe settles at `0.55` — so it slides in on its own beat once the eye has followed the black up. `morphProgress` is pinned at `1` across the whole post-wipe stretch, so leaving the morph clock was the only way to time the panel *after* the wipe. The conversation playback moves with it, so the chat doesn't stream while the panel is still `width: 0`. ### 2. Keep the AI heading to three lines below desktop (`8ae69a4b`) The AI heading is the page's longest line. Below `md`, only the mobile copy renders, where the measure was frozen at `360px` while the heading font scales fluidly toward `md` — so on tablet widths the title crammed onto four lines, recovering only at `921px` when the `672px` measure kicks in. Adds an `sm`-breakpoint measure step (`560px`) scoped to the AI heading; the intro heading and the desktop measuring path keep their existing `360/672` steps. ## Test plan - `product-hero-scroll-model` jest suite updated and green (11/11), including a new post-morph panel-timing test. - Lint (`check-conventions` + `oxlint` + `oxfmt`) and typecheck green. - Visually confirmed: the panel reveals after the wipe settles, and the AI title holds three lines across tablet widths. |
||
|
|
adf56cac56 |
fix(workflow): avoid 'file not found' when duplicating unbuilt code steps (#22179)
## Context Duplicating a workflow version (e.g. when editing an active workflow) clones each Code step's logic function via copyResources, which copied both the source and the built artifact unconditionally. Logic functions created from source are not built yet (no index.mjs, isBuildUpToDate=false), so copying the missing built file threw FILE_NOT_FOUND. Copy the built artifact only when it exists. This is safe because the duplicate inherits isBuildUpToDate=false and is rebuilt lazily on activation or run. In seed dev case for example, they are created with source but never built ## Test https://github.com/user-attachments/assets/5a7febba-413b-4041-985e-f84a99a5ebda |
||
|
|
b84f748237 |
Fix navigation opened section spacing (#22222)
## Summary - Remove the collapsed empty `Opened` sidebar section when no opened object exists. - Restore the first navigation section top alignment with the settings `User` section. ## Before/After Visual verification after the fix: in the current fixture data, the first visible main sidebar section (`Favorites`) starts at y=92, matching the settings `User` section at y=92. This confirms there is no collapsed `Opened` spacer pushing the main drawer content down. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22222?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
da6a2ee300 |
fix(ai-chat): keep streams alive on silent SSE death + make the stream job idempotent (#22201)
## Problem In production, an AI-chat assistant response sometimes freezes mid-stream (partial text, looks hung), then "picks up again on its own" later without the user resending and without a known worker restart. Root cause: the **agent-chat SSE subscription has no keepalive and no silent-death detection**. - Delivery is fire-and-forget Redis pub/sub (`SubscriptionService.publishToAgentChat`) and the resolver returns the **raw** iterator — unlike `EventStreamResolver`, which heartbeats every 30s via `wrapAsyncIteratorWithLifecycle`. - During a quiet model/tool gap the connection sends no bytes, so a proxy/LB/NAT can silently drop it mid-stream. `graphql-sse` neither surfaces an error nor resumes with `Last-Event-ID`, and **nothing re-pulls the existing Redis chunk catch-up on reconnect** (it only runs on thread (re)mount / `message-persisted` refetch). - So the live view freezes; recovery only happens when the terminal `message-persisted` fires a full refetch from the DB — the observed "self-recovery". This is the **same silent-SSE-death class fixed for the DB event stream in #21061**, which was never applied to the agent-chat path. The symptom also matches #21096 (worker logs the job finishing, client never updates, reload shows the message). It is **not** queue prioritization, and it is **not** addressed by #22193 (which only stabilizes the assistant message id and removes end-of-stream flicker). A secondary, independent self-recovery path also existed: BullMQ stalled-job re-run (default 30s `lockDuration`, no idempotency guard) re-streaming the whole turn → duplicate assistant messages / double billing. ## Changes ### Commit 1 — keepalive + silent-death recovery (ports the #21061 pattern to agent chat) - **Shared:** new `keepalive` variant on `AgentChatSubscriptionEvent`. - **Server:** wrap the agent-chat subscription iterator with `wrapAsyncIteratorWithLifecycle` — emit a `keepalive` on connect and every `APPLICATION_KEEPALIVE_INTERVAL_MS` (30s) so the connection keeps flushing bytes and a dead connection becomes detectable. - **Client:** track the last received event timestamp (refreshed on every chunk/keepalive in the SSE `next` sink); new `AgentChatStreamKeepAliveEffect` forces a resubscribe + messages refetch after 90s of silence, so the durable Redis chunk list backfills the gap (`firstLiveSeq` is reset on resubscribe). ### Commit 2 — stream-job idempotency + lockDuration - Thread a `lockDuration` option through `MessageQueueWorkerOptions` + the BullMQ driver; set `aiStreamQueue` to 10 min so long streams aren't falsely stalled. - Guard `StreamAgentChatJob.handle` with a `streamId`-scoped Redis lock (`SET NX PX` + compare-and-delete release) so a stalled re-run is skipped instead of double-processing. ## Verification ⚠️ I could **not run typecheck/lint locally** — `yarn install` could not complete in this environment (transient registry network aborts before the link step, so `node_modules` never populated). **Please rely on CI for type/lint verification.** The changes are written to match existing conventions; the points most worth a reviewer's eye are the resolver's iterator typing and the ioredis `set(..., 'PX', ttl, 'NX')` overload. How to confirm the root cause in prod: a frozen client with the worker logging `StreamAgentChatJob processed in …ms` and no `[AI_CHAT_NO_TEXT]` is the silent-death signature (check reverse-proxy idle/buffering). For the secondary path, watch `aiStreamQueue` `stalled`/re-processed metrics and duplicate turns around worker restarts. ## Notes / trade-offs - The 10-min `lockDuration` means a genuinely crashed worker's job isn't reclaimed for up to 10 min; the client-side keepalive/catch-up recovers the view independently, and the idempotency lock prevents duplicates. Faster dead-worker recovery could be a follow-up. - Touches `useAgentChatSubscription.ts` / `AgentChatRuntimeEffects.tsx` / `stream-agent-chat.job.ts`, which #22193 also touches — trivial rebase expected. Opened as **draft** pending CI. https://claude.ai/code/session_018dF82A1VcsuWMxPLmdY3dm --- _Generated by [Claude Code](https://claude.ai/code/session_018dF82A1VcsuWMxPLmdY3dm)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22201?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
8f7d6c24dd |
Add v2 onboarding import contacts page and unify the onboarding v2 shell (#22212)
<img width="3024" height="1668" alt="CleanShot 2026-06-26 at 13 29 28@2x" src="https://github.com/user-attachments/assets/9bdd0029-45eb-4ddb-859f-eaab9bb61406" /> Adds the new v2 onboarding **Import contacts** step (email + calendar import), shown right after workspace creation in the v2 flow. The presentational page was designed in a previous PR; this wires it in and unifies the shell. **What changed** - Reuses and unifies the existing v2 onboarding shell: extracts `OnboardingV2Layout` + `OnboardingV2Header` (the back + logo header, now with the free-credits pill), and the `SignInUpV2` workspace-creation step renders through it (old `SignInUpV2Header` removed). - New `SyncEmailsV2` route (`/sync/emails-v2`) under `BlankLayout`, wired to the same OAuth/skip hooks as v1 `SyncEmails`. - The `SYNC_EMAIL` step routes to the new page only when `isOnboardingV2` is set (mirrors the existing `WorkspaceActivation` → `WorkspaceActivationV2` branch); the v1 modal is unchanged for the non-v2 flow. - No backend changes — reuses the `SYNC_EMAIL` status and `skipSyncEmailOnboardingStep` mutation. **Reviewer notes** - Connect defaults to `METADATA` (private) visibility to match the "Only you will be able to see your emails and events" note (v1 had a selector defaulting to `SHARE_EVERYTHING`). - The header free-credits pill shows `0` for now (no current-workspace credits source on the frontend yet). - The back button is hidden on the import page (no meaningful "back" after workspace creation); unchanged on the workspace-creation step. |
||
|
|
9747e3a7a3 |
feat(messaging): link emails by Reply-To as a REPLY_TO participant (#22216)
Relay senders (e.g. a website form sending as a shared address with the real contact in Reply-To) never linked to the contact because matching only used From/To/Cc/Bcc. Record Reply-To addresses under a new REPLY_TO participant role across the Gmail, Microsoft and IMAP drivers, excluding any that just repeat the sender. Adds the REPLY_TO option to the messageParticipant role field and a 2.17 workspace command to backfill it for existing workspaces. QAed with real test run <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22216?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
ebe067f65c |
Fix favorite showing non-readable objects (#22217)
## Context Navigation menu items backed by objects the user has no read permission on were correctly hidden from the Workspace section, but Favorites still showed them. Favorites are user-scoped nav items (tied to workspaceMemberId), and FavoritesSection only filtered out folder children (!item.folderId) without ever checking canReadObjectRecords. The shared upstream filter (filterAndSortNavigationMenuItems) intentionally does not apply read permissions, because its output also drives drag-and-drop position math and layout-customization/edit mode, which need the complete, unfiltered list. So read-permission filtering belongs at the display layer. ## Fix New shared hook useReadableNavigationMenuItems that centralizes the read-permission filtering logic previously duplicated across sections: wires up objectMetadataItems + views + object permissions around isNavigationMenuItemReadable filters folder children and top-level items (dropping folders whose children are all unreadable) exposes both raw filtered* outputs and isLayoutCustomizationModeEnabled-aware display* outputs FavoritesSection now applies the filter via the hook, so unreadable favorites are hidden — while still showing everything in layout-customization mode (consistent with the Workspace section). WorkspaceSectionContainer refactored to consume the same hook, removing its inline isItemReadable, the dual-map reduce, and inline filtering. ## Before ### With access <img width="842" height="570" alt="Screenshot 2026-06-25 at 14 01 38" src="https://github.com/user-attachments/assets/ae51f3c8-c178-4162-84ce-3fe49cf07987" /> ### Without access <img width="987" height="590" alt="Screenshot 2026-06-25 at 14 02 08" src="https://github.com/user-attachments/assets/94dacea3-bd77-4fcb-868d-353ed513b28c" /> ## After ### Without access <img width="1001" height="615" alt="Screenshot 2026-06-25 at 14 02 46" src="https://github.com/user-attachments/assets/e1ccd5d9-8583-4ff1-ab91-6f6d187925c5" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22217?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
fdf9f543ae |
Fix rounded page layout tab edit outline (#22214)
## Summary - Round the edited page layout tab outline to match the tab hover radius. ## Test plan - `npx oxfmt --check packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableTab.tsx` - `npx oxlint --type-aware -c packages/twenty-front/.oxlintrc.json packages/twenty-front/src/modules/page-layout/components/PageLayoutTabListReorderableTab.tsx` - `npx nx run twenty-front:lint` - Browser: verified the edited `Notes` tab outline before/after locally. ## Visual <img width="1108" height="392" alt="clipboard" src="https://github.com/user-attachments/assets/1cb63180-83a9-4ef8-9c55-2b475eaaa02b" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22214?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
a22eb5a591 |
Bump call-recorder and people-data-lab apps (#22213)
as title <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22213?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
bea7857a1c |
Fix settings page root overflow (#22207)
## Summary - Clamp the fixed app shell with `overflow: hidden` so long nested settings scroll content no longer contributes to the document root scroll range. - Keeps the object permission page scrolling inside its existing settings `ScrollWrapper` instead of letting the whole page slide under the viewport. ## Screenshots Before: root document can scroll under the bottom of the viewport and exposes the gray app background.  After: the same root scroll attempt leaves the app shell fixed to the viewport.  ## Browser Validation Route tested: `/settings/members/roles/78fa69cb-1237-43b5-bfa5-5a11a47bf781/object/5ab1a16b-7811-471f-ac53-940666c667dd` - Before on `http://apple.localhost:3001`: `window.scrollTo(0, 9999)` moved the root to `scrollY=244.5`; `htmlScrollHeight=1287`, `htmlClientHeight=1043`. - After on `http://apple.localhost:3002`: the same root scroll attempt stayed at `scrollY=0`; `htmlScrollHeight=1043`, `htmlClientHeight=1043`. - The settings content still scrolls internally: wrapper `scrollHeight=1475`, `clientHeight=955`. ## Checks - `git diff --check` - `yarn oxfmt --check packages/twenty-front/src/modules/ui/layout/page/components/DefaultLayout.tsx` - `cd packages/twenty-front && npx oxlint --type-aware -c .oxlintrc.json src/modules/ui/layout/page/components/DefaultLayout.tsx` <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22207?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
d6fca7a82e |
Bump call-recorder and people-data-lab apps (#22208)
as title <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22208?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
34dc681c0e |
Use settings icon in settings drawer tab (#22204)
## Summary - Let the shared navigation drawer tab row accept a custom icon and accessible label for its navigation tab. - Use the Settings icon and Settings label when that tab row is rendered inside the settings drawer. - Keep the main navigation drawer defaulting to the Home icon. ## Screenshots ### Before <img width="420" alt="Before: settings drawer navigation tab uses the Home icon" src="https://github.com/user-attachments/assets/03b684ba-0f62-4a96-a9f1-8c6138efd9cf" /> ### After <img width="420" alt="After: settings drawer navigation tab uses the Settings icon" src="https://github.com/user-attachments/assets/3e5f4669-ed7c-4355-be92-c9cfbf160959" /> ## Validation - `yarn oxfmt --check packages/twenty-front/src/modules/navigation/components/MainNavigationDrawerTabsRow.tsx packages/twenty-front/src/modules/navigation/components/SettingsNavigationDrawer.tsx` - `git diff --check` - `yarn nx typecheck twenty-front` Note: `oxlint` could not run locally because the installed dependencies are missing the native `@oxlint/binding-darwin-*` optional package. |
||
|
|
cb49a7a053 |
Add v2 onboarding loading screen while creating workspace (#22152)
https://github.com/user-attachments/assets/cc7b1d10-7495-4f21-9311-4c22c0f14771 Adds the full-screen loading screen shown while a new workspace is being created in the v2 sign-up flow (`SignInUpV2`), building on the v2 "Create your workspace" step. How it works: - Submitting the v2 create-workspace form marks the flow as v2 (`isOnboardingV2State`) and creates the workspace. The flag is carried across the cross-subdomain redirect with an `onboardingV2=true` URL param, so v2 users land on a new `/workspace-activation-v2` route instead of v1's `/workspace-activation`. - `WorkspaceActivationV2` runs the real `activateWorkspace` mutation on mount and renders the loader: a pulsing Twenty logomark above a stack of status messages that shift up one at a time, cycling once per second. There is no faked/minimum duration; it advances to the next onboarding step as soon as the workspace is activated. - On activation failure it shows a "Workspace creation failed" screen with a Retry button. v1 onboarding is unchanged. Storybook: `Modules/Auth/SignInUpWorkspaceActivationV2`. Note: The flashes will be fixed in later PRs <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22152?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
ce4d0f3447 |
fix: hide "Create Workspace" button when multi-workspace is disabled (#22202)
## Description This PR fixes a bug where the "Create Workspace" button was unconditionally rendered in the workspace switcher dropdown, even when single-workspace mode was active (`IS_MULTIWORKSPACE_ENABLED=false`). This created a confusing "dead-end" action for users, as clicking the button would do nothing (because the backend correctly blocks workspace creation in this mode, and the frontend skips the redirect). ### Changes made - Imported the `isMultiWorkspaceEnabledState` atom from client-config. - Evaluated `isMultiWorkspaceEnabled` inside `MultiWorkspaceDropdownDefaultComponents`. - Conditionally rendered the "Create Workspace" `<MenuItem>` only if multi-workspace is enabled. Closes #22139 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22202?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
b625bd1995 |
fix(ai-chat) - improvements (#22193)
- remove flickering at assistant message streamed end - add copy code - leave chat history when navigating to settings <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22193?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
e60d790990 |
i18n - website translations (#22203)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22203?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
9b57c5dfac |
Set record card header height and calendar spacing (#22187)
## Summary Sets the shared `RecordCardHeaderContainer` height to `32px`, so board and calendar card headers use the same common header size. Updates the board fetch-more card-height estimate to use the same `32px` header value. Reduces the calendar card header/content gap by removing the body top padding while keeping the existing side and bottom padding. ## Screenshots Before:  After:  ## Validation - Browser verification on `http://apple.localhost:3001/objects/opportunities?viewId=0433d066-dda7-4b2c-89e5-04d4792d193c`: visible calendar card body padding computes to `0px 4px 4px` - Browser verification on board view: visible board card headers compute to `32px` - Browser console errors: none - `git diff --check` - `prettier --write` on changed files - `oxlint --type-aware` on changed files in the running checkout |
||
|
|
85f64abb28 |
Frameless ProductStepper with twenty-front-faithful scenes (#22197)
## What Reworks the website's **ProductStepper** (the scroll-driven *Data model / Automation / Layout* section) to be frameless and to render all three scenes faithfully to twenty-front — real icons, labels, colors, structure, and connectors. ## Changes **Frameless + scaling** - Removed the shared white card/header; the three scenes now sit directly on the dark dot-grid stage. - Unified every scene on one `StageFit` primitive (fixed design box → scaled to fit), pixel-identical at full width and uniform on smaller screens. **Data model scene** - Real object schema (3 Standard + 2 Custom objects, real relation fields); replaced an invented "Investors" object with the real **Employment History** custom object (`IconBriefcase`, Company + Person relations) from the server seed. - Clean spanning-tree connections; removed the Standard/Custom badge; fixed card sizing + edge centering. **Workflow scene** - Real action labels + a logical flow: *Record is Created → Filter → Search Records → AI Agent → (Update Record · Send Email · Create Record)*. Dropped the iterator (a loop construct shown without a loop body). - Per-action icon colors matching twenty-front (trigger blue, flow green, record gray, send-email red, AI agent pink) on a gray tile. - Rebuilt the node to twenty-front's real anatomy and the connectors (source circle → `getBezierPath` → arrow marker) verbatim. **Layout scene** - Real workspace sidebar: real Tabler object icons, exact labels, the true default sidebar (6 objects + Workflows folder), `getIconTileColorShades` tile colors; dropped invented entries. - Record overview + Fields editor with correct field-type labels (Links, True/False, Date and Time); legibility + spacing tuning. **Icons** — replaced every hand-drawn approximation with real `@tabler/icons-react` / twenty-front object icons. **Misc** — smoother step-to-step transitions (translate + easing tokens). ## Testing Marketing visual; `lint` / `typecheck` / `build` green, and each scene reviewed visually against twenty-front. https://github.com/user-attachments/assets/997e1b95-55c0-401a-93a4-c70545577057 |
||
|
|
b49225df4b |
Reorder validation execution to match migration action order (#22200)
## Summary Reorders the validation execution sequence in the workspace entity migration builder to match the actual execution order of migration actions (delete → create → update). This ensures that optimistic entity maps accurately simulate the post-migration state during validation. ## Key Changes - **Moved creation validation before update validation** in `WorkspaceEntityMigrationBuilderService`: Creation validation now executes immediately after deletion validation, allowing updates to reference entities created in the same migration without validators needing to peek into to-be-created maps. - **Removed `remainingFlatEntityMapsToValidate` parameter from update validation**: Since creation validation now completes before update validation begins, the optimistic maps already contain all created entities. Updates can safely reference newly created entities through the optimistic maps without needing access to remaining-to-create maps. - **Simplified `FlatNavigationMenuItemValidatorService`**: Removed the logic that combined remaining-to-create maps with optimistic maps, now relying solely on the optimistic maps which contain all previously validated creations. - **Updated type definition**: Modified `FlatEntityUpdateValidationArgs` type to exclude `remainingFlatEntityMapsToValidate` since it's no longer needed. ## Implementation Details This change enables a more intuitive validation flow where: 1. Deletions are validated first 2. Creations are validated next (in topological order for self-referential FKs) 3. Updates are validated last (can safely reference newly created entities) The optimistic maps are progressively built during creation validation, so by the time update validation runs, they faithfully represent the post-migration state, eliminating the need for validators to access separate remaining-to-create maps. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22200?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
7e48d36c98 |
fix(twenty-front): apply object type translations to details panel relation labels (#22090)
## Problem When users customize or translate object type names (e.g. "Company" → "Unternehmen" in German), the translated/customized names do **not** appear in the details panel. The default English names still show instead. This is because the frontend's relation metadata only carried `nameSingular`/`namePlural` (internal API identifiers), not `labelSingular`/`labelPlural` (user-facing display labels). Components that display relation names had no choice but to use the internal identifiers. Fixes #19790 ## Changes ### Data layer — add labels to the pipeline - **GraphQL fragment** (`fragment.ts`): Added `labelSingular`/`labelPlural` to `sourceObjectMetadata` and `targetObjectMetadata` in both `relation` and `morphRelations` - **Type** (`FieldMetadataItemRelation.ts`): Extended the `Pick` type to include `labelSingular`/`labelPlural` - **Field metadata type** (`FieldMetadata.ts`): Added `relationObjectMetadataLabelSingular`/`LabelPlural` to `FieldRelationMetadata` - **Mapping** (`formatFieldMetadataItemAsFieldDefinition.ts`): Maps the new label fields with `label ?? name` fallback for backwards compatibility ### Display layer — use labels for user-facing text - **RecordDetailRelationRecordsListItem**: Uses `relationObjectMetadataLabelSingular` for delete confirmation dialog title, subtitle, and button text (falls back to `nameSingular`) - **RecordDetailRelationRecordsList**: Threads `objectLabelSingular` prop through - **RecordDetailRelationSection**: Passes `labelSingular ?? nameSingular` from the looked-up object metadata - **FieldWidgetRelationCard**: Passes `relationObjectMetadataLabelSingular` from field metadata - **FieldWidgetJunctionRelationCard**: Passes `labelSingular ?? nameSingular` from object metadata lookup - **FieldWidgetMorphRelationCard**: Passes label from morph relation hook result - **useGetMorphRelationRelatedRecordsWithObjectNameSingular**: Carries `labelSingular` from matched morph relation ### Test data - Updated story/mock files with `relationObjectMetadataLabelSingular`/`LabelPlural` fields - Updated `SettingsDataModelRelationFieldPreview` with label fields in morph relation objects ## Design decisions - **Backwards compatible**: All new props are optional. Every display usage uses `label ?? name` fallback, so if `labelSingular` isn't available yet (e.g. before GraphQL regeneration), it falls back to the old behavior - **Lookup vs display separation**: `nameSingular` continues to be used for lookups, routing, and GraphQL queries (it's the identifier). `labelSingular` is only used for user-facing display text - **Minimal scope**: Only changes the display paths identified in the bug report — confirmation dialogs and relation labels in the details panel ## Test plan 1. Set workspace language to a non-English locale (e.g. German) 2. Navigate to a record with relation fields 3. Verify relation section titles and labels show translated names 4. Try to delete a related record — verify the confirmation dialog uses the translated name 5. Switch language back to English — verify everything still works correctly <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22090?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
b3e39e2198 |
fix: relative date picker calendar display (#21895)
Part of https://github.com/twentyhq/twenty/issues/19739#issuecomment-4652034526 (Bug 1-3). Maybe it feels like theses bugs are not actually bugs, but we can maybe say it as UX improvements: specially needed in case when an user will choose any past options. ### Bug 1: calendar open on wrong month With Is Relative (e.g. Past 1 Quarter), the calendar opened on today’s month instead of the range start. After the fix, it now opens on the first month of the filtered range. **Testing:** View filter → Date field → Is Relative → Past 1 Quarter. Calendar opens on January (range start), not today’s month https://github.com/user-attachments/assets/8849d00a-4d5c-4f8a-8d31-3a62535eb311 ### Bug 2: Dates not highlighted Ranges older than ~2 months (e.g. Q1 when today is June) showed no highlighted days. Highlighting now covers the full resolved range. **Testing:** Same setup: past 1 Quarter on a date when Q1 is outside the old 2‑month window. Jan 1 - Mar 31 will highlight. https://github.com/user-attachments/assets/d21e2272-c923-4493-80ff-bdf4228842b1 ### Bug 3: No month navigation Relative mode only showed Past - 1 - Quarter controls with no way to browse months. Now see the new arrows move through months without changing the filter. <img width="377" height="455" alt="Screenshot 2026-06-20 181107" src="https://github.com/user-attachments/assets/eb51feb9-af10-489a-b166-8b8d6c642e05" /> > [!NOTE] > 1. We can't do the fixes by one by one, i have to fix them within one PR because all the fixes are inter-related, like we can't test the bug 1 fix alone without implementing bug 3. > 2. Bug 4 will be done in a separate PR which is actually the issue #19739. See https://github.com/twentyhq/twenty/issues/19739#issuecomment-4652034526 for better understanding. > 3. If you see the screen recordings, they are actually done with the alignment fixes from #21881 . So without that changes you will see the alignmemt issues in the calendar grid in your local. --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> |
||
|
|
6e2df0654b |
[Workflows] Allow iterator to take whole item as variable (#22031)
**Select the whole item in iterator loops, and iterate over a step's array output** ## Summary Two related improvements to working with lists in workflows: - Pick the current item as a whole inside an iterator loop. Previously, in a node inside the loop, you could only reference individual fields of the Iterator's current item. Now you can select the whole item (e.g. a full record) — useful for passing it straight into a downstream step. <img width="1270" height="744" alt="Screenshot 2026-06-23 at 17 02 47" src="https://github.com/user-attachments/assets/6b92e72e-ec25-4c1a-9841-3a438210e753" /> - Iterate over a step's array output. A Code / Logic Function step that returns a top-level array couldn't be fed to the Iterator: its output was flattened into indexed entries (0, 1, …) with no way to select the array as a whole. A new "Whole list" option selects the step's entire output, and the Iterator infers the per-iteration item shape from it. <img width="1026" height="728" alt="Screenshot 2026-06-23 at 17 17 53" src="https://github.com/user-attachments/assets/db07dcd8-4fb8-4db9-8b45-aa56051d9f3b" /> Together these complete the loop ergonomics: select a list → iterate → reference the current item (whole or by field) downstream — matching the model used by tools like Windmill. ## What changed - The variable picker offers a "Use the whole item" option when viewing an iterator's current item, and a "Whole list" option when a step returns a top-level array. - The Iterator's current-item schema can now be inferred from a variable pointing at a step's whole output. ## Risks for existing workflows None expected. The change is purely additive: - No DB migration and no change to how output schemas are stored or read — existing schemas, variables, and iterators behave identically. - No change to runtime variable resolution; existing {{step.field}} and current-item references are untouched. - The new options only apply to new selections (whole item / whole list); all existing paths take the unchanged code path. - The only edge case: array detection is heuristic (an output whose keys are exactly 0…n-1), so an object that happens to have those keys would also show "Whole list". This is rare for real outputs, affects nothing unless a user selects it, and fails safe — the Iterator validates its input and throws a clear "items must be an array" error if a non-array is passed. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22031?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
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> |
||
|
|
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. -->
|