feac2df21675b6f78a030d64421146161fc1cf4f
10806 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
feac2df216 |
chore(website): remove partners marketplace route lint guard (#22121)
## Summary Follow-up to #22120. Removes the `check-partners-marketplace-routes.mjs` lint guard and its `project.json` wiring — the profile fix is just `force-dynamic` on the page; the extra script is not needed. ## Changes - Delete `packages/twenty-website/scripts/check-partners-marketplace-routes.mjs` - Restore `project.json` lint command to run only `check-conventions.mjs` (as before #22120) ## Context The guard was added in #22120 but the removal commit did not land before merge. This PR cleans that up. No runtime behavior change. ## Test plan - [ ] `nx lint twenty-website` (or CI) passes without the removed script <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22121?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
b841b92cb5 |
fix(billing): widen embedded add-card modal and align its buttons (#22141)
## Context Follow-up to #22125. The embedded "Add your credit card" modal had two visual issues: 1. **Too narrow** — it used `narrowWidth` (~368px), which capped the Stripe Payment Element too tightly (cramped card fields, country dropdown, and the Google Pay / Cash App / bank-account accordions). 2. **Mismatched buttons** — the submit used `MainButton` (onboarding's filled CTA) while Cancel used `Button` (the settings/`ConfirmationModal` outlined style), so the two never matched. No new components or behavior — purely prop/component swaps within existing primitives. ## Changes - **`AddCreditCardModal`**: drop `narrowWidth`, use `size="medium"` so the Payment Element has room. - **`AddPaymentMethodForm`**: replace `MainButton` with `Button` (`variant="secondary"`, `accent="blue"`, `fullWidth`), using `Button`'s built-in `isLoading` spinner. Both buttons are now full-width outlined `Button`s, matching the sibling `StartSubscriptionConfirmationModal` (which renders `ConfirmationModal`'s blue-accented confirm + plain cancel). Removed the now-unused `MainButton`/`Loader` imports and `StyledButtonContainer`. ## Notes - Frontend-only; no GraphQL/schema/codegen impact. https://claude.ai/code/session_01VU7SfrSgaYWr2AhVL8DMfu --- _Generated by [Claude Code](https://claude.ai/code/session_01VU7SfrSgaYWr2AhVL8DMfu)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22141?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
4277f8f04f | fix(sdk): render CLI OAuth success page on localhost (twenty dev) (#22131) | ||
|
|
7a23a3d851 |
i18n - docs translations (#22142)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22142?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
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. --> |
||
|
|
19bdf2122a |
docs(user-guide): remove unsupported Between operator for Date filters (#22136)
## What The Filters & Sorting user guide lists a **Between** operator for **Date** fields, but this operator is not currently supported by the product. This removes it from the Date operators table. | Field Type | Before | After | |---|---|---| | Date | Equals, Before, After, **Between**, Is empty | Equals, Before, After, Is empty | ## Why Avoids documenting a capability that does not currently exist. The shared `ViewFilterOperand` enum does not define an `IS_BETWEEN` operand, and the Date filter operands map does not include a Between operator. Related to #20932. ## Notes - Documentation-only change. - Only the canonical English source is edited. - Localized copies under `packages/twenty-docs/l/*` are expected to be regenerated by the existing docs translation workflow. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22136?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. --> |
||
|
|
811fecc119 |
chore(apps): move call-recorder to public, remove stale meeting-bot duplicate (#22132)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22132?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. --> |
||
|
|
14e4656fe3 |
Update TypeScript moduleResolution to bundler (#22127)
## Summary Updates the TypeScript `moduleResolution` configuration from `"node"` to `"bundler"` across template and example tsconfig files. This aligns with modern TypeScript best practices for bundler-based projects. ## Changes - Updated `moduleResolution` setting in `packages/create-twenty-app/src/constants/template/tsconfig.json` - Updated `moduleResolution` setting in `packages/twenty-apps/examples/hello-world/tsconfig.json` - Updated `moduleResolution` setting in `packages/twenty-apps/examples/postcard/tsconfig.json` ## Details The `"bundler"` module resolution strategy is the recommended approach for projects using modern bundlers (Vite, Webpack, etc.) as it provides better compatibility with ESM and package.json exports field resolution. This change ensures that new projects created from the template and example applications follow current TypeScript best practices. https://claude.ai/code/session_01XBdmaN1bpnE7DiH1XwBuX4 |
||
|
|
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> |
||
|
|
ad61d6d8a3 |
fix(server): dispatch each cron trigger exactly once (#22113)
## Problem
App/logic-function crons occasionally fire **twice, ~1 minute apart**.
The most visible symptom is a notification cron sending the same Discord
DM (or channel post) at e.g. `17:00` and again at `17:01`.
## Root cause
`CronTriggerCronJob` runs every minute (`* * * * *`) and re-dispatches
any logic function whose pattern is "due" according to `shouldRunNow`:
```ts
const diff = Math.abs(prevTriggerDate.getTime() - now.getTime());
return diff < rootCronIntervalMs; // 60_000
```
The detection window (`60_000ms`) is **equal to** the 60s tick interval.
So when a root tick drifts across a minute boundary (runs slightly
early/late, or BullMQ fires a catch-up), two adjacent ticks can both see
the *same* trigger as "within the last 60s" and each enqueue a
`LogicFunctionTriggerJob`. The dispatch isn't idempotent, so the
function runs twice.
## Fix
Make dispatch idempotent, keyed on the trigger itself:
- New `getMatchingTriggerTimestamp(pattern, now)` returns the epoch-ms
of the matched trigger (stable regardless of *when* within the window
the root job runs), or `null`. `shouldRunNow` now delegates to it —
behaviour unchanged.
- Before enqueuing, `CronTriggerCronJob` claims a
`logic-function-cron:{workspace}:{function}:{triggerTs}` key in the
`EngineLock` cache. A second tick that resolves to the same trigger
finds the key and skips.
Distinct triggers always have distinct timestamps (hence distinct keys),
so a later legitimate run is never suppressed. The TTL (2 min) only
needs to outlive the detection window.
## Notes
- `WorkflowCronTriggerCronJob` uses the same `shouldRunNow` pattern and
has the same latent double-dispatch; left out of this PR to keep it
focused, but the new helper makes the same guard a small follow-up.
- The cache `get`-then-`set` isn't atomic; for the observed failure mode
(ticks ~1 min apart, sequential) it's reliable. A Redis `SET NX` would
also close the rare concurrent-multi-instance race.
## Test plan
- [x] `should-run-now.utils.spec.ts` extended: two ticks within one
window resolve to the same timestamp; out-of-window and invalid patterns
return `null`. All 8 pass.
- [x] `oxlint --type-aware` + `oxfmt` clean on changed files.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22113?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. -->
|
||
|
|
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. --> |
||
|
|
fdea2d6e1e |
i18n - docs translations (#22122)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22122?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
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> |
||
|
|
500f441807 |
fix(website): force-dynamic partner profiles to stop OpenNext 404 cache (#22120)
## Summary Partner profile pages (`/partners/profile/[slug]`) returned **404 on every slug** on OpenNext/Cloudflare while `/partners/list` showed live partners from the same API. PR #21963 fixed the list with `export const dynamic = 'force-dynamic'` but only added `dynamicParams = true` on profiles. That is not sufficient on OpenNext — the Worker kept serving **cached prerendered 404s** even when `TWENTY_PARTNERS_API_KEY` was present at runtime. This PR mirrors the list page: **`force-dynamic` on the profile route**, plus a small lint guard so both marketplace routes stay dynamic. ## Root cause Partner data is fetched server-side from `https://partners.twenty.com/s/partners` using `TWENTY_PARTNERS_API_KEY`. That key is a **Wrangler runtime secret** (not in `dev.env` / `prod.env`, not available during CI build — by repo convention). | Route | Before | Behavior | |-------|--------|----------| | `/partners/list` | `force-dynamic` (#21963) | Fetches at request time on Worker → works | | `/partners/profile/[slug]` | static + `dynamicParams = true` | Build prewarm often empty; OpenNext served cached 404 | ## Fix - Add `export const dynamic = 'force-dynamic'` to `profile/[slug]/page.tsx` (keep `dynamicParams = true`). - Add `scripts/check-partners-marketplace-routes.mjs` — fails lint if list or profile drop `force-dynamic`. - Wire guard into `project.json` `lint` target (runs before existing `check-conventions.mjs`). **No infra changes.** We intentionally did not add a GitHub Actions secret for the API key — that would contradict the documented pattern (`wrangler secret put` only). ## Verification - [x] `node scripts/check-partners-marketplace-routes.mjs` → OK - [x] `npx jest src/partners-marketplace` → 36/36 pass - [x] Deployed to **dev** (`deploy-website`, env `dev`, ref `rk-partner-profile-404`) - [x] `curl -sI https://twenty-main.com/partners/profile/atlasprods-technologies-llp` → **HTTP 200** - [x] Browser: list → profile link loads ## Test plan - [ ] CI lint + tests green - [ ] After merge: deploy prod when ready (`environment: prod`, confirm `website`) - [ ] Spot-check `https://twenty.com/partners/profile/<slug>` → 200 ## Out of scope - Build-time `generateStaticParams` prewarm (would need a separate infra discussion; not required once profiles are `force-dynamic`) - Per-slug `/s/partner-by-slug` endpoint (optional perf follow-up) <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22120?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. --> |
||
|
|
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. --> |
||
|
|
cc21160d83 |
fix(server): scope server-route target dispatch to the resolver's application (#22101)
## Summary Security follow-up to #22002 (server-exposed logic functions). That PR's `ServerRouteTriggerService` resolved the **target** logic function by `(universalIdentifier, workspaceId)` alone, with no application scoping: ```ts // before const logicFunction = await this.logicFunctionRepository.findOne({ where: { universalIdentifier, workspaceId }, }); ``` Both values come straight from the resolver's return value. Because the only gate was "a function with that UID exists in that workspace", a resolver (owner-workspace code) could dispatch to a logic function belonging to a **different application**, or to a workspace where its own application is **not installed**, and read the target's return value back in the HTTP response (`buildRouteTriggerResponse(targetResult.data)`) — a cross-tenant / cross-application isolation break. The implementation this replaced (the deleted `server-webhook-trigger.service.ts`) enforced both checks: the app had to be installed in the target workspace, and the target function was scoped by `applicationId`. This PR restores that guarantee. ## Changes - **Scope the target dispatch to the resolver's `applicationRegistration`.** `handle()` captures `resolver.application.applicationRegistration.id` and threads it into the target `findOne` as `application: { applicationRegistrationId }` (joining the `application` relation). The target must belong to the same registration — which also guarantees the application is installed in the resolved workspace (no installed copy → no matching row). The resolver lookup itself is unchanged. - **Stop leaking raw internal error messages.** The `runFunction` catch block logged the raw executor/`Error.message` *and* returned it to the (unauthenticated) caller. It now logs the detail server-side and returns a generic, per-code message. - **Tests**: fixtures carry an `applicationRegistration.id`; new cases assert the target lookup is scoped to the resolver's registration, that a resolver not linked to a registration is rejected, and that a platform error returns the generic message instead of the raw internal text. Feature remains gated behind `IS_SERVER_LOGIC_FUNCTION_ENABLED` (default off). ## Test plan - [ ] `npx jest server-route-trigger` (verifying locally; environment dependency install was flaky) - [ ] `npx nx typecheck twenty-server` - [ ] `npx nx lint:diff-with-main twenty-server` https://claude.ai/code/session_014TNdRvQjjR8wN6MLTJ7rTE --- _Generated by [Claude Code](https://claude.ai/code/session_014TNdRvQjjR8wN6MLTJ7rTE)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22101?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. -->
|
||
|
|
ea3090e553 |
docs: rewrite billing credits page to clarify credit value and usage (#22099)
Rewrites the billing **Credits** doc so it actually explains what a credit is worth and how far it goes — the previous version listed bare credit counts and described AI cost only as "variable based on usage." What changed: - Leads with **1 credit = $1 of usage**, so the balance is easy to reason about. - Adds a "How far does a credit go?" table grounded in how billing actually works: standard workflow steps cost ~$0.0001 each, quick AI messages a fraction of a cent, while large multi-step agent tasks (e.g. configuring several objects) can run to ~$1 or more. - Explains that AI usage is metered at the model providers' published token rates and converted straight to credits — no marked-up internal rate. - Reframes allocation (5/mo, 50/yr) in terms of equivalent dollar usage, and tightens the rollover, monitoring, and top-up sections. Docs-only change; no code or behavior affected. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22099?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. --> |
||
|
|
6b460da622 |
Add backend primitive to credit a workspace's billing balance (#22094)
Adds `BillingCreditService.creditWorkspaceBalance({ workspaceId,
amountMicro })`, an internal, server-side primitive to grant spendable
resource credits to a workspace. This is the backend foundation for
awarding free credits during the new onboarding steps; there was no
existing way to add credits to a workspace.
What it does:
- Increments `billingCustomer.creditBalanceMicro` atomically
(workspace-scoped), then flushes the Redis available-credits cache so
the credit is immediately spendable, not just shown in the gauge.
- No-ops when billing is disabled or no billing customer exists; rejects
non-positive/non-finite amounts.
- Pure primitive with no GraphQL/REST surface; the caller owns
idempotency.
Notes for reviewers:
- Credits use the existing `RESOURCE_CREDIT` currency (micro units, 1
display credit = 1,000,000 micro).
- The credited balance is overwritten by the rollover job at the next
billing-period renewal, so it is not guaranteed to persist across
periods (intentional for onboarding bonuses).
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22094?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. -->
|
||
|
|
c71663946e |
chore(apps): move public apps to packages/twenty-apps/public and generalize CI workflow (#22096)
## What Introduces a `packages/twenty-apps/public/` folder and moves the publicly publishable apps into it, then generalizes the apps CI workflow to cover both folders. ### Moves The following apps were moved from `packages/twenty-apps/internal/` to `packages/twenty-apps/public/` (via `git mv`, history preserved): - `people-data-labs` - `twenty-discord` - `twenty-exa` - `twenty-fireflies` - `twenty-last-contact` - `twenty-linear` - `twenty-meeting-bot` - `twenty-slack` These remain in `internal/`: `self-hosting`, `twenty-for-twenty`, `twenty-partners`. ### Workflow - Renamed `.github/workflows/ci-internal-apps.yaml` → `.github/workflows/ci-twenty-apps.yaml`. - The discover job now scans **both** `packages/twenty-apps/internal` and `packages/twenty-apps/public`: - the "no nested `.github`" guard checks both folders, - `changed-files` watches both globs, - the matrix builder iterates over both roots (guarded with `existsSync` so a missing folder is a no-op). - Each matrix entry still carries its own `path`, so the `ci` job works unchanged regardless of which folder an app lives in. ## Notes - The apps are standalone packages (own `yarn.lock`, not part of the root Nx workspaces), so no root `package.json` / `nx.json` / `tsconfig` changes were needed. - The companion publish workflow lives in `twentyhq/twenty-infra` (`publish-internal-apps.yaml` → `publish-public-apps.yaml`) and is updated in a paired PR. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- _Generated by [Claude Code](https://claude.ai/code/session_01Fmu3DWf1yTTkVW49eSkXwh)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22096?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. --> |
||
|
|
00b7d7c74a |
i18n - docs translations (#22102)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
53bfc6ab1c |
fix: update skill to fit requirements (#22097)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22097?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. --> |
||
|
|
0bccbb5035 |
rename twenty meeting bot to call recorder (#22093)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22093?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: martmull <martmull@hotmail.fr> |
||
|
|
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. --> |
||
|
|
dd9ad876a4 |
Reduce published twenty-ui npm package size (#22087)
The published `twenty-ui@1.0.0-alpha.0` tarball was ~181 MB unpacked (27 MB compressed, 2,701 files). This was a build-config issue, so a clean CI build would reproduce the same size. Main fix: externalize `@tabler/icons-react` instead of bundling it. It was forced into the bundle and aliased to the full icon barrel, inlining the entire icon set into every entry point in both ESM and CJS (~81% of the package). It stays a `dependency`, so consumers still get it; the dynamic `<Icon name>` registry still resolves icons at runtime. Also: - Stop emitting/shipping declaration maps (`declarationMap: false`). - Exclude the internal `dist/individual` build and `*.map` from the tarball via `files` (it still builds locally for `twenty-front-component-renderer`). - Clean up the stale `files` / `project.json` build outputs at their source, `scripts/generateBarrels.ts`. - Add a `pack-size` CI guard (30 MB unpacked budget) and wire `size` + `pack-size` into `ci-ui.yaml`. Result: ~181 MB to ~2.3 MB unpacked (0.40 MB tarball, 400 files). All export subpaths, types, and icon rendering verified intact in both module formats. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22087?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. --> |
||
|
|
1cdede89de |
fix(email-settings): enable independent message folder and subfolder selection (#21853)
## Summary Fixes #21840 Currently, selecting a root folder in **Settings → Accounts → Emails → Folders** automatically selects all of its subfolders, and selecting a subfolder automatically selects all of its ancestor folders. Users have no granular control over individual folder sync. This PR replaces the cascade selection logic with fully independent per-node selection, matching standard tree-select UX patterns used in file explorers and permission trees. ## Changes ### Bug Fix - **`computeFolderIdsForSyncToggle.ts`**: Removed `collectChildren` and `collectParents` cascade helpers. The function now returns only the toggled folder's ID, enabling fully independent selection. - **`SettingsAccountsMessageFoldersCard.tsx`**: Updated call site to match simplified function signature (removed unused `allFolders` and `isSynced` args). ### Tests - **`computeFolderIdsForSyncToggle.test.ts`**: Rewrote tests to reflect new per-node behavior. Removed tests asserting old cascade behavior; replaced with tests verifying only the toggled folder is affected. - **`isFolderTreePartiallySelected.test.ts`** *(new)*: Added 9 tests for `isFolderTreePartiallySelected`, which is now the primary mechanism driving the indeterminate checkbox state on parent folders. ## Behavior Before / After | Action | Before | After | |--------|--------|-------| | Check a root folder | Checks root + all subfolders | Checks root only | | Check a subfolder | Checks subfolder + all ancestors | Checks subfolder only | | Uncheck a root folder | Unchecks root + all subfolders | Unchecks root only | | Parent with partial children | No indeterminate state (broken) | Shows `–` indeterminate correctly | ## What Was Already Correct The indeterminate checkbox UI was already fully implemented: - `isFolderTreePartiallySelected` correctly detects mixed sync states in subtrees - `SettingsMessageFoldersTreeItem` already passes `indeterminate` to the `Checkbox` component - The `Checkbox` component in `twenty-ui` already supports the `indeterminate` prop Only the toggle cascade logic needed fixing. ## Testing ```bash # Unit tests cd packages/twenty-front && yarn jest --testPathPattern="computeFolderIdsForSyncToggle|isFolderTreePartiallySelected" # Lint npx nx lint:diff-with-main twenty-front # Type check npx nx typecheck twenty-front <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21853?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: neo773 <62795688+neo773@users.noreply.github.com> Co-authored-by: neo773 <neo773@protonmail.com> |
||
|
|
90acecfbd9 |
feat(billing): invoice on seat increase (#22083)
## Context
Two billing improvements around workspace seat changes:
1. **Delay subscription quantity updates.** Every workspace member
create/delete/destroy event used to enqueue an
`UpdateSubscriptionQuantityJob` immediately.
2. **Invoice immediately when seats increase.** Seat increases
previously used `create_prorations`, which defers the charge to the next
billing cycle. We now bill the proration right away on increases, while
keeping deferred prorations on decreases / no-ops.
## Changes
### Job delaying
- `BillingWorkspaceMemberListener` now enqueues the job with the
per-workspace id and a 24h delay. Re-adds within the window coalesce to
a single delayed run per workspace, collapsing bursts of member changes
into one Stripe update.
### Proration behavior
- `computeSubscriptionUpdateOptions` now accepts an optional `{
currentSeats }` context. For `SEATS` updates it returns `always_invoice`
when `newSeats > currentSeats`, otherwise `create_prorations` (decrease
or unchanged).
- `BillingSubscriptionUpdateService` passes `currentSeats:
licensedItem.quantity` so the decision is based on the actual current
subscription quantity.
## Tests
- `compute-subscription-update-options.util.spec.ts`: added cases for
seat increase (`always_invoice`), decrease (`create_prorations`), and
unchanged (`create_prorations`).
- `billing-subscription-update.service.spec.ts`: updated expectations to
`always_invoice` for the seat-increase paths.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22083?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. -->
|
||
|
|
965a2753d1 |
chore: bump version to 2.17.0 (#22088)
## Summary - Moves current version to previous versions array - Sets TWENTY_CURRENT_VERSION to the new version - Updates TWENTY_NEXT_VERSIONS with the next minor version - Bumps twenty-client-sdk, twenty-sdk, and create-twenty-app to the same version ## Checklist - [ ] Verify version constants are correct - [ ] Verify npm package versions match <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22088?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> Co-authored-by: Github Action Deploy <github-action-deploy@twenty.com> |
||
|
|
411aee8b96 |
update readme file for meeting bot (#22069)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22069?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. --> |
||
|
|
614bc7b7e6 |
feat: serve HTTP logic functions on isolated *.withtwenty.com domain (#22045)
## Summary Implements [core-team-issues#2473](https://github.com/twentyhq/core-team-issues/issues/2473): serve HTTP-triggered logic functions from a dedicated, **cookieless** public domain (`{workspaceSubdomain}.withtwenty.com`) instead of the same-site `/s/` route, so functions can safely return **arbitrary headers** — custom headers, `Permissions-Policy` (camera/mic/geolocation), `Cross-Origin-Opener-Policy: same-origin`, `Cross-Origin-Embedder-Policy: require-corp`, `Set-Cookie`, etc. The `/s/` route stays the strict, same-site path it is today. **Self-hosting is unchanged** — everything new is gated on `PUBLIC_DOMAIN_URL` being set. ### Why Today user-authored function responses are served same-site with the Twenty app, so the response-header allow-list is restricted to 5 safe headers and request headers are limited to a per-function allow-list. Serving from an origin that shares nothing with `*.twenty.com` removes that constraint safely — the same "user content domain" pattern as GitHub (`*.githubusercontent.com`) and CodeSandbox (`*.csb.app`). ## What's in here **Routing** - The **root-path → `/s` rewrite happens at the nginx ingress**, not in app code. The existing `api-ingress.yaml` already rewrites root paths onto `/s` (host-agnostically) when the edge sets `X-Twenty-Public-Domain: true`, so `*.withtwenty.com` and registered custom public domains are handled by the same mechanism. (An earlier in-app middleware was removed as a redundant, wrong-layer duplicate.) - `WorkspaceDomainsService.resolveWorkspaceAndPublicDomain` recognizes `*.` subdomains, resolves the workspace by subdomain, and returns `isIsolatedOrigin`. Explicitly registered public-domain rows still take precedence and keep their application scoping. The ingress preserves the `Host` header, so this resolution still fires. **Headers (server)** - Isolated origin → all response headers pass through and all request headers are forwarded. Same-site `/s/` keeps the strict allow-lists. (Global CORS already handles preflight/ACAO.) **`/s/` deprecation for new routes (cloud only)** - New `LOGIC_FUNCTION_LEGACY_ROUTE_CUTOFF` config var (ISO date, optional). When `PUBLIC_DOMAIN_URL` is set, functions created on/after the cutoff return **410 Gone** on `/s/` with the new URL. Existing routes and self-hosted instances are untouched. **Frontend education** - `publicFunctionDomain` added to `ClientConfig` (from `PUBLIC_DOMAIN_URL`). - The logic-function **Live URL** now resolves to `https://{workspaceSubdomain}.{publicFunctionDomain}{path}` on cloud, falling back to `/s/` for self-hosting. - Front components call their functions through the SDK (`RestApiClient`), which now targets the isolated domain via the injected `TWENTY_FUNCTIONS_URL`. - New **"Public URL"** section on the application **Settings** tab explaining the isolated domain (shown when the app exposes HTTP-triggered functions). **Docs**: note the `withtwenty.com` domain for external callers in the apps guide. ## Infra prerequisites (not code — needs dashboard work) - Wildcard DNS `*.withtwenty.com` (proxied) + wildcard TLS in the public-domain Cloudflare zone. - Edge (Cloudflare) sets `X-Twenty-Public-Domain: true` for `*.withtwenty.com` requests, so the existing nginx ingress rewrites them onto `/s` (same header the custom-domain flow already relies on). - Set `PUBLIC_DOMAIN_URL=https://withtwenty.com` on cloud. - Submit `withtwenty.com` to the **Public Suffix List** (required for cross-tenant cookie isolation before relying on `Set-Cookie`). ## Test plan - [x] `nx typecheck twenty-server`, `nx typecheck twenty-front` - [x] `lint:diff-with-main` + oxfmt clean (server + front) - [x] `npx jest route-trigger public-function-domain domain-server-config workspace-domains build-logic-function-event client-config` → server unit tests passing (resolution tiers, header passthrough vs allow-list, `/s/` cutoff 410) - [x] `npx jest getLogicFunctionHttpUrl` (front) and `nx test twenty-client-sdk` (RestApiClient routing) passing - [x] CI green (server, front, sdk, renderer, ui, zapier, example apps) - [ ] Manual: hit `{subdomain}.withtwenty.com/` end-to-end once infra is provisioned <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22045?utm_source=github" rel="nofollow noreferrer noopener" target="_blank">``<img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg">``</a> |
||
|
|
bf345bb177 |
Allow workflows listing in MCP (#22013)
This resolves https://github.com/twentyhq/twenty/issues/21986 Add `list_workflows `MCP tool Workflow objects are excluded from the generic database CRUD tools exposed via MCP, which meant the only way to list workflows was through a direct API call. This adds a `list_workflows `tool to the `WorkflowToolProvider`, making it available via MCP alongside the existing workflow builder tools. It supports optional filtering by status (`DRAFT`, `ACTIVE`, `DEACTIVATED`) and pagination (`limit`/`offset`). The status filter uses an array-membership predicate (`ANY`) since `statuses `is a multi-value field. --------- Co-authored-by: Souheyl Gouadria <souheyl.gouadria@medius.com> Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> |
||
|
|
56e20a81ea |
Revert 21949 (#22081)
#21949 introduced deterministic uuid utils with usage in the same PR. Usage was not uniform and expected a backfill command as well. Since we want to release I'm reverting all the changes from that PR that concerns twenty-server and only keeping the unused utils in twenty-shared and I'll introduce usages within the same PR as backfill command |
||
|
|
b5a1aed24b |
feat(server): run server-exposed logic functions in the owner workspace (#22002)
## Summary Implements the server-level logic-function tier in the simplest shape: a logic function is "server-exposed" iff its manifest entry carries `serverWebhookTriggerSettings`. Execution delegates to the owner-workspace copy of that function — billing, throttling, env vars, and the existing executor all apply uniformly against that workspace. Supersedes #21971 with the simplified design from that discussion (no `applicationRegistrationLogicFunction` registry, no dedicated manifest type, no separate SDK helper, no special throttling). ## Design - **Manifest**: `LogicFunctionManifest` gains `serverWebhookTriggerSettings?`. The declarative `workspaceIdResolver` shape is dropped. - **Materialization**: those settings become two new jsonb columns on `LogicFunctionEntity`. The manifest → flat converter and the create-from-source DTO/util forward them; the property-config map and editable-properties list are extended. - **Lookup**: a single QB query joins `logicFunction → application → applicationRegistration` and filters on `lf.workspaceId = reg.workspaceId` to get only the owner workspace's copy. - **Webhook**: `POST /webhooks/server/:logicFunctionUniversalIdentifier` → `ServerWebhookTriggerService.handle` → join lookup → `LogicFunctionTriggerService.run`. No registry table, no `:applicationRegistrationUniversalIdentifier` segment, no resolver. - **Gate**: `IS_SERVER_LOGIC_FUNCTION_ENABLED` config var (disabled by default). ## Test plan - [x] `npx jest server-webhook-trigger` — 9 unit tests across the webhook service. - [x] `npx jest logic-function` — 88 existing tests stay green. - [x] `npx nx typecheck twenty-server`. - [x] `npx nx lint:diff-with-main twenty-server`. - [x] Reset DB → init → run `database:migrate:prod` → run `database:migrate:generate --name pending-migration-check` → no drift. - [ ] Manual: hit `/webhooks/server/<uid>` end-to-end against a manifest carrying `serverWebhookTriggerSettings`. https://claude.ai/code/session_01GgsnCGmYJ26xRirx8va1Yh --- _Generated by [Claude Code](https://claude.ai/code/session_01GgsnCGmYJ26xRirx8va1Yh)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22002?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. --> |
||
|
|
ad3c82bd15 |
fix(front): prevent lingui extract crash in buildCrudToolStatusMessage (#22080)
## Problem
The `build-front / s3-build` CD job fails during the `Build frontend`
step, in the `twenty-front:lingui:extract` target (`lingui extract
--overwrite --clean`):
```
Cannot process file .../build-crud-tool-status-message.util.ts:
Cannot read properties of undefined (reading 'name')
at @lingui/babel-plugin-extract-messages/dist/index.cjs:88:22
at extractFromObjectExpression (...index.cjs:87:18)
at extractFromMessageDescriptor (...index.cjs:121:19)
at PluginPass.CallExpression (...index.cjs:189:11)
```
## Root cause
`buildCrudToolStatusMessage` called `i18n._()` with an inline object
literal containing a spread:
```ts
i18n._({ ...verbs.loading, values: { objectLabel } })
```
Lingui's `extract-messages` babel plugin fires on every `i18n._(...)`
call. When the first argument is an `ObjectExpression`, it runs
`extractFromObjectExpression`, which reads `key.name` for **every**
property. The spread element `...verbs.loading` has no `key`, so
`key.name` throws `Cannot read properties of undefined (reading
'name')`, crashing `lingui extract` and failing the whole S3 publish
job.
## Fix
Hoist the descriptors into variables so `i18n._()` receives an
identifier rather than an inline object expression. The plugin then
skips extraction (no statically-extractable id), so no crash. Runtime
behavior is unchanged — the translatable strings are still extracted
from the `msg` macros in `CRUD_TOOL_OPERATION_VERBS`.
## Testing
- Reproduced the **exact** CI crash locally on `main` by running `lingui
extract --overwrite --clean` (same file, message, and stack frames).
- After the fix, `lingui extract --overwrite --clean` runs clean (exit
0).
- `build-crud-tool-status-message.util.test.ts` passes (2/2).
- `nx lint:diff-with-main twenty-front` passes (0 warnings, 0 errors,
formatting clean).
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22080?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. -->
|
||
|
|
a575ef56c3 |
Add logo to twenty-ui README (#22077)
<img width="408" height="408" alt="Twenty_UI" src="https://github.com/user-attachments/assets/f69fb630-97fb-4c21-ad44-d924e4bd72f5" /> Adds the Twenty UI logo to the top of the `twenty-ui` package README. - New `packages/twenty-ui/logo.png` (rasterized at 3x for retina). - README references it via an absolute raw GitHub URL so it renders on both the GitHub repo page and the npmjs.com package page (npm strips SVG images from READMEs, so PNG is used). <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22077?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. --> |
||
|
|
9e57ec3153 |
Update data model object settings labels (#22070)
## Summary - Update Data Model object list copy: rename the section to **Objects** and the count column to **Records**. - Improve relation rows by showing the related object name with the field name as a secondary label, including morph relation-specific labels/icons. - Hide relations to system objects unless Advanced mode is enabled, and default the System objects filter to on while Advanced mode is on. - Reuse a shared secondary-label component for the light subtitle/deactivated text treatment. ## Screenshots ### Before Mix of field name & object name. Not all relations are navigable <img width="1642" height="950" alt="image" src="https://github.com/user-attachments/assets/e04fb710-e333-4dd7-a29f-82c226202e77" /> ### After <img width="1690" height="1112" alt="image" src="https://github.com/user-attachments/assets/f7d75974-a5cc-401b-bbd2-ab82a2006cf9" /> |
||
|
|
73e9374ef8 |
[BREAKING CHANGE] harden call recording failure handling (#22062)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22062?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. --> |
||
|
|
0df83eceb2 |
fix(front): restore loading state on third-party app command menu actions (#22073)
## Problem Headless command-menu actions provided by third-party applications (e.g. the "Twenty Eng" app actions like *Fetch Pull Requests*, *Recompute Build Tasks*) no longer show a loading/progress indicator while they run, so users can't see that the action is in progress. ## Root cause In `CommandMenuItemSelectableRenderer`, [#21020](https://github.com/twentyhq/twenty/pull/21020) added an early-return branch for third-party application actions that renders `AppMenuItem`: ```tsx if (isThirdPartyApp) { return ( <SelectableListItem ...> <AppMenuItem ... /> // no loader passed </SelectableListItem> ); } ``` This branch returns **before** the `listItem` path that builds the `loaderComponent` (spinner + progress %), and `AppMenuItem` had no way to render a right-side loader. So `progress` / `showDisabledLoader` from `useCommandMenuItemClick` were computed but dropped for third-party app actions. Native (non third-party) actions kept their loader because they go through the `listItem` path. ## Fix - Add an optional `RightComponent` prop to `AppMenuItem`, forwarded to the underlying `MenuItem` (which already renders it). - Hoist the `loaderComponent` computation in `CommandMenuItemSelectableRenderer` above the branches and pass it to both the third-party `AppMenuItem` path and the existing `listItem` path (no behavior change for the latter). The loader now appears for third-party app actions exactly as it does for native ones — `<CommandListItemLoader progress={progress} />` once progress is reported, falling back to a `<Loader />` spinner before the first progress update. ## Verification - `oxlint --type-aware` clean on both changed files. - `typecheck` clean for the changed files. - Manual browser repro requires a third-party application with a progress-reporting headless action installed in the workspace (as in the reported screenshot), which isn't available in a stock dev workspace. The fix mirrors the already-working native `listItem` loader path. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22073?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. --> |
||
|
|
0f2ea47335 |
Twenty standard backfill non searchable object search field metadata (#22063)
# Introduction This PR https://github.com/twentyhq/twenty/pull/21964 introduces a search field metadata workspace command backfill that will recompute all the standard search field metadata but only for the searchable object Whereas the non searchable object still have a search vector as they can still be searched but internally Preserving their search vector by computing their search field metadata <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22063?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. --> |
||
|
|
dd7435b807 |
fix: normalize date-time field input on backend to prevent timeline crash (#22035)
## Context Reported via support ([private-issues#477](https://github.com/twentyhq/private-issues/issues/477)): a customer saw **"Invalid Configuration"** in red on a record's **Timeline** tab. The dev console was flooded with: ``` RangeError: Cannot parse: 2026-05-07 at Temporal.Instant.from (...) at RecordFieldComponent ... ``` ## Root cause A `DATE_TIME` field in their workspace holds **date-only** values like `2026-05-07`. `validateDateTimeFieldOrThrow` (the write-path validator) **accepts** date-only formats — `'yyyy-MM-dd'` is in `ACCEPTED_DATE_TIME_FORMATS` — and **returns the raw input string unchanged**, with no normalization. So a date-only string passes validation and propagates verbatim into the mutation response and the timeline event payload. On render, `DateTimeDisplay` builds the timezone hint with `Temporal.Instant.from(value)`. That's strict — it requires a full instant (time + offset/`Z`) and throws `RangeError` on a bare date. The throw escapes into the page-layout widget error boundary, which renders the **"Invalid Configuration"** fallback and breaks the whole timeline. ## Fix **Backend (root cause) — normalize on write.** `validateDateTimeFieldOrThrow` now canonicalizes every accepted value to a full ISO 8601 instant, so a date-only value can never reach storage, the mutation response, or timeline events for a `DATE_TIME` field: - strict ISO-8601 carrying an offset/`Z` -> kept as its exact instant (server-timezone-independent) - zoneless / date-only / lenient formats -> interpreted as **UTC** (date-only -> midnight UTC), deterministically Lenient input is preserved — parsing still uses date-fns for the ~20 accepted formats (which `Temporal.Instant.from` cannot parse); only the *output* is canonicalized, via Temporal. | input | before (stored raw) | after (normalized) | |---|---|---| | `2026-05-07` | `2026-05-07` | `2026-05-07T00:00:00Z` | | `2026-05-07T12:00:00+02:00` | `2026-05-07T12:00:00+02:00` | `2026-05-07T10:00:00Z` | | `2026-05-07T12:00:00.000Z` | `2026-05-07T12:00:00.000Z` | `2026-05-07T12:00:00Z` | | `January 15, 2024` | `January 15, 2024` | `2024-01-15T00:00:00Z` | **Frontend (existing data) — Temporal-native guard.** Existing workspaces already have date-only values stored in events, so the backend fix alone won't un-break the reporting customer's timeline. `DateTimeDisplay` now parses the value via a new `parseStringToInstantOrNull` helper (Temporal `Instant.from` with a `PlainDate` start-of-day-UTC fallback) and only renders the timezone hint when valid — so stored bad data renders gracefully instead of crashing. This replaces the initial `new Date()` guard with a Temporal-native one, in line with the codebase's Temporal migration. ## Tests - `validate-date-time-field-or-throw.util.spec.ts` updated to assert the normalized instant output, incl. explicit date-only -> midnight-UTC cases. - `parseStringToInstantOrNull.test.ts` — unit coverage for the frontend helper (instant, offset, date-only, unparseable). - `DateTimeDisplay.stories.tsx` — story rendering a date-only value under a non-system timezone (the previously-crashing path). |
||
|
|
558e2e4107 |
Add new onboarding login screen at /welcome-v2 (#22027)
Stands up the new onboarding login screen at a new route `/welcome-v2`, as the foundation for the new onboarding flow (future PRs build the post-login steps on top of it). There is no feature flag: feature flags are per-workspace and read from `currentWorkspaceState`, which is null on the pre-auth welcome screen, so they can't cleanly gate it. A dedicated route is used instead. `/welcome` is untouched and stays the default for logged-out users; `/welcome-v2` is reachable only by navigating to it directly (nothing links or redirects to it yet), so this is fully non-breaking. The new page reuses all existing auth logic and behavior components (`useSignInUp`, `useSignInUpForm`, step state, the Google/Microsoft/credentials forms, `Logo`, `Title`, `ModalContent`) and mirrors `SignInUp.tsx` almost exactly. The only intentional design delta from today's screen is the footer wording, per Figma: "Data Processing Agreement" (linking to `/legal/dpa`) instead of "Privacy Policy". Notable: - Added an optional `to` prop to the shared `Logo` (defaults to `AppPath.SignInUp`, backward-compatible) so the logo on `/welcome-v2` doesn't bounce users back to `/welcome`. - The remaining changes are single-line additions to the pre-auth allowlists next to the existing `AppPath.SignInUp` entries (router, redirect guard, auth modal, metadata gater, captcha, page title, focus). https://github.com/user-attachments/assets/abfc96ec-a87d-4608-b92a-87e2322e4874 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22027?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. --> |
||
|
|
1589b9b912 |
Add search to new sidebar item picker (#22041)
## Summary - Add search to the custom layout “New menu item” side panel. - Group search results by Objects, Views, and Records. - Reuse the existing record search behavior through a shared hook and preserve add-to-navigation drag/select flows. ## Video - Recording: https://gist.githubusercontent.com/Bonapara/c78107650efd94b580e38426b9fc2dbd/raw/755c87fab253281a9c68e5a24cbfdff6c9248af1/search-nav-item-custom-layout.webm ## Verification - Browser plugin: opened layout customization, clicked `Add menu item`, searched `o`, and verified `Objects`, `Views`, and `Records` result groups with object/view/record results. - `npx oxlint --type-aware -c packages/twenty-front/.oxlintrc.json packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemPage.tsx packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemRecordSubPage.tsx packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/components/SidePanelNewSidebarItemSearchResults.tsx packages/twenty-front/src/modules/navigation-menu-item/edit/side-panel/hooks/useAvailableNavigationMenuItemSearchRecords.ts` - `npx nx typecheck twenty-front` - `npx nx lint twenty-front` <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22041?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. --> |
||
|
|
5ff1d997c7 |
i18n - docs translations (#22068)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22068?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
5ca41d55fb |
feat(ai): humanize tool-call (#21976)
# Humanize tool-call labels cc: https://github.com/twentyhq/twenty/pull/21462 ## Preview <img width="459" height="156" alt="Screenshot 2026-06-22 at 19 13 11" src="https://github.com/user-attachments/assets/e7a2f5f5-cd09-4ec6-920b-5eb16b98285c" /> <img width="461" height="156" alt="Screenshot 2026-06-22 at 19 14 54" src="https://github.com/user-attachments/assets/c2114d2e-2aa8-499a-9801-68e3bb7c45f8" /> <img width="461" height="505" alt="Screenshot 2026-06-22 at 19 15 01" src="https://github.com/user-attachments/assets/ee9ca5d0-8e79-4c63-a2ff-ed5e359a9a9c" /> ## Why In the AI chat, tool steps were displayed using raw tool identifiers (`find_many_companies`, `create_one_task`, `send_email`...) and labels were partially reconstructed/humanized on the frontend. This was hard to localize and inconsistent across tool categories. This PR makes the **backend the single source of truth for human-readable, localized tool labels**, exposes them through `getToolIndex`, and reduces the frontend to a thin resolver that picks the right label for the current status (in-progress / completed). ## What changed ### Backend - `ToolIndexEntry` (and the `getToolIndex` GraphQL DTO) now carry `label`, `inProgressLabel?`, `completedLabel?`. - New `getCrudToolLabels(operation, objectLabel, i18nService, locale)` builds CRUD labels from a verb table (Search / Find / Group / Create / Update / Upsert / Delete × imperative / in-progress / completed) + the (translated, lowercased) object label. - New `translate-tool-label.util.ts` translates a source label via `I18nService` (`generateMessageId` → fallback to source when no translation exists). - Action tools: labels extracted to the `ACTION_TOOL_LABELS` constant (`msg` + `i18nLabel`) and translated in `ActionToolProvider.buildDescriptor`. - Logic-function tools use the function name as label; `toolSetToDescriptors` (workflow / view / metadata / dashboard) accepts an optional `labels` map and falls back to a humanized tool name. - Labels are localized server-side using the request locale (`@RequestLocale` → `buildToolIndex` → `context.locale`, threaded through `ToolContext` / `ToolProviderContext`). - `code_interpreter` schema now asks the model for `loadingMessage` (present tense) and `completedMessage` (past tense), so its status text is model-generated. - Removed the old generic `loadingMessage` injection mechanism (`wrap-tool-for-execution.util.ts` deleted; `wrapJsonSchemaForExecution` / `stripLoadingMessage` no longer wrap every tool). ### Frontend - New `useToolLabelMap()` hook builds a `Map<name, { label, inProgressLabel, completedLabel }>` from `getToolIndex`. - `getToolDisplayMessage` → `resolveToolDisplayMessage({ input, toolName, isFinished, labelMap, output })`: a small resolver registry keyed by tool name (`execute_tool`, `web_search`, `learn_tools`, `load_skills`, `code_interpreter`, default). - Default resolver prefers backend `completedLabel` / `inProgressLabel`, falling back to `Ran X` / `Running X`. - `learn_tools` / `load_skills` resolve their inner tool/skill names to labels (label map → tool output labels via `getToolOutputLabelEntries` → raw name). - `code_interpreter` step is now expandable to show the code even while running. ## How tool labelling flows (BE → FE) ```text BACKEND ┌───────────────────────────────────────────────────────────────────────────┐ │ Tool providers (per category) → ToolIndexEntry │ │ │ │ DatabaseToolProvider │ │ getCrudToolLabels(operation, object.labelPlural/Singular, i18n, locale) │ │ verb table (Search/Create/Update/Delete…) + translateToolLabel(object) │ │ → { label, inProgressLabel, completedLabel } │ │ │ │ ActionToolProvider │ │ ACTION_TOOL_LABELS[toolId] (msg) → translateToolLabel(…, locale) │ │ → { label, inProgressLabel?, completedLabel? } │ │ │ │ LogicFunctionToolProvider → label = logicFunction.name │ │ toolSetToDescriptors → label = labels[name] ?? humanize(name) │ │ (workflow / view / metadata / dashboard) │ └───────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────────────────┐ │ GraphQL Query getToolIndex : [ToolIndexEntry] │ │ { name, label, inProgressLabel, completedLabel, description, │ │ category, objectName, icon } │ └───────────────────────────────────────────────────────────────────────────┘ │ ▼ FRONTEND ─ resolve the right label for the current status ┌───────────────────────────────────────────────────────────────────────────┐ │ useGetToolIndex() → useToolLabelMap() │ │ Map<name, { label, inProgressLabel?, completedLabel? }> │ └───────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌───────────────────────────────────────────────────────────────────────────┐ │ resolveToolDisplayMessage({ input, toolName, isFinished, labelMap, output })│ │ │ │ TOOL_LABEL_RESOLVERS[toolName] ?? defaultResolver │ │ ├─ execute_tool → unwrap { toolName, arguments } then re-resolve │ │ ├─ web_search → "Searching/Searched the web for <query>" │ │ ├─ learn_tools → "Learning/Learned <labels>" │ │ ├─ load_skills → "Loading/Loaded <labels>" │ │ │ inner names resolved via: labelMap → output labels → raw name │ │ ├─ code_interpreter → model's loadingMessage / completedMessage │ │ └─ default → isFinished │ │ ? completedLabel ?? "Ran <label>" │ │ : inProgressLabel ?? "Running <label>" │ └───────────────────────────────────────────────────────────────────────────┘ │ ▼ Rendered by ThinkingStepsDisplay / ToolStepRenderer ``` ## Localization notes - Standard object labels and action/CRUD verbs are translated server-side via `I18nService` using the requester's locale. - Custom object labels are not translated unless a workspace custom translation exists (matched by `generateMessageId`); otherwise the source label is used as-is. ## Tests - **FE:** `resolveToolDisplayMessage` / `getToolOutputLabelEntries` (status selection, inner-name resolution, `code_interpreter` model labels, fallbacks). - **BE:** `toolSetToDescriptors` (label map + humanized fallback) and `database-tool.provider` label generation. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21976?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. --> |
||
|
|
680e4a712b |
feat(ui): additional social providers to link components (#21716)
The current link component matches only to linkedin, twitter and facebook. It is currently missing the x handle. In addition to this, we should also accomodate for instagram, bluesky and tiktok. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21716?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> |
||
|
|
d2387430a1 |
Factorize from entity to flat entity utils (#21972)
## What Factorizes the two responsibilities that were copy‑pasted across every `from-<entity>-entity-to-flat-<entity>` util into two reusable tools. ### `fromEntityToScalarEntity` Projects a TypeORM entity into its scalar flat shape using an **allow‑list** driven by `ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME` (plus the base columns `id`/`workspaceId`/`applicationId`/`universalIdentifier`). Only registered scalar columns are forwarded, `Date`s are serialized to ISO strings, and absent values are normalized to `null`. Replaces the previous deny‑list (`removePropertiesFromRecord`) approach, so unregistered/deprecated columns can no longer silently leak into the flat entity. ### `resolveManyToOneRelationIdsToUniversalIdentifiers` Resolves an entity's many‑to‑one foreign keys to their universal identifiers, driven by `ALL_MANY_TO_ONE_METADATA_RELATIONS`. Handles the always‑present `application`, nullable relations, and throws a `FlatEntityMapsException` when a referenced id is missing from its identifier map. Mirrors `resolveUniversalRelationIdentifiersToIds` in the opposite direction. Each `from-<entity>` util now reduces to: scalar spread + relation spread (+ explicit one‑to‑many id/universalIdentifier arrays where applicable). ### Note The allow‑list drops `isUIReadOnly` (a `WasRemovedInUpgrade` column not in the config) from `fieldMetadata`, which is the only integration‑snapshot change. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21972?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. --> |
||
|
|
41d1b478b0 |
Fix Opportunity email timeline relation traversal (#22064)
## Summary - Stop the related-person path walker from traversing system objects while deriving timeline people. - Keep direct `person` terminal paths valid so CRM relations still resolve. - Add a regression test covering the bad Opportunity owner -> workspace member -> message participant path. ## Root Cause PR #21684 introduced generic relation traversal for email and calendar timelines. That traversal walks relation paths from the current record to `person`, then the Emails tab loads message threads for those derived people. For Opportunities, the traversal was too broad because it could enter internal/system objects. In particular, it could follow: `opportunity.owner -> workspaceMember.messageParticipants -> messageParticipant.person` That path does not describe people related to the Opportunity. It describes people who appeared in messages involving the Opportunity owner. As a result, an Opportunity owned by Josh could show threads from Josh's broader mailbox activity, which matches the customer report: recently communicated people appeared in the Opportunity Emails tab even though they were not specifically related to that Opportunity. ## Behavior Before On an Opportunity record, the Emails tab could include message threads for: - the Opportunity point of contact; - people related through the Opportunity company; - people reached through internal/system relations, including the owner workspace member's message participants. The last category was the regression. It made the Opportunity Emails tab look like a broad inbox for the owner instead of a timeline for people related to the CRM record. ## Behavior After The traversal still allows valid CRM person paths, including: `opportunity.pointOfContact -> person` and non-system CRM paths such as: `opportunity.company -> company.people -> person` But it now stops before traversing system objects such as `workspaceMember` and `messageParticipant`. This blocks the bad owner-mailbox expansion path: `opportunity.owner -> workspaceMember.messageParticipants -> messageParticipant.person` Email sync is unchanged. This only changes which synced emails are displayed on a record timeline. ## Video https://github.com/user-attachments/assets/26de4cee-06d9-4f42-b91e-32e60a260b5b ## Validation - `yarn nx jest twenty-server src/engine/core-modules/related-person-ids/utils/__tests__/find-relation-paths-to-person.util.spec.ts --runInBand` - Focused `oxlint` and `oxfmt` on the touched files. - GitHub `server-lint-typecheck` passes on the updated branch. - Browser verification on local Apple seed workspace: fixed relation set renders `Inbox 280`; the excluded owner-derived path would have resolved `300` threads. |