facdbb5ba8ea0d29b95a340ec21900db6e778bed
6299 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
facdbb5ba8 |
v2 onboarding: dedicated verify step and upgrade-free-trial as the last step (#22303)
https://github.com/user-attachments/assets/b1ee4f77-c6d7-4638-b9f1-dd801d1cc0db Completes the onboarding-v2 flow: a dedicated verify step, the reordering that makes the plan step come last, and the upgrade-free-trial page itself. ## Verify step (`/verify-v2`) After the cross-domain token exchange, v2 sign-ups land on a clean `BlankLayout` "Verifying your email" screen (fading Twenty logo) instead of the v1 `AuthModal` flashing over the background mock. The redirect target is chosen from `isOnboardingV2` (read from the Jotai store at redirect time). The pulsing logo is extracted into a shared `OnboardingPulsingLogo`, reused by the workspace-activation loader. `/verify-v2` joins the same exempt lists as `/verify` (ongoing-creation guard, metadata gater, apollo unauthenticated handler, captcha, page title) — intentionally not `useShowAuthModal`, which is what drops the modal. ## Plan step is now last `getOnboardingStatus` checks `PLAN_REQUIRED` after invite-team instead of first, so onboarding runs workspace activation → email → profile → invite → plan. This is what lets the upgrade step be reached as the final step instead of gating right after sign-up. Applies to both v1 and v2 (same order). ## Upgrade free trial page (`PlanRequiredV2` → `ChooseYourPlanV2` / `UpgradeFreeTrial`) The final step, full-screen under `BlankLayout` via `OnboardingV2Layout`, matching the Figma (billing card with the Stripe form, the "Basic / without credit card" option, trial + credits pills). Reuses the v1 `ChooseYourPlanContent` billing logic (`SubscriptionPaymentForm`, `useHandleCheckoutSession`). The "+N free credits" reward comes from `clientConfig.onboarding.upgradeCreditsReward` (sourced from `BILLING_FREE_WORKFLOW_CREDITS_FOR_TRIAL_PERIOD_WITH_CREDIT_CARD`). ## Also Fixes a latent staleness in the Apollo `onUnauthenticatedError` handler — it captured `location` from the memoized client, now read via a ref — so auth-path exemptions are correct after navigation. Note: the onboarding step order change affects v1 too (plan becomes its last step as well). |
||
|
|
2bb7fb2e9b |
Polish settings page titles and admin tables (#22305)
## Summary - Reuse the shared settings title presentation for read-only and editable settings page titles. - Polish settings AI/app icons, breadcrumb cropping, and admin detail title icons. - Align admin panel table/card typography and spacing with existing settings tables. ## Before/After <img width="1524" height="2214" alt="Settings pages before and after" src="https://github.com/user-attachments/assets/91037b25-b442-4eb2-b244-1d8280ce2cd9" /> <img width="2200" height="3268" alt="Additional settings UI before and after" src="https://github.com/user-attachments/assets/f10d7283-7031-4958-8539-649c3067cd31" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22305?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. --> |
||
|
|
1b2f2c71c3 |
Move filter group destruction after filter operations (#22248)
### The current order is: 1. Create filter groups 2. Update filter groups 3. Destroy filter groups ← **happens here** 4. Clean up store (cascade) 5. Create/update/delete filters (which may reference groups just destroyed) The fix is to move filter group destruction after filter operations, so filters that reference those groups get created/updated/deleted first. ### after fix : The persistence order is now: 1. Create filter groups 2. Update filter groups 3. Create/update/delete view filters (these can safely reference groups that still exist) 4. Destroy filter groups (only after all filter mutations are done) 5. Clean up store (cascade-deleted filters **root cause :** step 4 happened before step 3, so filter creates/updates would reference groups that had already been deleted in the same save cycle ; causing the backend to fail with "Migration execution failed" when it couldn't resolve the `viewFilterGroupId `foreign key. this fixes the bug : #21351 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22248?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: Souheyl Gouadria <souheyl.gouadria@medius.com> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
d1854bc7e9 |
Add marketplace catalog synchronization to admin panel (#22260)
## After <img width="1476" height="658" alt="image" src="https://github.com/user-attachments/assets/25a042dd-eabf-4b71-9872-d3b627104634" /> ## Summary This PR adds the ability for admins to manually synchronize the marketplace application catalog from the admin panel. It introduces a new mutation endpoint and UI controls to trigger catalog synchronization with user feedback via snackbar notifications. ## Key Changes - **Frontend (SettingsAdminApps component)**: - Added `useSnackBar` hook for user feedback on sync success/failure - Imported `useMutation` from Apollo Client to handle the sync operation - Added `IconRefresh` and `Button` imports for the sync UI control - Created `handleSyncCatalog` function that triggers the mutation, refetches app registrations, and displays appropriate snackbar messages - Added a new "General" section with a "Synchronize catalog" button above the existing app registrations table - Button shows loading state and is disabled while sync is in progress - **Backend (AdminPanelResolver)**: - Added `syncMarketplaceCatalog` mutation that queues a `MarketplaceCatalogSyncCronJob` via the message queue - Uses `@InjectMessageQueue` decorator to inject the cron queue service - Includes job deduplication via `id: 'marketplace-catalog-sync'` to prevent multiple pending sync jobs - Protected with `@UseGuards(AdminPanelGuard)` for admin-only access - **GraphQL Schema**: - Added `SyncMarketplaceCatalog` mutation type definition - Generated corresponding TypeScript types and mutation document - **New Files**: - Created `syncMarketplaceCatalog.ts` GraphQL mutation document <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22260?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. --> |
||
|
|
a215d3cbdf |
fix: prevent crash when leaving dashboard edit mode with side panel open (#22292)
## Problem
When creating or editing a dashboard and opening a page layout side
panel (clicking the **"Add widget"** box, or editing a widget via grid
edition), clicking another nav item like **"Opportunities"** crashes the
whole app ("Sorry, something went wrong").
### Steps to reproduce
1. Go to `/objects/dashboards` and click **"+ Add new"** to create a
dashboard.
2. Click the **"Add widget"** box (or add a widget via grid edition and
open its settings).
3. Click another nav item such as **"Opportunities"**.
4. The app crashes.
## Root cause
The side panel stays mounted during its close animation, but the main
context store has already switched to the new page (the record index has
no single targeted record). The still-mounted page layout side panel
page (`SidePanelPageLayoutDashboardWidgetTypeSelect`, chart settings,
etc.) re-renders and calls `usePageLayoutIdFromContextStore`, which
throws `Error: Only one record should be selected`. With no local error
boundary, the throw propagates to the top-level boundary and crashes the
app.
## Fix
`SidePanelRouter` now skips rendering page layout side panel pages
whenever the main context store has no single targeted record — the same
condition `usePageLayoutIdFromContextStore` requires to not throw.
During navigation the panel closes cleanly instead of crashing.
This is safe because `usePageLayoutIdFromContextStore` unconditionally
throws without a single-record selection, so the guard can only skip
pages that would otherwise crash — it cannot break a currently-working
flow. The guard uses the existing `isPageLayoutSidePanelPage` helper, so
it covers all page layout side panel pages (widget type select, chart /
iframe / record table settings, record page field settings, etc.).
## Testing
- Reproduced the crash in the running app, applied the fix, and
confirmed navigating to Opportunities from both the **widget type
select** and the **chart settings** panels now lands on the
Opportunities list with no console or page errors.
- `oxlint --type-aware` and `nx typecheck twenty-front` both pass.
|
||
|
|
db7d8172f7 |
Add v2 onboarding invite team page (#22229)
<img width="3024" height="1500" alt="CleanShot 2026-06-26 at 18 09 47@2x" src="https://github.com/user-attachments/assets/e91f30a5-2763-42a0-9abf-d9fa8400870c" /> Adds the v2 onboarding **Invite team** page (`INVITE_TEAM`), shown right after the create-profile step for the onboarding-v2 cohort. It renders full-screen under `BlankLayout` via the shared `OnboardingV2Layout`, matching the Figma (340px column, email inputs with inline remove, dark Invite, Skip). Reuses all v1 invite-team logic via a new `useInviteTeam` hook (v1 `InviteTeam` now consumes it too; its UI is unchanged). Routing mirrors `SyncEmailsV2`/`CreateProfileV2`: new `AppPath.InviteTeamV2`, lazy route, and an `isOnboardingV2`-gated branch in `usePageChangeEffectNavigateLocation` (+ tests and a Storybook story). No backend changes. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22229?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. --> |
||
|
|
13f80f0d95 |
fix: ignore IME composition Enter in chat-thread and attachment rename inputs (#22270)
## What's this PR doing? Two inline rename inputs run their action on `Enter` without ignoring the `Enter` that confirms an IME composition: - `AiChatThreadListItem` (renaming an AI chat thread). It also calls `preventDefault()`, so the composition-commit `Enter` is swallowed and the half-typed title gets saved. - `AttachmentRow` (renaming an attachment). The same `Enter` saves the unfinished name. When you type with an IME (Japanese, Chinese, Korean), the first `Enter` after typing confirms the candidate text rather than submitting, so these handlers fire with text the user hasn't finished entering. ## Why The codebase already guards this where keyboard handling goes through `useHotkeysOnFocusedElement` (`if (keyboardEvent.isComposing || keyboardEvent.keyCode === 229) return`), and the inline inputs that don't use that hook add the same check themselves: see `SettingsAccountsBlocklistInput`, `SettingsDevelopersApiKeysNew`, and the sign-up workspace forms. These two rename inputs were just missing it. ## How Add the same `isComposing || keyCode === 229` guard before the `Enter` branch. For input without an IME, `isComposing` is `false` and `keyCode` is `13`, so the rename-on-Enter behavior stays the same. This only skips the action on the composition-commit key. I checked the change against the repo's Prettier config locally. I couldn't add a unit test because jsdom doesn't dispatch real composition events (`isComposing` stays `false`), so it can't reproduce the keystroke. Happy to add an e2e test if that's preferred. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22270?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. --> |
||
|
|
d81b3c3fa3 |
feat(twenty-sdk): extract & compile app translations into the manifest (#22236)
## Summary **PR 2/4** of the app-metadata-translations stack. Gives app developers the authoring side, as part of the normal manifest build — and it stays out of the way of developers who don't translate. - `twenty-sdk` CLI i18n pipeline: collect translatable strings from the manifest, generate value-as-key message ids (`sha256(value)` truncated, byte-identical to the server's `generateMessageId`), a `dev i18n-extract` command to scaffold per-locale catalog files, and a compile step folded into `build` that emits `manifest.translations`. - Opt-in: no `locales/` dir → `compileApplicationTranslations` returns `undefined` → manifest is unchanged. - Adds an optional `locale` to the front-component execution context so components can translate against the host locale. ## Stack Stacks on #22235 (PR 1/4). Base branch: `claude/app-translation-1-runtime-resolution`. ## Tests Unit (vitest): extract/compile round-trip + message-id determinism. ## Verification note `yarn install` could not complete in the remote dev environment, so typecheck/lint/tests were not run locally — **CI is the source of truth**. https://claude.ai/code/session_01NiE7o3cd3zCLZarVkJa6UA --- _Generated by [Claude Code](https://claude.ai/code/session_01NiE7o3cd3zCLZarVkJa6UA)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22236?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
538b180824 |
feat(dpa): self-serve Data Processing Agreement generator (#22243)
## What
A single, region-aware DPA that serves all customers, generated
automatically from the customer's deployment. Two layers:
1. **Click-through DPA** — recorded at signup (acceptance = execution),
resolving merge fields from the deployment region. Cloud only.
2. **In-app signed-PDF generator** — Settings → Legal → Generate DPA:
preview the agreement, enter legal entity + authorized signatory,
download a PDF pre-signed by Twenty, and store the executed copy against
the workspace with its template version + timestamp. Deep-linkable at
`/dpa` (login-gated) for `twenty.com/dpa`.
## How it resolves
A typed variable matrix (`dpa-region-config.constant.ts`) maps the
deployment region to the contracting Processor entity and terms:
- **EU (default)** → Twenty.com SAS, hosting EU/Frankfurt, governing law
France, SCC section dormant.
- **US (custom)** → Twenty, Inc., hosting US, SCC section active.
Region is a deployment-wide setting (`DPA_DEPLOYMENT_REGION`, default
EU) behind a `DpaRegionService` seam so it can later become
per-workspace without touching callers. The legal text is verbatim from
the template (generated into `dpa-template.constant.ts` directly from
the source `.docx`); only the 6 merge fields are filled and the SCC
sections (7.2–7.5) stay in the document for every region per the spec —
only field values branch. Sub-processors are deferred to
trust.twenty.com (not enumerated). Billing stays decoupled (Twenty, Inc.
remains merchant of record regardless of Processor).
## UI
Standard list + create-page pattern (mirrors API keys / webhooks): a
list of executed copies (with re-download) — or the agreement preview
when none exists — and a top-right blue **Generate DPA** CTA opening a
standard create page. The "Legal" item is intentionally **not** in the
settings menu; the page is reached via the `/dpa` deep link.
## Notable implementation details
- **PDF** is rendered server-side with `@react-pdf/renderer`. The
built-in standard-14 fonts only encode ASCII and crash on the template's
curly quotes / em–en dashes / accented Latin, so Liberation Sans (OFL)
is **subset to a Latin glyph set and embedded as base64 data: URLs** —
no font files to ship or resolve at runtime (works in dev, prod-Docker
and CI).
- New `core.dpaAgreement` table via a fast instance command (FK hash
reproduced to match TypeORM).
- Self-hosted deployments (billing disabled) skip click-through
recording and stamp a prominent "not a valid agreement" banner on the
preview and PDF.
## Tests
- Unit: resolver (per-region entity/law/SCC state, EU default, no
unresolved `{{ }}`, SCC sections present in both regions, self-hosted
notice) and HTML renderer.
- Integration (`test/integration/graphql/suites/dpa`): preview has no
unresolved fields; `generateSignedDpa` renders + persists + returns a
downloadable PDF (asserted with accented input to guard the font
regression); list re-download.
## ⚠ Needs legal input before go-live (marked `TODO_CONFIRM` in
`dpa-region-config.constant.ts`)
- Registered-office addresses for Twenty.com SAS and Twenty, Inc.
- US deployment governing law (the template only specifies France).
- DPO name and the Twenty pre-signed authorized signatory name/title.
## Out of scope (flagged per spec)
Intra-group legal agreement and any Stripe/billing-entity changes. A
future e-sign provider would plug in at `DpaService.generateSignedDpa` +
the signatory input.
> Draft until the integration test passes in CI and the legal
`TODO_CONFIRM` values are supplied.
https://claude.ai/code/session_01Ahjydxx6J1souz1s1NeA9a
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Ahjydxx6J1souz1s1NeA9a)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22243?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
|
||
|
|
2a21eb46c0 |
perf: cap to-many relation records per parent and inline chips in table (#22206)
## Problem Record table views that show a to-many relation column (e.g. Workflows with a "Runs" column) get slow and janky to scroll when some records have many related records. Two root causes, found by profiling the page live: 1. **Backend over-fetch + unfairness.** Nested one-to-many relations were loaded with a single flat limit of `QUERY_MAX_RECORDS_FROM_RELATION * parentCount` shared across *all* parents in the page (`WHERE parentColumn IN (ids) LIMIT 60*N`, no per-parent cap). A single hot parent can consume the entire budget — returning thousands of rows for one cell, and potentially starving sibling parents of records they actually have. The `limit * parentCount` shape shows the original intent *was* a per-parent budget; it was just implemented as a global limit. 2. **Frontend DOM explosion.** `ExpandableList` mounts the *entire* child array inline (clipped with `overflow: hidden`) when unfocused, and mounts all children for measurement when focused. A cell with 2,000+ relation chips mounts ~14k DOM nodes — one observed page reached ~55k nodes for 43 rows, producing 100–300 ms main-thread long tasks on every scroll. ## Fix - **Backend:** load one-to-many relations with a true **per-parent** cap via a `LATERAL` join — each parent runs its own indexed, `LIMIT`-ed scan that stops after the per-parent budget. This is `O(perParentLimit × parentCount)` and never reads or sorts a parent's full relation set. The per-parent query is built through the workspace query builder (so it stays schema-qualified and keeps the soft-delete predicate) and wrapped as a `FROM` subquery; read/row-level permissions are enforced when records are hydrated by id, as elsewhere in the relation loader. Many-to-one is unchanged. - **Frontend:** add an opt-in `maxInlineCount` to `ExpandableList` so to-many relation cells mount only a small inline preview; the expand dropdown still renders the full fetched set. Fully backward compatible (no cap → identical behavior). ## Why LATERAL over a window function A windowed `ROW_NUMBER() OVER (PARTITION BY parent) <= limit` is correct and fair too, but a window function **cannot stop early within a partition** — it must read every matching row (and sort it). Measured on skewed data (one parent with ~4k children, on the existing single-column join index, PG16): | Approach | Time | Buffers | Rows read from the hot partition | |---|---|---|---| | Pre-PR (`LIMIT 60×N`) | 1.6 ms | 91 | ~180 total, early-stops, but **unfair** (starves siblings) | | Window (`ROW_NUMBER`) | 3.7 ms | 128 | **all ~4k + sort** | | **LATERAL (`per-parent LIMIT`)** | **0.5 ms** | **57** | **~60, index early-stop** | LATERAL matches the pre-PR read cost while being fair, needs no new index, and scales independently of how large any single relation is. ## Verification - Backend integration test (`nested-relation-per-parent-limit`): a parent with 65 children is capped at 60 while a sibling with 3 keeps all 3 — passes. - `EXPLAIN ANALYZE` on the generated SQL: Index Scan with the `LIMIT` pushed into the per-parent lateral (early-stop). - Frontend unit test for the `ExpandableList` cap. - Manual check on a table cell with 40 related records: exactly 10 chips mount inline (down from 40), no console errors, chips still clickable and the overflow count reflects the true total. |
||
|
|
0e22ae0521 |
feat: create calendar events on Google and Microsoft accounts (#22231)
## Context Twenty can import calendar events and send emails, but cannot create calendar events. This adds calendar event creation on connected **Google** and **Microsoft** accounts, mirroring the existing email-send architecture (`message-outbound-manager`). ## What it adds The capability is exposed three ways, all backed by the same composer → driver → persist pipeline: - **GraphQL mutation** `createCalendarEvent` (metadata API) - **AI agent tool** `create_calendar_event` (flows to MCP automatically), gated by a new `CREATE_CALENDAR_EVENT_TOOL` permission flag - **Workflow builder node** "Create Calendar Event" in the **Core** section, with a full settings form (variable interpolation supported) CalDAV/IMAP is intentionally out of scope for now (different long pole). ## Design notes - **Reuse over reinvention** — the created event is run through the existing inbound formatters (`formatGoogleCalendarEvents` / `formatMicrosoftCalendarEvents`) and persisted immediately via the existing `CalendarSaveEventsService`, so it appears in Twenty right away and is reconciled by the next provider sync (dedup on external id). Persistence is best-effort. - **OAuth scopes** — Google already requests `calendar.events` (read+write), so no change there. Microsoft moves `Calendars.Read` → `Calendars.ReadWrite`; existing Microsoft accounts must re-consent (surfaced as a clear "reconnect" error via a missing-scope check). - **Deliberate invitation semantics** — `sendInvitations` is off by default. When off, the event is created with **no attendees** on either provider, so creating an event never silently emails external people. When on, attendees are attached and notified (Google `sendUpdates: all`, Microsoft's default). This sidesteps Microsoft Graph having no per-request suppression. - **Timezone correctness** — Microsoft Graph interprets `dateTime` as wall-clock in the supplied `timeZone` and ignores the offset, so the absolute instant is converted to its wall-clock form before sending (Google honors the offset directly). Both providers end up scheduling the same instant. - **Conferencing** — optional Google Meet (`conferenceData.createRequest`, with a follow-up `events.get` to resolve the async link) / Microsoft Teams (`isOnlineMeeting`). - Attendees are a comma-separated string everywhere (tool input, GraphQL DTO, workflow input), consistent with `send_email` recipients; the composer parses to its internal list. ## Test plan - **Unit**: 45 tests covering the composer (validation, all-day boundaries, offset enforcement, timezone, scope checks, default-account resolution), both provider drivers, the dispatcher, and the workflow step-log builder. - **Integration**: `createCalendarEvent` on the `/metadata` API fails closed with a structured error for a non-existent account (the auth/ownership/validation path that doesn't require provider mocking). - **Manual**: verified the workflow node appears in the Core section, the settings form renders and round-trips (edit → autosave → reload), and the live mutation returns a structured failure for a bogus account. ## Open question for reviewers The metadata mutation `createCalendarEvent` shares a name with the core schema's auto-generated `createCalendarEvent(data:)` CRUD mutation for the CalendarEvent object — they live on different endpoints (`/metadata` vs `/graphql`) so there's no runtime conflict, but it's a potential point of confusion for API consumers. Happy to rename (e.g. `createCalendarEventOnConnectedAccount`) if preferred. ## Out of scope / follow-ups - CalDAV/IMAP support - Event update/delete and recurrence - Existing Microsoft accounts need re-consent for the widened scope <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22231?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: neo773 <neo773@protonmail.com> |
||
|
|
3525187321 |
fix(ai) - fixes (#22227)
- ai chat author fix (before : "workflow", after : "user") - https://discord.com/channels/1130383047699738754/1496872385687584768 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22227?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
e747bc3e42 |
Add backfill installation feature for pre-installed apps (#22199)
## After <img width="1070" height="514" alt="image" src="https://github.com/user-attachments/assets/9cbd2ff5-1678-4c2f-84da-074568f37c51" /> ## Summary Adds the ability to backfill application installations across all existing workspaces. This allows admins to retroactively install a pre-registered application on every active and suspended workspace through a background job, making the feature idempotent and non-blocking. ## Key Changes - **Backend Service**: Added `backfillApplicationOnAllWorkspaces()` method to `PreInstalledAppsService` that: - Validates the application registration exists - Iterates through all workspaces using `WorkspaceIteratorService` - Installs the app on each workspace - Swallows `APP_ALREADY_INSTALLED` errors for idempotency - Logs success/failure counts - **Background Job**: Created `BackfillApplicationInstallationJob` to process backfill requests asynchronously via the message queue - **GraphQL Mutation**: Added `backfillApplicationInstallation` mutation to `AdminPanelResolver` that: - Validates the application registration exists - Enqueues the background job - Returns immediately without blocking the request - **UI Components**: Enhanced `SettingsAdminApplicationRegistrationGeneralToggles` with: - New "Pre-install on new workspaces" toggle for the `isPreInstalled` flag - "Backfill on all workspaces" button with confirmation modal - Loading state and success/error snack bar feedback - **Data Model**: Added `isPreInstalled` field to `UpdateApplicationRegistrationPayload` input type - **Tests**: Added comprehensive unit tests for `PreInstalledAppsService.backfillApplicationOnAllWorkspaces()` covering: - Missing registration validation - Successful multi-workspace installation - Idempotent handling of already-installed errors - Proper error propagation for unexpected failures ## Implementation Details The backfill operation is designed to be: - **Idempotent**: Already-installed apps are skipped without error - **Non-blocking**: Runs as a background job via message queue - **Resilient**: Per-workspace failures don't block other installations - **Observable**: Logs aggregated success/failure counts for monitoring <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22199?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> |
||
|
|
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. --> |
||
|
|
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. |
||
|
|
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. --> |
||
|
|
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. --> |
||
|
|
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. --> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
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" /> |
||
|
|
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. --> |
||
|
|
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> |
||
|
|
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.
|
||
|
|
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. -->
|
||
|
|
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> |
||
|
|
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. |
||
|
|
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 |
||
|
|
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. --> |
||
|
|
94dbcc27a9 |
feat(billing): embed credit card form in the add-card trial-end modal (#22125)
## Context
When a trialing workspace (trial without a credit card) clicks **Add
Credit Card** from the "End trial period" banner or the AI-chat
usage-limit banner, the modal currently redirects the browser to
Stripe's hosted billing portal to collect the card. Since we already
embed the Stripe Payment Element in onboarding, this brings the same
in-app experience to the trial-end modal so the whole flow stays inside
Twenty.
## Why the onboarding flow couldn't be reused as-is
The onboarding embed (`createSubscriptionPaymentIntent` /
`SubscriptionPaymentForm`) **creates a new subscription** with
`payment_behavior: 'default_incomplete'`. In the trial-end case the
customer **already has a trialing subscription**, so that path throws
`BILLING_SUBSCRIPTION_INVALID`. The correct primitive here is a
**SetupIntent** against the existing customer: collect + save the card,
then end the trial.
A standalone SetupIntent attaches the card to the customer but does
**not** make it the default (the Stripe portal used to do that for us),
so the trial-end invoice would have no payment method. The backend now
backfills the customer default before charging.
## Changes
**Backend**
- `StripeCustomerService`: `createSetupIntent()` for an existing
customer, and `ensureDefaultPaymentMethod()` which sets the customer
default only when none is already set (won't clobber a portal-chosen
default).
- `BillingPortalWorkspaceService.createPaymentMethodSetupIntent()`:
returns a SetupIntent client secret for the current non-canceled
subscription's customer.
- `BillingSubscriptionService.endTrialPeriod()`: ensures a default
payment method before `trial_end: 'now'`.
- New `createBillingPaymentMethodSetupIntent` mutation +
`BillingSetupIntent` DTO; SDK schema snapshot synced.
**Frontend**
- `AddPaymentMethodForm`: Stripe Elements (`mode: 'setup'`), confirms
with `redirect: 'if_required'` so the common card case stays in-app; 3DS
still redirects and is finished by the existing
`EndTrialAfterPaymentMethodEffect`.
- `AddCreditCardModal`: hosts the embedded form.
- Both trial-end banners (`InformationBannerEndTrialPeriod`,
`AIChatNoMoreBillingCreditsBanner`) open the embedded modal instead of
redirecting when no card is on file; the AI-chat path preserves its
thread context in the 3DS return URL.
## Flow
1. User clicks **Add Credit Card** → embedded modal opens.
2. Card entered → `createBillingPaymentMethodSetupIntent` →
`confirmSetup({ redirect: 'if_required' })`.
3. Non-3DS: confirms inline → `endSubscriptionTrialPeriod` →
subscription active, no redirect.
4. 3DS: redirects to `?startSubscriptionAfterPaymentMethod=true` →
existing effect finishes activation.
5. Self-hosted instances without a Stripe publishable key fall back to
the existing portal redirect (the form renders an unavailable state).
## Notes for reviewers
- The metadata GraphQL types were regenerated by hand (codegen needs a
live `/metadata` server, which wasn't available in the authoring
environment); a `graphql:generate --configuration=metadata` run against
a live backend should be a no-op.
- Local `typecheck`/`lint` could not be run in the authoring environment
(dependency install was blocked); relying on CI to validate.
- Scope is intentionally limited to the two trial-end banner modals. The
Settings → Billing "update payment method" link still uses the Stripe
portal.
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/22125?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. -->
|
||
|
|
179ab2f066 |
feat(billing): clarify subscription, credits, and add-credits sections (#22126)
## What & why Redesigns the **content** of the workspace billing settings page so the numbers that matter are surfaced clearly. Strictly reuses existing design-system components — no new UI primitives, no color/font changes, same section structure. All pricing values are pulled from the pricing config (nothing hardcoded). ## Changes **Subscription card** (`SettingsBillingSubscriptionInfo.tsx`) - **Seats** row shows per-seat pricing inline, e.g. `16 × $25 / seat / mo` (interval-aware unit). - **"Credits by period" → "Monthly credits"** (interval-aware: "Yearly credits" on yearly plans). - New **"Total per month"** row (→ "Total per year" on yearly) with a helper line via the existing `Label`: `$400 seats + $2,000 credits · next charge <date>`. The total is computed at the billing interval, so it equals the actual next charge. - **"Switch to Yearly · save 20%"** — discount computed from real monthly vs. yearly config prices (only appended when > 0). **Credit usage card** (`SettingsBillingCreditsSection.tsx`) - Denominator stays total available (allocation + rollover); figures shown in full (`3,856`, not `3.86k`). - "Base Credits" → **"This month's credits"** + a **"Rolled over"** row rendered as `+1,856`. - Adds a blue `Info` note stating the rollover rule: *"Unused credits roll over, up to 2× your plan's credits (max 4,000)."* — verified against the backend cap (`billing-credit-rollover.service.ts`: `rolloverAmount = min(unused, tierQuantity)`). **Add-credits section** (`ResourceCreditPriceSelector.tsx`) - "Resource credits" → **"Add monthly credits"** with a description clarifying it stacks on the plan and adjusts the bill. - On selection, shows the resulting **new total** and **new rollover cap** (2× the new allocation). **Shared logic** - New `useBillingSubscriptionCost` hook centralizes the seats/credits/total math so the subscription card and the add-credits selector stay consistent. - Yearly-discount calculation lives in `useBillingWording` (`getYearlyDiscountPercent`) next to the existing price logic. All new figures degrade gracefully — the Total row and the add-credits summary only render when fully computable, so trial/edge states never show partial numbers. ## Testing ⚠️ `nx typecheck` / `lint` could **not** be run in the authoring environment (dependency install couldn't complete due to flaky network). The changes were reviewed manually against the generated GraphQL types (`BillingPlanKey` = `{ENTERPRISE, PRO}`, `unitAmount: number`, `creditAmount: Maybe<number>`, nullable `interval`/`currentPeriodEnd`/`quantity` — all guarded) and a 4-angle cleanup pass (reuse/simplify/efficiency/altitude). **Please let CI run typecheck + lint to confirm.** https://claude.ai/code/session_01GNhCHPfD1SRzAiBACXGCf7 --- _Generated by [Claude Code](https://claude.ai/code/session_01GNhCHPfD1SRzAiBACXGCf7)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22126?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. --> |
||
|
|
e22d31c553 |
Close side panel after command menu actions (#22118)
## What changed - Close the command side panel after eligible headless command-menu actions complete or are confirmed. - Cover delete, restore, permanent destroy, create view, import, see deleted, and hide deleted record commands. - Keep create-record behavior unchanged so a newly created record can still open in the side panel. - Add focused Jest coverage for the affected command components and the create-record exception. # Before https://github.com/user-attachments/assets/36632578-3cab-47db-8f6d-350cd6fce683 # After https://github.com/user-attachments/assets/c9dba19b-7f4a-44aa-ae7f-57660c5a1e96 --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
02120aac42 |
Add v2 create-workspace onboarding screen (#22075)
https://github.com/user-attachments/assets/30d69db2-ef50-48b5-8233-d9a36511b5e8 Builds the second step of the new onboarding flow on top of #22027: the v2 "Create your workspace" screen, shown inside `/welcome-v2` at the `WorkspaceCreation` step. What changed: - New `SignInUpV2Header` (back chevron + Twenty logo) and `SignInUpWorkspaceCreationFormV2` (left-aligned title/subtitle, logo upload, Name + Subdomain fields, "Create workspace"), wired into `SignInUpV2` for the workspace-creation step. - When a subdomain is taken, a box now lists 3 server-verified-available alternatives. Backend `SubdomainAvailabilityDTO` returns `suggestedSubdomains` via a new `findAvailableSubdomains` helper. - The shared `useWorkspaceSubdomainField` hook is extended additively (new `suggestions` + `applySuggestionValue`) so the v1 `/welcome` screen is untouched. Reviewer notes: - `generated-metadata/graphql.ts` was hand-patched (metadata codegen needs a running server). - Storybook: `Pages/Auth/SignInUpV2 → WorkspaceCreation`. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22075?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. --> |
||
|
|
33d2441a93 |
fix(ux): input error messages no longer overlap adjacent fields in forms (#21886)
## Context `InputErrorHelper` used `position: absolute` which took the error message out of normal document flow. In multi-field grid layouts (e.g. the HTTP Request workflow node editor), this caused the error text to render on top of the input below it instead of pushing it down. ## Solution Removed `position: absolute` from `InputErrorHelper`. The error message now participates in normal document flow and pushes subsequent content downward, as expected in modern forms. ## Test plan - [x] Open the HTTP Request node editor, fill in a field incorrectly — error message appears below the field without overlapping the next input - [x] Verify single-field forms still display error messages correctly 🤖 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/21886?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> |
||
|
|
6ee5413951 |
chore(vite): replace vite-tsconfig-paths with resolve.tsconfigPaths (#22100)
### Summary Migrates main monorepo packages from the `vite-tsconfig-paths` plugin to vite’s built-in path resolution. Vite 8 showing this warning when the plugin is detected: > The plugin "vite-tsconfig-paths" is detected. Vite now supports tsconfig paths resolution natively via the resolve.tsconfigPaths option. You can remove the plugin and set resolve.tsconfigPaths: true in your Vite config instead. ### References - https://vite.dev/config/shared-options#resolve-tsconfigpaths - https://vite.dev/guide/features#paths - https://github.com/vitejs/vite/pull/21781 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22100?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> |
||
|
|
8830ef89bd |
chore(deps-dev): bump @storybook/addon-docs from 10.3.4 to 10.4.6 (#22110)
Bumps [@storybook/addon-docs](https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs) from 10.3.4 to 10.4.6. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/storybookjs/storybook/releases">@storybook/addon-docs's releases</a>.</em></p> <blockquote> <h2>v10.4.6</h2> <h2>10.4.6</h2> <ul> <li>CSF: Allow partial globals overrides in story and meta annotations - <a href="https://redirect.github.com/storybookjs/storybook/pull/34985">#34985</a>, thanks <a href="https://github.com/TheSeydiCharyyev"><code>@TheSeydiCharyyev</code></a>!</li> <li>Dependencies: Upgrade esbuild - <a href="https://redirect.github.com/storybookjs/storybook/pull/35157">#35157</a>, thanks <a href="https://github.com/Kakadus"><code>@Kakadus</code></a>!</li> </ul> <h2>v10.4.5</h2> <h2>10.4.5</h2> <ul> <li>Core: Rework AI checklist feature gate - <a href="https://redirect.github.com/storybookjs/storybook/pull/35053">#35053</a>, thanks <a href="https://github.com/Sidnioulz"><code>@Sidnioulz</code></a>!</li> <li>Preview: Stop mixed CSF3+4 stories getting core annotations injected twice - <a href="https://redirect.github.com/storybookjs/storybook/pull/35094">#35094</a>, thanks <a href="https://github.com/JReinhold"><code>@JReinhold</code></a>!</li> </ul> <h2>v10.4.4</h2> <h2>10.4.4</h2> <ul> <li>Telemetry: Add timeout to event-log POST to prevent build hang - <a href="https://redirect.github.com/storybookjs/storybook/pull/35085">#35085</a>, thanks <a href="https://github.com/badams"><code>@badams</code></a>!</li> </ul> <h2>v10.4.3</h2> <h2>10.4.3</h2> <ul> <li>Addon Docs: Fix Primary and Controls blocks not rendering in custom MDX pages - <a href="https://redirect.github.com/storybookjs/storybook/pull/34496">#34496</a>, thanks <a href="https://github.com/NYCU-Chung"><code>@NYCU-Chung</code></a>!</li> <li>Core: Respect !dev tag on MDX docs in sidebar - <a href="https://redirect.github.com/storybookjs/storybook/pull/35031">#35031</a>, thanks <a href="https://github.com/JReinhold"><code>@JReinhold</code></a>!</li> <li>React: Add support for resolving subcomponents attached as properties of a parent component - <a href="https://redirect.github.com/storybookjs/storybook/pull/34967">#34967</a>, thanks <a href="https://github.com/yatishgoel"><code>@yatishgoel</code></a>!</li> <li>UI: Prevent docs page scroll reset on HMR re-render - <a href="https://redirect.github.com/storybookjs/storybook/pull/35021">#35021</a>, thanks <a href="https://github.com/LongTangGithub"><code>@LongTangGithub</code></a>!</li> </ul> <h2>v10.4.2</h2> <h2>10.4.2</h2> <ul> <li>Bug: Fix Windows command resolution for non-Node package managers - <a href="https://redirect.github.com/storybookjs/storybook/pull/33534">#33534</a>, thanks <a href="https://github.com/copilot-swe-agent"><code>@copilot-swe-agent</code></a>!</li> <li>Build: Upgrade type-fest to latest version 5.6.0 - <a href="https://redirect.github.com/storybookjs/storybook/pull/34791">#34791</a>, thanks <a href="https://github.com/tobiasdiez"><code>@tobiasdiez</code></a>!</li> <li>CSF: Fix parsing of string literal export names - <a href="https://redirect.github.com/storybookjs/storybook/pull/34901">#34901</a>, thanks <a href="https://github.com/shilman"><code>@shilman</code></a>!</li> <li>Publish: Add npm provenance attestations - <a href="https://redirect.github.com/storybookjs/storybook/pull/34936">#34936</a>, thanks <a href="https://github.com/copilot-swe-agent"><code>@copilot-swe-agent</code></a>!</li> </ul> <h2>v10.4.1</h2> <h2>10.4.1</h2> <ul> <li>Angular: Detect model() signal outputs (type inference + compodoc autodocs + runtime binding) - <a href="https://redirect.github.com/storybookjs/storybook/pull/34833">#34833</a>, thanks <a href="https://github.com/valentinpalkovic"><code>@valentinpalkovic</code></a>!</li> <li>Build: Upgrade type-fest to latest version 5.6.0 - <a href="https://redirect.github.com/storybookjs/storybook/pull/34791">#34791</a>, thanks <a href="https://github.com/tobiasdiez"><code>@tobiasdiez</code></a>!</li> <li>CLI: Run `npx expo install --fix` after init for Expo projects - <a href="https://redirect.github.com/storybookjs/storybook/pull/34803">#34803</a>, thanks <a href="https://github.com/ndelangen"><code>@ndelangen</code></a>!</li> <li>CLI: Support `peerDependencies` in framework detection for component libraries - <a href="https://redirect.github.com/storybookjs/storybook/pull/34516">#34516</a>, thanks <a href="https://github.com/zhyd1997"><code>@zhyd1997</code></a>!</li> <li>Next.js: Add useLinkStatus mock to next/link export mock - <a href="https://redirect.github.com/storybookjs/storybook/pull/34593">#34593</a>, thanks <a href="https://github.com/philwolstenholme"><code>@philwolstenholme</code></a>!</li> <li>Vue3: Specify a specific version for non-dev dependency - <a href="https://redirect.github.com/storybookjs/storybook/pull/34794">#34794</a>, thanks <a href="https://github.com/ScopeyNZ"><code>@ScopeyNZ</code></a>!</li> </ul> <h2>v10.4.0</h2> <h2>10.4.0</h2> <blockquote> <p><em>AI-assisted setup, change-aware review, and stronger framework support</em></p> </blockquote> <p>Storybook 10.4 contains hundreds of fixes and improvements including:</p> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md">@storybook/addon-docs's changelog</a>.</em></p> <blockquote> <h2>10.4.6</h2> <ul> <li>CSF: Allow partial globals overrides in story and meta annotations - <a href="https://redirect.github.com/storybookjs/storybook/pull/34985">#34985</a>, thanks <a href="https://github.com/TheSeydiCharyyev"><code>@TheSeydiCharyyev</code></a>!</li> <li>Dependencies: Upgrade esbuild - <a href="https://redirect.github.com/storybookjs/storybook/pull/35157">#35157</a>, thanks <a href="https://github.com/Kakadus"><code>@Kakadus</code></a>!</li> </ul> <h2>10.4.5</h2> <ul> <li>Core: Rework AI checklist feature gate - <a href="https://redirect.github.com/storybookjs/storybook/pull/35053">#35053</a>, thanks <a href="https://github.com/Sidnioulz"><code>@Sidnioulz</code></a>!</li> <li>Preview: Stop mixed CSF3+4 stories getting core annotations injected twice - <a href="https://redirect.github.com/storybookjs/storybook/pull/35094">#35094</a>, thanks <a href="https://github.com/JReinhold"><code>@JReinhold</code></a>!</li> </ul> <h2>10.4.4</h2> <ul> <li>Telemetry: Add timeout to event-log POST to prevent build hang - <a href="https://redirect.github.com/storybookjs/storybook/pull/35085">#35085</a>, thanks <a href="https://github.com/badams"><code>@badams</code></a>!</li> </ul> <h2>10.4.3</h2> <ul> <li>Addon Docs: Fix Primary and Controls blocks not rendering in custom MDX pages - <a href="https://redirect.github.com/storybookjs/storybook/pull/34496">#34496</a>, thanks <a href="https://github.com/NYCU-Chung"><code>@NYCU-Chung</code></a>!</li> <li>Core: Respect !dev tag on MDX docs in sidebar - <a href="https://redirect.github.com/storybookjs/storybook/pull/35031">#35031</a>, thanks <a href="https://github.com/JReinhold"><code>@JReinhold</code></a>!</li> <li>React: Add support for resolving subcomponents attached as properties of a parent component - <a href="https://redirect.github.com/storybookjs/storybook/pull/34967">#34967</a>, thanks <a href="https://github.com/yatishgoel"><code>@yatishgoel</code></a>!</li> <li>UI: Prevent docs page scroll reset on HMR re-render - <a href="https://redirect.github.com/storybookjs/storybook/pull/35021">#35021</a>, thanks <a href="https://github.com/LongTangGithub"><code>@LongTangGithub</code></a>!</li> </ul> <h2>10.4.2</h2> <ul> <li>Bug: Fix Windows command resolution for non-Node package managers - <a href="https://redirect.github.com/storybookjs/storybook/pull/33534">#33534</a>, thanks <a href="https://github.com/copilot-swe-agent"><code>@copilot-swe-agent</code></a>!</li> <li>Build: Upgrade type-fest to latest version 5.6.0 - <a href="https://redirect.github.com/storybookjs/storybook/pull/34791">#34791</a>, thanks <a href="https://github.com/tobiasdiez"><code>@tobiasdiez</code></a>!</li> <li>CSF: Fix parsing of string literal export names - <a href="https://redirect.github.com/storybookjs/storybook/pull/34901">#34901</a>, thanks <a href="https://github.com/shilman"><code>@shilman</code></a>!</li> <li>Publish: Add npm provenance attestations - <a href="https://redirect.github.com/storybookjs/storybook/pull/34936">#34936</a>, thanks <a href="https://github.com/copilot-swe-agent"><code>@copilot-swe-agent</code></a>!</li> </ul> <h2>10.4.1</h2> <ul> <li>Angular: Detect model() signal outputs (type inference + compodoc autodocs + runtime binding) - <a href="https://redirect.github.com/storybookjs/storybook/pull/34833">#34833</a>, thanks <a href="https://github.com/valentinpalkovic"><code>@valentinpalkovic</code></a>!</li> <li>Build: Upgrade type-fest to latest version 5.6.0 - <a href="https://redirect.github.com/storybookjs/storybook/pull/34791">#34791</a>, thanks <a href="https://github.com/tobiasdiez"><code>@tobiasdiez</code></a>!</li> <li>CLI: Run <code>npx expo install --fix</code> after init for Expo projects - <a href="https://redirect.github.com/storybookjs/storybook/pull/34803">#34803</a>, thanks <a href="https://github.com/ndelangen"><code>@ndelangen</code></a>!</li> <li>CLI: Support <code>peerDependencies</code> in framework detection for component libraries - <a href="https://redirect.github.com/storybookjs/storybook/pull/34516">#34516</a>, thanks <a href="https://github.com/zhyd1997"><code>@zhyd1997</code></a>!</li> <li>Next.js: Add useLinkStatus mock to next/link export mock - <a href="https://redirect.github.com/storybookjs/storybook/pull/34593">#34593</a>, thanks <a href="https://github.com/philwolstenholme"><code>@philwolstenholme</code></a>!</li> <li>Vue3: Specify a specific version for non-dev dependency - <a href="https://redirect.github.com/storybookjs/storybook/pull/34794">#34794</a>, thanks <a href="https://github.com/ScopeyNZ"><code>@ScopeyNZ</code></a>!</li> </ul> <h2>10.4.0</h2> <blockquote> <p><em>AI-assisted setup, change-aware review, and stronger framework support</em></p> </blockquote> <p>Storybook 10.4 contains hundreds of fixes and improvements including:</p> <ul> <li>🤖 Agentic Setup: New CLI workflow for AI-assisted Storybook setup and onboarding</li> <li>🔍 Change review: Sidebar filtering to highlight new, modified, and related stories based on git changes</li> <li>🧭 Sidebar review tools: Status filtering, URL-persisted filters, and clearer review signals in the sidebar</li> <li>⚛️ TanStack React: New <code>@storybook/tanstack-react</code> framework with routing and server function support</li> <li>🧩 React MCP: Faster, more accurate component docgen powered by the TypeScript Language Server</li> <li>📱 React Native: Zero config RN project initialization</li> <li>🤝 Sharing: Easily publish and share your local Storybook with teammates, powered by Chromatic</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/storybookjs/storybook/commit/5496a4270da7f3a8e0203185792685cba671fdc5"><code>5496a42</code></a> Bump version from "10.4.5" to "10.4.6" [skip ci]</li> <li><a href="https://github.com/storybookjs/storybook/commit/48e7b20074222ed926d14fb6c678c2edfc86ee7b"><code>48e7b20</code></a> Bump version from "10.4.4" to "10.4.5" [skip ci]</li> <li><a href="https://github.com/storybookjs/storybook/commit/5adebe753f29d414d1e214e935c94d6e5451861f"><code>5adebe7</code></a> Bump version from "10.4.3" to "10.4.4" [skip ci]</li> <li><a href="https://github.com/storybookjs/storybook/commit/624e6187fd462e56719cbd80c1b4bfb67b68fc89"><code>624e618</code></a> Bump version from "10.4.2" to "10.4.3" [skip ci]</li> <li><a href="https://github.com/storybookjs/storybook/commit/c89882282295be3bc05b3a366916c53d7a499841"><code>c898822</code></a> Merge pull request <a href="https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs/issues/34496">#34496</a> from NYCU-Chung/fix/docs-blocks-custom-mdx</li> <li><a href="https://github.com/storybookjs/storybook/commit/c920fd08c79c57879fa2ddb4e8538e1684c71ec2"><code>c920fd0</code></a> Merge pull request <a href="https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs/issues/35021">#35021</a> from LongTangGithub/fix/docs-hmr-scroll-to-top</li> <li><a href="https://github.com/storybookjs/storybook/commit/1750494e9f36748b2d89335e77f23f125fc5ec78"><code>1750494</code></a> Merge pull request <a href="https://github.com/storybookjs/storybook/tree/HEAD/code/addons/docs/issues/35031">#35031</a> from storybookjs/jeppe/fix-mdx-no-dev-tag</li> <li><a href="https://github.com/storybookjs/storybook/commit/298dea20c6370e5c670178d88a79fc9e9ff436b2"><code>298dea2</code></a> Bump version from "10.4.1" to "10.4.2" [skip ci]</li> <li><a href="https://github.com/storybookjs/storybook/commit/cc19ae1a2145e8f7cda8dc869f1b90d5346dcedb"><code>cc19ae1</code></a> Bump version from "10.4.0" to "10.4.1" [skip ci]</li> <li><a href="https://github.com/storybookjs/storybook/commit/f8c16d115cfcf0f79125b358266c37e5343bb70d"><code>f8c16d1</code></a> Bump version from "10.4.0-beta.0" to "10.4.0" [skip ci]</li> <li>Additional commits viewable in <a href="https://github.com/storybookjs/storybook/commits/v10.4.6/code/addons/docs">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22110?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: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
9ed0d85954 |
Update workspace domain card layout (#22108)
## What changed - Render the Workspace domain cards side by side instead of stacked. - Keep both domain cards full width within the row. - Rename the section title from `Workspace Domain` to `Workspace domain`. - Use the `www` globe icon for Subdomain while keeping the standard globe for Custom Domain. - Export `IconWorldWww` from `twenty-ui/icon` for front-end consumers. <img width="1029" height="220" alt="image" src="https://github.com/user-attachments/assets/83ad8a29-d63c-42bb-9233-7754d1e7adf7" /> ## Why This matches the updated settings design for the Workspace domain section and makes the Subdomain and Custom Domain options scan as sibling actions. ## Validation - Ran focused `oxlint` and `oxfmt` checks on edited files. - Built `twenty-ui` successfully. - Ran `twenty-front` typecheck successfully. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22108?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. --> |
||
|
|
b5958fb331 |
Enforce server route app configuration requirements (#22091)
## Summary
This PR enforces that applications exposing server route logic functions
must be claimed (have an owner workspace) and installed on that owner
workspace to be considered "configured". This ensures server route
resolvers have a valid workspace context to execute in.
## Key Changes
- **ApplicationRegistrationVariableService**: Enhanced
`isConfiguredBatch()` to check server route configuration in addition to
required variables
- Added `ApplicationEntity` repository injection to track app
installations
- Implemented `isServerRouteConfigured()` private method that validates:
- If app exposes server route logic functions, it must have an owner
workspace
- If it has an owner workspace, it must be installed on that workspace
- Added comprehensive test suite covering all configuration scenarios
- **ServerRouteTriggerService**: Removed feature flag check
(`IS_SERVER_LOGIC_FUNCTION_ENABLED`)
- Deleted `TwentyConfigService` dependency
- Removed feature disabled exception handling
- Server route triggers are now always enabled (gated by app
configuration instead)
- **Configuration**: Removed `IS_SERVER_LOGIC_FUNCTION_ENABLED` config
variable from `ConfigVariables`
- **Exception handling**: Removed `FEATURE_DISABLED` exception code from
`ServerRouteTriggerExceptionCode`
- **UI & Documentation**: Updated messaging and docs to reflect that
server route apps require claiming and installation on owner workspace
## Implementation Details
- Server route configuration is checked alongside required variable
validation in `isConfiguredBatch()`
- Uses efficient batch queries with `Promise.all()` to fetch variables,
registrations, and installations in parallel
- Installs are tracked via a Set of `${registrationId}:${workspaceId}`
keys for O(1) lookup
- Apps without server route functions are unaffected by this change
https://claude.ai/code/session_01Ub3K25p2q4XE1LW1LGJbkG
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22091?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. -->
|
||
|
|
7d3cd5ed00 |
feat(front): add search to the language picker (#22095)
## What Adds a search bar to the **Settings → Experience → Language** picker, and makes languages searchable across languages. Each option is matched against: - its displayed label (the name in the current UI language) - its name **in English** — typing `chinese` finds "Chinois — Simplifié" - its **native name** — typing `中文` finds the same option Matching is also accent-insensitive (`francais` finds "Français"). ## How - The shared `Select` already supports search via `withSearchInput` (used by the currency/country pickers) — the picker just opts in. - Cross-language matching uses the platform `Intl.DisplayNames` API to derive each language's English and native names — no hardcoded translation tables, no extra requests. - A generic optional `searchKeywords` field on `SelectOption` lets the `Select` filter match synonyms on top of the label; the filter now runs through the existing `normalizeSearchText`, hence the accent-insensitivity. Behavior is unchanged for every existing `Select` (strict superset for ASCII labels). ## Test - `nx typecheck` / `nx lint` pass for `twenty-front` and `twenty-ui`. - Open the Language dropdown and try `chinese`, `中文`, or `francais`. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22095?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. --> |