880e7144ecd3ec06a67dd1f044d01ed0c9871e6c
10580 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
0332d8c706 |
i18n - website translations (#21795)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21795?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> |
||
|
|
7a1cfc17cc |
fix(logic-function): treat invoke timeout as a user-level error, not a platform error (#21779)
Fixes https://twenty-v7.sentry.io/issues/7527156270?project=4507072499810304 ## Problem Sentry was flooded with high-severity alerts for logic functions that simply ran too long: > Lambda timed out for function '…' during invoke (functionState=Active, phase=invoke …) A function exceeding its configured `timeoutSeconds` is a **user-level outcome** (their code is too slow), not a platform failure — but it was being reported as one. ## Root cause The two timeout mechanisms were classified inconsistently: - **Lambda's own timeout** → returns `{ status: ERROR, … }` → handled as a user error (route returns 500 with `shouldBeCapturedBySentry: false`, queue job records it without failing). Not in Sentry. ✓ - **Client-side `AbortSignal` timeout** → **threw** `LOGIC_FUNCTION_EXECUTION_TIMEOUT`, which isn't mapped in `mapErrorToRouteTriggerCode`, so it fell through to `ROUTE_TRIGGER_PLATFORM_ERROR` (Sentry) and failed the BullMQ job (Sentry). ✗ Since the executor Lambda is fixed at 900s, the client abort is the *sole* timeout enforcement for every function with `timeoutSeconds < 900` — so essentially every slow function paged the team. The `local` driver already returns an ERROR result here; only the Lambda driver threw. ## Fix On an **invoke-phase** `TimeoutError`, return a structured ERROR result instead of throwing — mirroring the Lambda's own timeout and the local driver. The timeout now flows through the normal result path: surfaced to the caller as `status: ERROR`, recorded via `handleExecutionResult` (which the throw path skipped), and kept out of Sentry. **Build- and fetch-phase timeouts still throw** and stay in Sentry — those are platform-side (executor build / code fetch too slow, even for short user code), which is exactly what the phase instrumentation exists to catch. ## Tests - Unit test on the new `buildLogicFunctionTimeoutResult` util - `npx jest logic-function-drivers/drivers/lambda` green |
||
|
|
7afc991bd6 |
Partner application form: required fields, skills rework, fail-fast validation (#21710)
## Summary (twenty-website — partner application form) - **Required fields**: website URL, city, hourly rate, minimum project are now required (client step-gate + server zod) with `*` markers. The final step validates before POSTing, so empty required fields fail fast client-side instead of round-tripping. - **Technical skills reworked to *complement* "What you cover"** (the service categories) rather than duplicate them — now a small shown set + a larger searchable-only pool of tools / technologies / industries. Field hint clarifies the intent. - **No competitor CRMs** in suggestions (Salesforce/HubSpot/Attio removed); a guard test fails if one ever reappears. Migrations surface as a generic "CRM migration". - `Form.TagInput` gains an optional `searchPool` prop (autocomplete-only entries, not rendered as chips). Companion to the app-side PR #21709. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21710?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. --> |
||
|
|
0f4cb2c2c2 |
[Website] Full-bleed hero/testimonials, nav restructure, OpenNext deploy config (#21794)
### Full-bleed backgrounds on wide screens Decorative section backgrounds were capped at the 1512px content width, leaving large empty gutters on ultra-wide viewports. - Added an opt-in `fullBleedBackground` prop to `SectionShell` that lifts the background layer off the content-width cap (default unchanged, so every other section is untouched). - Applied it to `HomeHero` (bridge halftone field) and both testimonials sections. - Capped the `NotchedCardShape` notch at its width at the content cap, so the white/dark card can span the full viewport while the notch stays fixed and centered. The footer and any other capped card are unaffected. <img width="3024" height="1718" alt="image" src="https://github.com/user-attachments/assets/a2200677-58af-4740-9257-77f6385ade28" /> <img width="3024" height="1224" alt="image" src="https://github.com/user-attachments/assets/73aa6745-b766-405e-b736-8c0c7591232e" /> ### Navigation restructure - Removed **Product** from the menu and footer nav, and promoted **Why** out of the Resources dropdown to a top-level item in Product's place. (The Product page itself is unchanged). - Resources dropdown polish: tightened the preview frame height now that the list is shorter; restored the cleaner/brighter User Guide and Developers preview assets from `twenty-website`; gave User Guide a center + 2× image scale so its halftone fills the frame like Developers, and raised `NextImage` `sizes` to keep the fine halftone crisp through that magnification. - Restored the **current-page highlight** in the Resources dropdown (active icon/label in highlight blue + marker bar), matching the old `NavDropdown`. <p> <img width="1509" height="323" alt="image" src="https://github.com/user-attachments/assets/d5702761-31f5-4b4d-9fcc-c33d5c7ae6ab" /> </p> ### OpenNext / Cloudflare deployment config Ported the Cloudflare Workers deployment setup from `twenty-website` so the same CI/deploy pipeline works against the redone package: - `open-next.config.ts` (R2 incremental cache + regional cache + skew protection), `wrangler.jsonc` (dev/prod envs — **worker names, routes, R2 buckets kept identical** for a seamless cutover), `initOpenNextCloudflareForDev()` in `next.config.ts`, the `preview`/`deploy:*`/`cf-typegen` scripts, the `@opennextjs/cloudflare` + `wrangler` devDependencies, a `.dev.vars.example` template, and the relevant `.gitignore` entries. |
||
|
|
86a4066997 |
Accessibility fix pass for remaining twenty-ui domains (#21790)
Completes the twenty-ui accessibility fix pass for the remaining 32
`a11y: { test: 'todo' }` story files (navigation, feedback,
data-display, typography, surfaces, layout, json-visualizer), following
the input pilot (#21776, merged). The package now has **zero** `test:
'todo'` overrides left.
**What changed (gate-driven)**
- Bulk color-contrast deferred via the shared
`A11Y_DEFER_COLOR_CONTRAST` (greppable, tracked for the later
design-token pass).
- Real violations the axe gate surfaced and fixed: accessible names on
`ProgressBar` (progressbar), `Modal` (dialog), and the
MultiSelect/Toggle inner Checkbox/Switch; `role="listbox"` parents for
`MenuItemSelect`/`SelectAvatar`; `ariaLabel` on
`MenuItem`/`MenuItemDraggable` icon-buttons; a `version` for
`GithubVersionLink`; and `JsonTree`'s empty array/object now renders a
proper `<li>` (`list` rule).
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21790?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. -->
|
||
|
|
c6309fd92b |
feat(workflow): auto-layout steps on AI workflow creation via shared tidy-up (#21756)
## Context
The workflow builder has a "Tidy up" action that auto-positions steps
using a
Dagre layout. However, this lived entirely in the frontend and depended
on node
dimensions measured by React Flow after rendering in the browser.
As a result, workflows (and steps) created through AI Chat / MCP tools
were never
laid out: `create_complete_workflow` accepted optional `stepPositions`
that the
LLM had to invent, and `create_workflow_version_step` stored an optional
position
verbatim. In practice this produced overlapping / poorly positioned
steps.
## What this does
Extracts the tidy-up layout into a pure, frontend-free util in
`twenty-shared` and
reuses it from both the frontend tidy-up and the server, so
AI/MCP-created
workflows are auto-laid out at creation time.
### twenty-shared
- New `computeWorkflowLayout({ nodes, edges, options? })` — a pure Dagre
layout over
a minimal `{ id, width, height }` / `{ source, target }` graph,
returning
top-left-anchored positions (matching React Flow). Ignores edges
pointing to
unknown nodes.
- New constants: `WORKFLOW_LAYOUT_DEFAULT_OPTIONS`
(ranksep/nodesep/rankdir) and
`WORKFLOW_DIAGRAM_DEFAULT_NODE_DIMENSIONS` (estimated node size for
server-side
layout, where measured sizes are unavailable).
- Added `@dagrejs/dagre` dependency.
### twenty-front
- `getOrganizedDiagram` now delegates to `computeWorkflowLayout`,
passing real
measured node sizes. No behavior change for users.
### twenty-server
- New `WorkflowVersionWorkspaceService.autoLayoutWorkflowVersion(...)`
builds the
graph topology via the existing `buildWorkflowGraph` (covers if-else
branches and
iterator loops), feeds estimated node sizes into
`computeWorkflowLayout`, and
persists through the existing `updateWorkflowVersionPositions`.
- `create_complete_workflow`: removed `stepPositions` from the tool
schema; the
server always auto-lays out after creation/edges.
- `create_workflow_version_step`: re-tidies the whole version after each
added step
(wired at the tool level so the builder UI is unaffected) and dropped
the now
redundant `position` field.
## Notes
- Server-side layout uses estimated node sizes, so it is "good enough";
opening the
workflow and running the existing FE tidy-up refines it with real
measured sizes.
- Auto-layout is wired in the MCP tools, not in the shared creation
service, so
manual step creation in the builder UI is unchanged.
## Test plan
- [x] `twenty-shared` unit tests for `computeWorkflowLayout` (linear
chain, if-else
spread, dangling-edge safety)
- [x] `twenty-shared` builds; `twenty-server` and `twenty-front`
typecheck
- [x] Lint/format clean on changed files
- [ ] Create a workflow via AI Chat / MCP and confirm steps are laid out
without
overlap
- [x] Add a step via MCP and confirm the version is re-tidied
- [ ] Frontend "Tidy up" still behaves as before
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21756?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. -->
|
||
|
|
f1d4d6aeaf |
Converge Recall transcript artifacts on meeting start (#21788)
## what - converge stuck call recordings after the meeting starts, not based on scheduled end - replace the pending-transcript cron with Recall transcript artifact reconciliation - list existing Recall transcripts before creating one, so we avoid duplicate requests - keep a local pending marker as an idempotency latch for stale/redelivered events <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21788?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. --> |
||
|
|
454758471f |
Fix side panel command menu header controls (#21747)
## Summary Tested the 3 behaviors of the issue locally. Animation is not perfect on the closing but I think this is a great v1 - Move the side-panel close action to the right side of the top bar while keeping back navigation on the left. - Keep the nav side-panel button as the command-menu entry point for direct side-panel pages and hide it while command-menu pages/history are active. - Reset command-menu search/filter state when opening the root command menu from the nav button. Fixes twentyhq/core-team-issues#2504 ## Videos ### Before https://github.com/user-attachments/assets/08c1b6b3-5fbd-4154-a85d-5072a3b7690e ### After https://github.com/user-attachments/assets/11682dea-f21c-47b5-91a8-869f30b09d96 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21747?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. --> |
||
|
|
cc4659ce11 |
fix(server): type WasRemovedInUpgrade columns with WasRemovedInUpgrade<> (#21785)
## Context WasRemovedInUpgrade type brand was introduced in https://github.com/twentyhq/twenty/pull/21228/changes#diff-1b6d688610669a46b3ee8e3a41b1c7eb0ee03e19146d0d249f95df6e56164a92R15 for the `isCustom` property deprecation. The @WasRemovedInUpgrade decorator and the WasRemovedInUpgrade<T> type are meant to go together: the type brand makes the property optional in every derived flat-entity type, so the column only needs to be declared on the entity itself. RolePermissionFlagEntity.flag had the decorator but was typed as a plain PermissionFlagType, forcing the property to be supplied everywhere. This PR: - Types flag as WasRemovedInUpgrade<PermissionFlagType> (matching the isCustom reference impl on object/field metadata). - Removes the now-redundant flag from the flat-entity construction sites, the create input, and the service call site — leaving it only on the entity. The GraphQL RolePermissionFlagDTO.flag is kept (it's an API field derived from permissionFlag.key, not the removed column). - Fixes a latent brand-leak in the flat-entity config type: toStringify is computed via object-detection, and a branded type reads as an object. This was harmless for boolean but wrongly forced toStringify: true for enum/string columns. Added UnwrapWasRemovedInUpgrade<T> and applied it so the brand is transparent making the pattern work for any type, not just booleans. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21785?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. --> |
||
|
|
2b3b2362db |
i18n - docs translations (#21789)
Created by Github action Co-authored-by: github-actions <github-actions@twenty.com> |
||
|
|
22baf2c6c5 |
fix(server): prevent enum migration failure for long identifier names (#21748)
Fixes https://github.com/twentyhq/twenty/issues/20524 ## Problem Adding (or renaming/removing) an option on a `SELECT` / `MULTI_SELECT` field failed for fields whose object + field name combination is long, surfacing to the user only as: > Migration action 'update' for 'fieldMetadata' failed — Migration execution failed. The real underlying Postgres error was: ``` type "_personalInsurancePolicyOrQuote_insuranceCoverageClassification" already exists ALTER TYPE "..."."_personalInsurancePolicyOrQuote_insuranceCoverageClassifications_enum" RENAME TO "..._insuranceCoverageClassifications_enum_old" ``` ## Root cause PostgreSQL truncates identifiers to **63 bytes** (`NAMEDATALEN - 1`). The enum type name `_personalInsurancePolicyOrQuote_insuranceCoverageClassifications_enum` is **69 chars**, so it was already stored truncated to 63 (`..._insuranceCoverageClassification` — the `_enum` suffix chopped off). Multi-select / select option changes go through the rename-and-recreate path in `alterEnumValues`, which renames the enum to `<name>_old`. That candidate is 73 chars → Postgres truncates it back to the **same 63-byte string** as the source → `type "..." already exists`. The failure is deterministic, so every retry on that field failed. The transaction rolls back cleanly, leaving no `_old` artifacts behind. The temporary column name (`<column>_old`) had the same latent bug for very long field names. ## Fix Add `buildTemporaryIdentifier(base, suffix)` to `WorkspaceSchemaEnumManagerService`, which trims the base name so the `_old` suffix survives within 63 bytes and stays distinct from the original. Applied to both the temporary enum name and the temporary column name. The `_old` type/column are transient (dropped within the same transaction), so the trimmed name only needs to fit and not collide — which it now does. ## Test Added `workspace-schema-enum-manager.service.spec.ts` reproducing the exact failing object/field names. Both assertions (target identifier ≤ 63 bytes; truncated source ≠ truncated target) fail on `main` and pass with the fix. ## Recovery No manual cleanup needed for affected workspaces — failed migrations rolled back cleanly. Once deployed, option edits on long-named fields work; the field remained fully usable in the meantime (only option changes were blocked). |
||
|
|
a2d030e97a |
Fix flaky Argos diffs (#21771)
Inputs and selects with a trailing icon (copy button, chevron, etc.) were flaky in Argos: the icon would intermittently flag as appearing twice. The icon's x-position depends on font-rendered content width, and under Argos's `fit-content` capture the whole row is content-sized. Inter is loaded from the Google Fonts CDN with `display=swap`, and Argos's font gate (`document.fonts.status === "loaded"`) doesn't reliably catch the late swap, so the snapshot sometimes lands pre-swap and sometimes post-swap. The diff then paints both the old and new icon positions, which reads as a duplicated icon. This adds a Storybook loader that force-loads the Inter weights and awaits `document.fonts.ready` before each story renders, so screenshots are always taken post-swap. Fixes the whole class at once rather than per component. Note: affected trailing-icon stories will need a one-time Argos re-baseline. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21771?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. --> |
||
|
|
b4113de74a |
Accessibility fix pass for twenty-ui input components (#21776)
Re-enables the live Storybook axe gate across the twenty-ui `input`
domain (17 story files) by removing the inherited `a11y: { test: 'todo'
}` overrides. Pilot for the wider a11y fix pass; other domains follow
the same playbook.
**What changed**
- Accessible names added to icon-only buttons, `Toggle`, `Checkbox`, and
the button groups via small additive `aria-label`/`ariaLabel` props on 8
components. Where this fully fixes the story, the override is dropped so
the full gate applies.
- Color-contrast (design-token level) is deferred — not changed — via a
new shared `A11Y_DEFER_COLOR_CONTRAST` parameter (`@ui/testing`) that
disables only the `color-contrast` rule while every other axe rule stays
enforced. Grep the constant to find all deferrals when tokens are
darkened later.
- `CatalogDecorator`: unique cell ids (clears `duplicate-id-aria`) and
dimension titles switched from empty `h1/h2/h3` to `div` (clears
`empty-heading`). Both help every domain's catalogs.
**Reviewer notes**
- No visual change: edits are aria / id / story-args / axe-config only,
so Argos parity holds.
- Input axe gate goes from 12 failing to 60/60 passing; typecheck and
lint green.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21776?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. -->
|
||
|
|
41b7c53dbf |
Bump call recording app version (#21787)
as title <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21787?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. --> |
||
|
|
d67aa2889b |
feat(workflow): add update_agent tool and responseFormat-aware AI Agent step schema (#21755)
## Summary
Brings the workflow AI/MCP tooling for **AI Agent steps** to parity with
the existing **CODE / logic-function** flow, and makes AI Agent output
references reliable in validation and the variable picker.
Just like a CODE step needs a logic function, an `AI_AGENT` step needs
an agent. The agent is already created as a side effect of step
creation; this PR adds the missing "configure it" tooling and fixes the
output schema so it reflects the agent's actual response format.
## Changes
### New `update_agent` MCP tool
- `update-agent.tool.ts`: lets the assistant configure the agent backing
an `AI_AGENT` step — `prompt` (system prompt), optional `modelId`,
optional `responseFormat` (text or structured json) — via
`AgentService.updateOneAgent`. Direct analog of
`update_logic_function_source`.
- Wired through: added `agentService` to `WorkflowToolDependencies`,
injected `AgentService` and registered the tool in
`workflow-tool.workspace-service.ts`, imported `AiAgentModule` in
`workflow-tools.module.ts`.
### Guided creation flow (mirrors CODE)
- `create-workflow-version-step.tool.ts`: `enrichResultWithNextStep` now
returns an `AI_AGENT` hint instructing the assistant to call
`update_agent` with the step's `settings.input.agentId` (and to set the
task prompt via `update_workflow_version_step` if needed).
- `create-complete-workflow.tool.ts`: rejects `AI_AGENT` steps (it
inserts steps directly and never runs the side effect that creates the
agent), with a description note pointing to
`create_workflow_version_step` + `update_agent`. Same treatment CODE
already gets.
### Correct output schema for AI Agent steps
- Backend `computeStepOutputSchema`
(`workflow-schema.workspace-service.ts`): the `AI_AGENT` case now
derives the output schema from the agent's `responseFormat` instead of a
hardcoded `{ response }`:
- text → `{ response: string }`
- json → one leaf per `responseFormat.schema.properties` field
This makes workflow validation resolve `{{stepId.fieldName}}` references
against the agent's real output (previously json agents validated wrong:
real fields rejected, `{{stepId.response}}` accepted but undefined at
runtime).
- Frontend `useStepsOutputSchema.ts`: when an `AI_AGENT` step has no
persisted `outputSchema`, fall back to generating it from the agent's
`responseFormat` (via `FindManyAgents` + the existing
`agentResponseSchemaToOutputSchema`) instead of the hardcoded `{
response }`. Keeps the variable picker correct for structured agents.
## Why output schema matters
Validation resolves every `{{stepId.path}}` against the referenced
step's `settings.outputSchema` (`validateWorkflowVariableReferences`).
The agent step's output schema is therefore the single source of truth
for "is the right output referenced." Because the runtime output depends
on `responseFormat` (text → `{ response }`, json → schema fields
directly), the schema must be derived from `responseFormat` to be
accurate.
## Not solved issue
We want each AI_AGENT step's settings.outputSchema to always match the
backing agent's responseFormat:
- text → { response }
- json → one field per responseFormat.schema.properties
That output schema is what everything downstream relies on: validation
(validateWorkflowVariableReferences resolves {{stepId.field}} against
it), the frontend variable picker (useStepsOutputSchema), and it's also
persisted inside workflowVersion.steps.
Agent should be unique source of truth but syncing agent -> step is not
possible
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21755?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. -->
|
||
|
|
3fe2aec5d1 |
Add search input to manual record trigger object select (#21777)
When configuring a manual record trigger in the workflow editor, the object selector could only be scrolled, not searched. This adds a search input to the object `Select` (via the existing `withSearchInput` prop), matching the behavior already used in the create-record action's object selector. ## Before <img width="786" height="544" alt="CleanShot 2026-06-18 at 13 35 45@2x" src="https://github.com/user-attachments/assets/30426a16-927e-4be3-80b6-73a296253c16" /> ## After <img width="786" height="628" alt="CleanShot 2026-06-18 at 13 35 22@2x" src="https://github.com/user-attachments/assets/57455518-0f95-483b-81d4-e1f4547c0d30" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21777?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. --> |
||
|
|
06cefb1dac |
Configure Recall bot server variables (#21774)
## What changed - Added server variables for Recall bot leave behavior - Added `RECALL_BOT_JOIN_EARLY_MINUTES` so the bot can join slightly before meeting start - Defaults stay aligned with Recall where applicable - Kept descriptions more human-friendly in app config + README <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21774?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. --> |
||
|
|
e7488deb58 |
Remove twenty-shared import from postcard app (#21786)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21786?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. --> |
||
|
|
9bc0db5666 |
fix: exclude non-groupBy date fields (deletedAt) from calendar field selection (#21764)
Closes #21608 The Tasks Calendar renders an empty grid because `GroupByTasks` fails with `Field "deletedAt" is not supported in groupBy` while the header count (`AggregateTasks`) still succeeds. The calendar renders by grouping records on the selected date field. Calendar-field eligibility only checked `isFieldMetadataDateKind`, so `deletedAt` (a system DATE_TIME field) could be picked or auto-defaulted as the calendar field — and the groupBy engine correctly rejects it (only `createdAt`/`updatedAt` are groupable system date fields). Fix: gate calendar-field eligibility on `isFieldMetadataSupportedInGroupBy` (the same authority the backend groupBy validator uses), so non-groupable date fields can no longer be selected. - `useGetAvailableFieldsForCalendar` — add the groupBy-support check alongside the date-kind filter - `ObjectOptionsDropdownCalendarFieldsContent` — reuse the hook's list instead of re-filtering raw fields |
||
|
|
148d312cc7 |
Fix typecheck (#21783)
Typed `onNodeDragStop` as `@xyflow/react`'s `OnNodeDrag<WorkflowDiagramNode>` instead of hand-annotating the event param with the global DOM `MouseEvent | TouchEvent`, which didn't match the prop's expected React `MouseEvent` and broke typecheck. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21783?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. --> |
||
|
|
e7e99247e8 |
Centralize and standardize impersonation validation rules (#21717)
# Introduction Followup https://github.com/twentyhq/twenty/pull/21707 ## Behavioral change worth calling out Server-level impersonation now requires verified 2FA outside development at every checkpoint (generation, exchange, and per-request). In main the 2FA gate only existed in ImpersonationService. This is the right tightening, but it means existing server-admin impersonation sessions in production for admins without verified 2FA will now be rejected on the next request, not just at token creation. cc @s0yd4RK <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21717?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: s0yd4RK <285671363+s0yd4RK@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
465eb05aaf |
Reworked website. (#21763)
twenty-website had accumulated structural problems that were cheaper to fix by rebuilding than to refactor in place: - Drift had no guardrails. Values were matched at call sites instead of single-sourced, so things silently diverged — e.g. the radius token base was wrong for days (every radius() consumer rendered double) because nothing measured it against the old site's CSS variables. - A whole tree escaped quality checks. src/lib/ (~9.8k lines) was never format-checked, because oxfmt silently ignores directories named lib/. - Inconsistent rhythm. Hero spacing varied 24–88px between pages (CEO-flagged), because section spacing wasn't a token. - Over-extraction. -config.ts sprawl pulled single-component configuration out into the wrong place. The goal: a ground-up rebuild where drift is structurally impossible, held to a Linear / Railway / Notion / Attio quality bar. The old site is treated as source of intent only — nothing is blindly ported; every piece is re-decided and A/B-verified. **Rebuild** A full rebuild on Next 16 + Turbopack + Linaria (zero-runtime CSS), ~1,100 files. Marketing pages (home, product, pricing, partners + marketplace, customers/case-studies, why-twenty, releases, legal), the interactive AppPreview product mockup, the platform/visuals WebGL system (engine + rigs, three code-split off every initial chunk), and the standalone /halftone studio (the dev tool that generates the site's halftone art — engine, exporters, and full UI ported as an isolated island). **Architecture & guarantees** - Parity by construction. src/tokens/definitions.ts is the only file with raw values; the :root CSS-variable block is generated from it at build time and accessors derive var names through the same helpers — derived alpha tokens appear in served CSS without ever being hand-written. - Mobile-first by API shape. mediaUp() is the only media helper (no max-width helper exists, on purpose). - Section rhythm is a token (RHYTHM.section) — the hero-spacing inconsistency class is fixed by construction. - Fluid type ramps interpolate font-size and line-height between designed endpoints [390px → md]; TYPE_SCALE is the single source. - three.js never enters an initial chunk — confined to platform/visuals heavy zones, reached only via dynamic(ssr:false), enforced by check-visual-bundle. |
||
|
|
ccc77932a0 |
Tool execution metrics (#21587)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21587?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. --> |
||
|
|
8034c7725f |
Reorganize twenty-ui into best-practice component domains and per-component folders (#21745)
Reorganizes `twenty-ui`'s component organization to follow how the best
UI libraries (MUI, Mantine, Base UI, Polaris) structure their source,
now that the package has stabilized.
**Taxonomy** — dissolves the meaningless `components/` junk-drawer and
the 107-file `display/` mega-category. New domains/subpaths:
`data-display`, `typography`, `icon`, `surfaces`; `feedback` and
`layout` absorb the rest (banners/callout/info + placeholders →
feedback; modal/card → surfaces; motion + separators → layout).
**Per-component layout** — every component is now
`<domain>/<ComponentName>/<ComponentName>.tsx` with colocated
styles/stories/types, `internal/` for private helpers and `parts/` for
re-exported compound sub-parts. The redundant inner `/components/` is
gone. `icon` and `json-visualizer` are kept as cohesive subsystems.
**Also:** adds a tree-shakeable root barrel (`import { Button } from
'twenty-ui'`), the generator now owns `individual-entry.ts`, and a real
barrel-leak bug is fixed (private `internals/` parts were leaking into
the public API).
Consumer imports (~1.2k files) and the `twenty-sdk` UI aggregator were
updated by codemod. The change is **export-neutral** except 16
intentionally-removed private internals symbols (all verified
unconsumed). Gates green: typecheck, lint, build, size-limit, storybook.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21745?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. -->
|
||
|
|
640a8e6ca6 |
Add post-call recording ingestion and billing (#21758)
## Summary - Add post-call Recall recording ingestion for transcripts, audio, and video - Request/retrieve async transcripts and reconcile stale pending transcript markers - Complete call recordings atomically once all artifacts and billable timestamps are available - Charge `CALL_RECORDING` usage once per completed recording based on recording duration - Add Recall recording/media API helpers, transcript marker utilities, and audio/video field identifiers - Update generated metadata/SDK files and billing usage operation support - Add unit coverage for ingestion, completion, charging, Recall API behavior, and reconciliation flows <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21758?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. --> |
||
|
|
01bb2f4ab2 |
fix: enforce strict rules for currency value handling by ai chatbot (#21470)
opportunity: <img width="382" height="75" alt="image" src="https://github.com/user-attachments/assets/be443c29-0bca-4537-a775-01cdbf704cdb" /> fix: <img width="382" height="289" alt="image" src="https://github.com/user-attachments/assets/11aa9552-f3ac-4d25-b5aa-efbacfba3a13" /> closes #21419 --------- Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> |
||
|
|
39e00d5853 |
feat(workflow): expected output schema for runtime-output steps + validation (#21744)
## Summary Extends the workflow validation layer (introduced in #21422) and adds a new "expected output schema" capability for steps whose output structure is only known at runtime. Some workflow steps (HTTP Request, Code, Logic Function, AI Agent (coming soon), Webhook trigger) don't have a statically known output shape, so downstream steps can't resolve `{{step.x.y}}` variable paths or validate them. This PR lets users declare a **sample/expected output** for those steps, derives an output schema from it, and uses that schema both to power variable resolution and to surface validation issues at build time. ## What's included ### Expected output schema (shared schemas + types) - New `expectedOutputSchemaShape` reused across the HTTP request, code, logic function and AI agent action settings schemas, plus the webhook trigger schema (`expectedOutputSchema` optional loose object). - Mirrored on the server-side action/trigger settings types. ### Output schema computation (server) - `workflow-schema.workspace-service` now computes a step's output schema from the user-declared `expectedOutputSchema` sample (via `getOutputSchemaFromValue`) when no statically computed schema is available. ### Validation layer (server) - `STEP_HAS_NO_VARIABLE_REFERENCE` (warning): flags steps of `VARIABLE_CONSUMING_ACTION_TYPES` (HTTP_REQUEST, CODE, LOGIC_FUNCTION, SEND_EMAIL, record CRUD) that reference no upstream variable. - `LOGIC_FUNCTION_OUTPUT_SCHEMA_MISMATCH` / `AI_AGENT_OUTPUT_SCHEMA_MISMATCH` (warnings): compare the declared output schema against the expected sample using the new shared `getOutputSchemaMismatchIssues` util (missing keys, leaf/object mismatches, type mismatches). - Trigger is now validated alongside steps (trigger type requirements + trigger variable references). - Validation issues no longer return both `suggestions` and `availablePaths` when they are identical (avoids redundant, costly payloads). ### Shared utilities - New `getOutputSchemaMismatchIssues` (+ tests) in `twenty-shared/logic-function`. - Moved `agentResponseSchemaToOutputSchema` from `twenty-front` into `twenty-shared/ai` so it can be reused on both sides. ### Frontend - New `WorkflowExpectedOutputBodyInput` component (JSON sample editor with validation) used by HTTP request, code, logic function and AI agent step editors. - New `resolvePersistedStepOutputSchema` util + `useStepsOutputSchema` update: resolves a step's output schema from `outputSchema`, falling back to `expectedOutputSchema`, with an AI_AGENT default. - HTTP request / code / logic function editors persist `expectedOutputSchema` and derive `outputSchema` from it. - Webhook trigger default settings include `expectedOutputSchema`. BONUS : iterator loop validation <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21744?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. --> |
||
|
|
ecd90b78b9 |
fix(front): stop impersonation from corrupting the impersonator's profile name (#21757)
## Problem A customer reported that after impersonating another user, **their own account's first/last name had been permanently changed to the impersonated user's name** (persisted in the DB, surviving logout), and their actions showed up under the wrong "Updated by". The "Updated by = impersonated user" part is expected (while impersonating you genuinely act as that user). The real bug is the **durable overwrite of the impersonator's profile name**. ## Root cause About a week ago `currentUserState` became localStorage-backed with `getOnInit: true` (`currentWorkspaceMemberState` already was). Impersonation swaps the auth token and does a full reload, but — unlike sign-out — it never cleared those cached identity keys. So after the reload: 1. The atoms hydrate **synchronously from localStorage with the previous identity** (e.g. the impersonated user, on stop), and because `currentUser` is now non-null the authenticated UI renders immediately with that stale identity. 2. The network `loadCurrentUser` then corrects `currentUser` / `currentWorkspaceMember` **in place**. If **Settings → Profile** was mounted across that in-place identity flip, `NameFields` — which seeds local `useState` from `currentWorkspaceMember` once and auto-saves on change — read the stale name as a pending edit and debounce-saved it onto the **now-current** workspace member, persisting one user's name onto another. Read-only caches (object metadata, permissions) tolerate the same staleness because nothing writes them back — they're only ever overwritten by the network. `NameFields` is the one consumer that *persists* a cached identity value, which is what turns a transient stale read into a durable write. ## Fix Two small, complementary layers: - **`useImpersonationSession`** — clear the cached session identity (`clearSessionLocalStorageKeys()`) on both `startImpersonating` and `stopImpersonating`, before the reload. The reload then re-bootstraps from a clean slate for the correct user (and the brief stale-**permissions** flash goes away too). The admin's token stash lives in `sessionStorage` and is untouched; `tokenPairState` has its own key and is not in the cleared set. - **`NameFields`** — re-seed the inputs when the workspace-member **identity** changes, so an identity swap is never mistaken for a user edit. This closes the underlying footgun regardless of how the identity changes. ## Testing - Added `NameFields.test.tsx`: swapping `currentWorkspaceMemberState` to a different member must **not** trigger `updateWorkspaceMemberSettings`, while a genuine user edit still saves. Verified the test **fails without** the `NameFields` fix (it writes the previous member's name onto the new member) and **passes with** it. - `nx typecheck twenty-front`, `oxlint --type-aware`, and `oxfmt --check` all pass on the changed files; full `twenty-front` Jest suite green. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_015MVW3gg7CVq5oR572ctisx --- _Generated by [Claude Code](https://claude.ai/code/session_015MVW3gg7CVq5oR572ctisx)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21757?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
f96e36d3e6 |
fix(ai): prevent chat thread bricking from tool parts with null input (#21752)
## Problem Fixes #21695. An AI chat thread became **permanently unusable** — every subsequent message failed with `AI_APICallError: Internal server error` from Anthropic — when the thread history contained a tool part in `output-error` state with a **null input** (e.g. a tool call that failed input validation before execution, so neither `toolInput` nor `toolOutput` was ever captured). ## Validation of the reported findings I reproduced and confirmed the root cause empirically against the pinned `ai@6.0.97` SDK before writing the fix. **Root cause (confirmed from SDK source).** `convertToModelMessages` serializes every non-`input-streaming` tool part into a provider `tool_use` block, and for errored parts it uses: ```ts input: part.state === 'output-error' ? (part.input ?? ('rawInput' in part ? part.rawInput : undefined)) : part.input, ``` When both `input` and `rawInput` are nullish, the block is built with `input: undefined`, which `JSON.stringify` drops — so the HTTP payload carries a `tool_use` with **no `input` field**. This matches the reporter's minimal repro exactly (no `input` → `400 Field required`; `input: {}` → `200`). Inside a large streamed conversation the same malformed block surfaces as the generic `500`, and because the bad part is replayed on every turn the thread stays bricked. **Why #21276 didn't catch it.** `finalizeDanglingToolParts` only rewrote `input-available` parts; a part that arrives already in `output-error` with a null input was passed through untouched. **Note on current `main`.** A read-path default added recently (`mapDBPartToUIMessagePart`: `input: part.toolInput ?? {}`) already masks the live 500 on the standard reload path. However the gap is real and worth closing: the persist path still writes `toolInput = NULL` (the exact malformed rows the reporter found in `core."agentMessagePart"`), `finalizeDanglingToolParts` still doesn't normalize this case, and the protection rested on a single implicit default with no regression coverage. A small repro harness confirmed all of this: persisted `toolInput` was `undefined`, and a raw (non-defaulted) `output-error` part produced a `tool-call` whose `input` value was `undefined`. ## Fix Defense-in-depth so the invariant *"a tool part always carries a defined input"* holds at both the finalize and storage boundaries: - **`finalizeDanglingToolParts`** now backfills `input: {}` for `output-error` parts whose input is null, while preserving the original error message. This is the natural chokepoint (it already runs immediately before every persist). - **`mapUIMessagePartsToDBParts`** defaults a nullish tool input to `{}` so malformed rows are never persisted, independent of the caller. The existing read-path `?? {}` default is kept as a third safety net. ## Tests - Unit tests for `finalizeDanglingToolParts`: backfills `{}` for an `output-error` part missing its input, and preserves the existing validation error message. - Persistence test: `mapUIMessagePartsToDBParts` stores `{}` (never `null`) for a missing input. - End-to-end round-trip test: after finalize → persist → reload, `convertToModelMessages` produces a `tool-call` with a defined input and the errored call stays resolved. All three new core assertions were verified to **fail without the fix** and pass with it. Full AI module suite (97 tests) passes; `oxlint --type-aware`, `oxfmt`, and `tsgo` typecheck are clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01SpuX6Pp2yTevk1zKTRiB9G --- _Generated by [Claude Code](https://claude.ai/code/session_01SpuX6Pp2yTevk1zKTRiB9G)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21752?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
a1f79c4f40 |
fix(server): enforce lowercase universalIdentifier in sync (#21754)
## Context App sync fails when a manifest defines an entity with an uppercase UUID `universalIdentifier`. Postgres `uuid` columns normalize to lowercase on write, but the sync diff matches `universalIdentifier` strings case-sensitively. So an uppercase-defined entity never matches its lowercased DB row and is seen as delete + create on every sync, which trips downstream guards like "Parent navigation menu item not found". ## Change Reject non-lowercase `universalIdentifier`s at validation time in `WorkspaceEntityMigrationBuilderService.validateUniversalIdentifier` (right after the existing UUID-v4 check). This lives in the abstract base builder, so it covers every syncable entity type. App authors now get a clear "must be lowercase" error on the first sync instead of confusing downstream failures. Validation is sufficient here, no normalization needed — because the DB side is always lowercase, so rejecting uppercase input guarantees both sides of the diff match. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21754?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. --> |
||
|
|
d99e479be8 |
feat(billing) - facilitate top up in ai chat (#21645)
Today, when a trialing user hits their AI usage cap inside the Ask AI chat, ending the trial bounces them to the Stripe billing portal (and, for card-less users, loses their place in the conversation). This PR makes activating a paid plan / topping up credits feel seamless from within the chat: Trial users with a card on file activate their subscription in place, without leaving the app. Trial users without a card are sent to the Stripe payment-method portal and, on return, the trial is ended automatically and they're dropped back into the exact Ask AI thread they came from. Credit-exhaustion and trial banners now reflect whether a payment method exists (Add Credit Card vs Subscribe Now / End Trial Period) and upgrade inline via a confirmation modal instead of redirecting to Settings. Uploading Screen Recording 2026-06-16 at 07.51.12.mov… https://github.com/user-attachments/assets/4ea77273-da63-4b32-b6f1-5ac9e9560651 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21645?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. --> |
||
|
|
177afde866 |
[BREAKING CHANGE] fix chart cache collisions with key-based data plumbing (#21743)
closes https://discord.com/channels/1130383047699738754/1514946035317997709 This fixes Apollo cache collisions for pie slices and line series by keeping chart bucket identity as key end-to-end, matching how bar chart already works. What changed -- - Renamed pie/line chart response identity from id to key in the chart data path. - Kept key through frontend chart hooks, types, stories, and tooltip/drilldown logic. - Only adapt key to id at actual external boundaries like Nivo and GraphWidgetLegend. - Added/updated tests covering cache normalization and chart data behavior. before - <img width="2600" height="844" alt="CleanShot 2026-06-17 at 20 17 14@2x" src="https://github.com/user-attachments/assets/b9ee83e9-db4b-423e-8668-a7beb4c4c62e" /> after - <img width="2614" height="800" alt="CleanShot 2026-06-17 at 20 16 17@2x" src="https://github.com/user-attachments/assets/674a5417-ffc2-441d-9484-e1126438254c" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21743?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. --> |
||
|
|
02a3a3c47c |
fix(ai): handle dynamic-tool message parts in chat persistence (#21740)
## Summary Fixes #20558. AI chat streams crashed with `Unsupported part type: dynamic-tool` whenever the model emitted a *dynamic* tool call (a tool that isn't part of the bound schema). The assistant message never persisted, so the user saw a hard failure mid-stream. ## Root cause The AI SDK v6 emits two flavors of tool parts: - **Static** — `type: "tool-<toolName>"` (e.g. `tool-execute_tool`) - **Dynamic** — `type: "dynamic-tool"`, with the name on `part.toolName` `mapUIMessagePartsToDBParts` recognised tool parts with a homegrown check: ```ts part.type.includes('tool-') && 'toolCallId' in part ``` That returns `false` for `'dynamic-tool'` (it contains `-tool`, not `tool-`), so dynamic parts fell through to `throw new Error(\`Unsupported part type: ${part.type}\`)` during the `handleStreamFinish` persistence step. Stack trace from the issue matches exactly. The same broken heuristic was duplicated in: - `packages/twenty-server/.../mapDBPartToUIMessagePart.ts` (reverse mapper) - `packages/twenty-front/.../utils/mapDBPartToUIMessagePart.ts` (frontend mirror — would also throw on a `dynamic-tool` row reloaded from history) Meanwhile, two other call sites in the codebase (`finalize-dangling-tool-parts.util.ts`, `isThinkingStepPart.ts`) already correctly use the SDK's `isToolUIPart`, which natively recognises both flavors. ## What this PR does 1. **Switches all three mappers to the SDK's canonical check** (`isToolUIPart` on the forward path; explicit `dynamic-tool` + `tool-` startsWith on the reverse paths, where the input is an entity/DTO, not a UI part). 2. **Persists `toolName`** — the column already existed on the entity, DTO and GraphQL fragment but nothing wrote it. For static parts the name is recoverable from `type`; for dynamic parts it's the only place the name lives, so without it the round-trip is impossible. The shared denormalisation also helps existing per-tool analytics (`count-native-web-search-calls-from-steps.util.ts`). 3. **Reconstructs `dynamic-tool` parts on read** (with `toolName`) so they survive a DB round-trip both on the server and on the frontend history view. 4. **Adds a round-trip unit test** covering both `dynamic-tool` and a static tool part to lock the behavior in. ## Architecture notes (called out for review) - `mapDBPartToUIMessagePart` is duplicated frontend + backend because the input shape differs (TypeORM entity vs. GraphQL DTO). Out of scope to consolidate here, but they're drifting — this PR is what that drift looked like in production. Worth a follow-up to express the shared logic once over a unified row type. - I left the existing renderer guard `part.type !== 'dynamic-tool'` in `AiChatAssistantMessageRenderer.tsx` alone — it's a reasonable UI-side decision to not attempt to render an unknown dynamic tool generically. Persistence and history reload now work; rendering of dynamic tool calls is a separate UX decision. - No DB migration needed — the `toolName` column already exists. Old static rows have `toolName: null`; the reverse mapper recovers their name from the `type` column as before. Old dynamic-tool rows don't exist (they all threw on write). ## Test plan - [x] `yarn workspace twenty-server jest map-message-parts.dynamic-tool` — 5 passed - [x] `yarn workspace twenty-server jest finalize-dangling-tool-parts.roundtrip` — still 4 passed (no regression) - [x] `yarn nx typecheck twenty-server` — clean - [x] `yarn nx typecheck twenty-front` — clean - [x] `yarn nx lint:diff-with-main twenty-server` — clean - [x] `yarn nx lint:diff-with-main twenty-front` — clean - [ ] Manual: trigger an AI chat that exercises a dynamic tool (e.g. via an MCP server returning a tool not in the bound schema) and confirm the stream finishes and the message persists. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_013EE11eVWtyxmdcbEHVJKoc --- _Generated by [Claude Code](https://claude.ai/code/session_013EE11eVWtyxmdcbEHVJKoc)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21740?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
105f9565a5 |
feat(workflow): surface manual-trigger payload + metadata in variable picker (#21692)
## Summary Step 2 of the manual-trigger output schema restructuring (expand → display → migrate → contract). Builds on the now-merged #21676 (which expanded the runtime payload to serve `payload` and `metadata` siblings at the trigger root). This PR **surfaces** those in the variable picker as nested, expandable nodes: - `trigger.payload.{record fields}` — the record(s) that triggered the run - `trigger.metadata.workspaceMemberId` — who triggered it The flat root fields (`trigger.id`, etc.) remain available, so existing saved variable references keep working until a later migration phase moves them. ### Changes - **twenty-shared**: metadata/payload label constants + `build-manual-trigger-metadata-node` util + barrel exports. - **twenty-front**: `computeStepOutputSchema` MANUAL branch now nests `payload` (RecordNode for SINGLE_RECORD, array Node for BULK_RECORDS, omitted for GLOBAL) and `metadata`; `ManualTriggerOutputSchema` type updated to `{ payload?; metadata }`. - **twenty-server**: `computeTriggerOutputSchemaFromAvailability` mirrors the same nested shape for server-side validation. The key is `metadata` (not `_metadata`) — custom fields can't start with `_`, so collision risk was deemed acceptable. ## Test plan - [x] `npx nx build twenty-shared` - [x] `computeStepOutputSchema` unit tests pass (55) - [x] Manual: create a manual-trigger workflow (GLOBAL / single-record / bulk), confirm the picker shows `payload` and `metadata` as expandable folders and that selecting a field yields `{{trigger.payload.<field>}}` / `{{trigger.metadata.workspaceMemberId}}` > Note: server typecheck has pre-existing unrelated failures on main (Stripe billing mocks, gmail mocks); none touch workflow files. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21692?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. --> |
||
|
|
607d9ee6e5 |
fix(server): allow app-defined permission flags to be referenced by a role in the same sync (#21742)
## Context When an application defined custom permission flags and a role referencing them in the same sync, installation failed, first at validation (Permission flag not found) and then at execution (Migration action 'create' for 'rolePermissionFlag' failed). Root cause: both the migration builder order and the runner execution order processed rolePermissionFlag before permissionFlag, so the role's flag assignments were validated/inserted before the flags they reference existed. ## Changes - Builder order: run the permissionFlag builder before rolePermissionFlag so newly created flags are visible in the optimistic maps when assignments are validated. - Execution order: order the permission-flag actions so definitions are created before assignments, and assignments deleted before definitions, keeping the FK satisfied in both directions. - In-use check: move the "flag still assigned to a role" guard out of the per-entity deletion validator (order-dependent, false-positived when a flag and its assignments were deleted together) into a new order-independent validatePermissionFlagNotInUseCrossEntity (aligned with existing validateObjectMetadataCrossEntity, validateViewFieldLabelIdentifierCrossEntity, ...), run after all builders against the migration's final state. This fixes both the create path (define flag + reference it in one sync) and the teardown path (delete flag + its assignments in one sync). <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21742?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. --> |
||
|
|
d4e0b78d20 |
Add stale Recall bot reconciliation (#21720)
This PR adds a scheduled reconciliation pass for the Twenty Meeting Bot app so call recording state does not depend only on event-driven updates from calendar changes and Recall webhooks. Why we need this -- - A call recording row can be created, but the process can fail before the Recall bot id is written back. - Recall webhooks can be missed or delivered late, leaving Twenty stuck in an older local state. - A bot can disappear from Recall, leaving Twenty with a stale externalBotId. - A cancellation can fail locally, leaving an app-managed Recall bot that would still join the meeting. What this adds -- - A cron logic function that heals botless scheduled call recordings. - A convergence pass that pulls Recall bot state for stale local rows, including SCHEDULED rows. - Orphaned bot cleanup for app-managed Recall bots that are no longer claimed by an open call recording. - Guards so destructive bot cleanup does not affect bots claimed by another app registration. - Tests for the new stale-state, missed-webhook, and orphan cleanup behavior. Not included -- - Media ingestion. - Transcript pipeline. - Billing. - Marking call recordings as COMPLETED. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21720?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. --> |
||
|
|
9c249c766b |
i18n - docs translations (#21746)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21746?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> |
||
|
|
102c530d0f |
Add limit on view widget (#21718)
<img width="1345" height="463" alt="image" src="https://github.com/user-attachments/assets/a5d9ac2f-6375-4956-895d-3675aa9bebc1" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21718?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> |
||
|
|
60b559a659 |
Provide custom workspace id while seeding (#21721)
# Introduction Currently working on e2e test ci that will iterate over dedicated twenty instance. In order to allow multi concurrent tests to be performed we need to isolate testing context Allowing to provide custom workspaceId allow easy isolation and post test cleanup on aws related account close https://github.com/twentyhq/core-team-issues/issues/2556 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21721?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. --> |
||
|
|
8130fa1c45 |
feat(workflow): use workspace member as variable sender for emails (#21582)
## Summary
Lets the email workflow node sender be driven by a variable: the
connected-account field accepts a `{{variable}}`, and the backend
resolves it to a connected account at run time.
<img width="436" height="195" alt="Capture d’écran 2026-06-16 à 11 59
27"
src="https://github.com/user-attachments/assets/18eee21e-aed6-4447-9bf4-5cb0e2cfc371"
/>
### Email sender by variable
- The connected-account field now accepts a `{{variable}}` via the
variable picker (uses `FormSelectFieldInput` with
`WorkflowVariablePicker`), with a hint to pick a connected account or
set a workspace member as a variable.
- The email workflow action resolves the stored sender value explicitly:
if it is a `workspaceMemberId` (a UUID matching a workspace member), it
resolves that member's first connected account; otherwise the value is
used directly as a `connectedAccountId`.
- Resolution lives in `EmailWorkflowActionBase` and applies to both
`SEND_EMAIL` and `DRAFT_EMAIL`. If a matching member has no connected
account, the run fails fast with a clear message (no silent fallback).
- `DRAFT_EMAIL` also fails fast when the resolved connected account is
missing the required OAuth scopes (`gmail.compose` / `Mail.Send`), via a
server-side `getMissingDraftEmailScopes` util that mirrors the front-end
check.
- Existing workflows with a hardcoded `connectedAccountId` keep working
unchanged (no migration needed).
> Note: exposing the running workspace member as a manual-trigger
variable (`_metadata.workspaceMemberId`) is split into a follow-up PR.
## Test plan
- [x] Backend unit tests for `draft-email-tool`,
`get-missing-draft-email-scopes`, and the `send-email` / `draft-email`
workflow actions (incl. workspace-member sender resolution)
- [x] Lints clean on all changed files
- [x] Manual: configure an email node with a workspace-member variable
sender and confirm it resolves and drafts/sends
- [x] Manual: confirm a member lacking compose permission fails the run
with the permission message
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21582?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. -->
---
### Update — scoped to Draft Email only
The sender variable picker is now exposed **only on the Draft Email
node**. The Send Email node keeps a plain account select (no variable
picker, no variable hint) until we enable it there in a follow-up.
Backend resolution still lives in `EmailWorkflowActionBase` and remains
generic, so enabling the picker for Send Email later requires no backend
change.
|
||
|
|
3ee93b5ec9 |
feat(server): add isSystemSideEffect & merge createOneObject/createOneField side-effect migrations (#21673)
## Context When an object is created via the metadata API, `createOneObject` creates its side-effect entities (INDEX view + viewFields, indexes, navigation menu item, "go to" command menu item, record-page fields view, page layout/tabs/widgets) across **three separate `validateBuildAndRunWorkspaceMigration` calls**, purely because the protection behavior (mutations → overrides, delete → deactivate, reset → reactivate) was keyed on *"owned by the standard app"*, forcing the side effects into batches with different application owners. This misrepresents ownership and breaks atomicity. This PR separates two orthogonal concepts: - **Ownership** (`applicationId`), the true owner: the caller's application (the workspace custom app today, 3rd-party apps later). - **Protection** (`isSystemSideEffect`), the row was generated by the system, so user mutations route to overrides, deletion becomes deactivation, and reset restores defaults. Once side effects are re-owned to the caller, the old `applicationId === standardApp` check can no longer tell an original side-effect row from a user-added one so a dedicated `isSystemSideEffect` flag carries the protection instead. This is **PR 1 of 2** (forward-only). It makes newly created objects and fields correct; existing workspaces are handled by a follow-up backfill (see *Out of scope*). ## What this PR does - **`isSystemSideEffect` column** on the 8 affected entities (`view`, `viewField`, `indexMetadata`, `commandMenuItem`, `pageLayout`, `pageLayoutTab`, `pageLayoutWidget`, `fieldMetadata`), with `@WasIntroducedInUpgrade` + an entry in the flat-entity property configuration (`toCompare: true`, read-only). - **Single atomic migration in `createOneObject`**: the three `validateBuildAndRunWorkspaceMigration` calls are merged into one, owned by the caller (`resolvedOwnerFlatApplication`) and the record-page view/fields, page layout, and navigation command item are re-owned to the caller and flagged `isSystemSideEffect: true`. `buildNavigationFlatCommandMenuItem` is parameterized with `applicationUniversalIdentifier` (no longer hardcoded to the standard app). - **Field-creation side effects** (`createManyFields`/`createOneField` already run as a single caller-owned migration, so no re-ownership/merge was needed): the auto-created viewField is flagged `isSystemSideEffect: true`, and a new field now also propagates to the object's **INDEX/table view** (added there as a **hidden** column, `isVisible: false`) in addition to the record-page FIELDS widget. The INDEX view is targeted directly by `key = INDEX` (it is not a page-layout widget), de-duplicated per `(viewId, fieldMetadataUniversalIdentifier)` to respect the per-view unique index. The unique-field index is likewise flagged the inverse relation field stays unflagged (`isSystem: false`). - **Protection predicate** extended: `isCallerOverridingEntity` and the removal/reset split strategies now treat `isSystemSideEffect` rows as protected even when caller-owned (route to overrides / deactivate / reset) and the page-layout-reset guards allow resetting flagged entities. - **Standard compute maps** set the flag consistently so a re-sync produces no diff (standard-object side effects stay `false`; per-object nav command items and custom-object base fields are `true`). - **Read-only GraphQL exposure** of `isSystemSideEffect` on the view / view-field / page-layout / tab / widget / command-menu-item DTOs (not exposed on create/update inputs). => Todo: needs to take this new flag into account. This is fine for now because isSystem remains on object/field. - **Fast instance command** (`2-14`) adding the 8 columns (`NOT NULL DEFAULT false`). ## Scope decisions - **`pageLayout` is not an `OverridableEntity`**, its own row has nothing user-overridable (all customization lives on tabs/widgets). It's dual-purpose (`RECORD_PAGE` side-effect vs. user `DASHBOARD`), so it gets `isSystemSideEffect` for protection only, no `overrides` jsonb. - **`navigationMenuItem` is out of scope.**: Those are side effects only for the metadata API and not marked as "system" (they can be deleted/updated etc...) - **`viewFieldGroup` is not a side effect**, it's only created via the explicit view-field-group API, never by object/field creation, so it gets no flag. ## Out of scope (follow-ups) **PR 2** — slow per-workspace backfill (re-own + flag existing side effects, recreate missing ones) and deterministic v5 identifiers for base fields / pageLayout / tab. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21673?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. --> |
||
|
|
57d15fa73a |
i18n - docs translations (#21741)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21741?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> |
||
|
|
91050dba8d |
docs: update calendar-email page (#21719)
Adds 3 changes 1.) Add IMAP mention 2.) Clarifies disabling SSRF for self hosters running air gapped systems, (we have received this question several times ) 3.) Toggling Syncing internal emails <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21719?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. --> |
||
|
|
1f9c4cf5b9 |
fix(front): home redirect honors first object of the navigation menu (#21626)
https://github.com/user-attachments/assets/db11f02c-4502-4adc-8cf7-55d3dc0211a6 ## Summary Fixes [#21166](https://github.com/twentyhq/twenty/issues/21166). Going to the workspace root (`/`) always redirected to the alphabetically-first object. Following [the review discussion](https://github.com/twentyhq/twenty/issues/21166#issuecomment-4611570420), this redirects to the **first object of the navigation menu** instead of the last-visited object. "Last-visited" had too many edge cases (which page? records vs. index?), whereas the first item of the user's menu is unambiguous and matches what they see at the top of the left sidebar. ## Changes - New `getFirstObjectNavigationMenuItemLink` util walks the workspace navigation menu items in display order (sorted by `position`) and returns the link of the first object-backed item (`OBJECT`/`VIEW`) the user can read. - `useDefaultHomePagePath` uses it as the primary target. It stays on `AppPath.Index` until navigation menu items have loaded (they load *after* the minimal-metadata fast path, so resolving earlier would land on the wrong object during the post-login window), then falls back to the first readable object if the menu has no object item. Scope is intentionally minimal: the last-visited tracking is left in place and will be cleaned up separately now that the redirect no longer reads it. ## Test plan - `npx jest useDefaultHomePagePath --config=packages/twenty-front/jest.config.mjs` — covers menu order honored over alphabetical, `VIEW` item links, the loading deferrals (object metadata + navigation menu items), the readable-object fallback, and the no-readable-object profile-settings fallback. - Manually: visit a non-first object, navigate to `/`, confirm the redirect goes to the first object of the navigation menu. |
||
|
|
ece7a384df |
fix(front): wait for viewFields + fieldMetadataItems before opening the metadata gate (#21713)
## Problem On twenty-main, loading a record-index/standalone page for the first time renders the page chrome (title, view chip with record count) but the table body stays blank. A subsequent reload fixes it. Regression introduced by the cache-first `currentUser` bootstrap (#21532); follow-up to #21592, which already mentioned the experiment "should be reviewed." ## Root cause (concurrency) The metadata loader runs in two phases and the gate opens between them: 1. **`loadMinimalMetadata`** fast-paths `objectMetadataItems` and `views` to `status: 'up-to-date'` with only their *minimal* fields. `viewFields` and `fieldMetadataItems` stay `'empty'`. 2. **`IsMinimalMetadataReadyEffect`** opens the gate as soon as those two are `'up-to-date'` — before viewFields exist. 3. The page mounts. `viewsSelector` joins views with an empty `viewFields` collection, so `view.viewFields = []`. `RecordIndexLoadBaseOnContextStoreEffect` calls `loadRecordIndexStates(view, …)` with the empty viewFields and pins `loadedViewId === contextStoreCurrentViewId`. 4. `loadStaleMetadataEntities` later populates viewFields; the selector recomputes, but the effect bails out on the `loadedViewId` guard. `currentRecordFields` stays empty. 5. `visibleRecordFields` stays empty → `RecordTableVirtualizedInitialDataLoadEffect` hits its `isEmpty(visibleRecordFields)` guard and never fetches → empty body. The "300" count visible in the screenshot comes from `useGetRecordIndexTotalCount`'s separate aggregate query, which doesn't depend on viewFields. **Why the gate close/reopen self-heal doesn't work reliably:** `replaceDraft → applyChanges` happen in the same microtask chain. React 18 automatic batching collapses both into a single render where status goes `'empty' → 'up-to-date'` without an intermediate `'draft-pending'` observable to React. The gate never closes, children never unmount, `loadedViewId` is never reset. **Why it surfaced after #21532:** Before, `currentUser` was loaded only after `GetCurrentUser` returned — by which time `loadStaleMetadataEntities` had typically also completed and viewFields were populated when the gate opened. Now the cached `currentUser` lets the gate open the moment `loadMinimalMetadata` finishes. ## Fix Extend `IsMinimalMetadataReadyEffect` to also require `fieldMetadataItems` and `viewFields` to be `'up-to-date'` before opening the gate. Both are joined into the data the record-index page reads on first paint (`objectMetadataItemsWithFieldsSelector` reads fieldMetadataItems; `viewsSelector` reads viewFields), so the page can't render correctly without them. - **Warm cache** (all entities hydrated `up-to-date` from IndexedDB): unaffected — gate opens immediately. - **Cold cache and the first load post–IndexedDB-migration**: the gate stays closed until `loadStaleMetadataEntities` + `applyChanges` finish, then opens with full metadata. The page mounts once with a populated view; no race. ## Tests - [x] `nx typecheck twenty-front` clean (file change passes `oxlint` on the touched file). - [ ] Manual on twenty-main: cold reload + first navigation to a record-index page renders the table body without needing a second reload. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01DD3469JAWYURa2sKUTJ85e --- _Generated by [Claude Code](https://claude.ai/code/session_01DD3469JAWYURa2sKUTJ85e)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21713?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: Claude <noreply@anthropic.com> |
||
|
|
1400c6e952 |
i18n - docs translations (#21724)
Created by Github action <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21724?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> |
||
|
|
ca7ffb97b9 |
Added page card box-shadow (#21688)
## Summary - add a subtle left-side page card shadow in light and dark mode - preserve the existing border-ring box shadow - give the page card wrapper enough left padding for the shadow to render <img width="904" height="1116" alt="image" src="https://github.com/user-attachments/assets/2948fe4a-81ff-4045-ad6f-c0bc9b58be26" /> --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
34362de7b7 |
fix(route-trigger): distinguish user vs platform logic function execution errors (#21715)
## What Splits route trigger logic-function failures into two cases instead of one catch-all: - **User error** — the function's own code threw an uncaught error. Returns `500` and is **not** sent to Sentry. - **Platform error** — an infrastructure/execution failure on our side. Returns `500` and **is** sent to Sentry. A disabled logic function now returns `403`. ## Why User-code failures were flooding Sentry: a single workspace's function hitting a transient upstream error generated tens of thousands of events. #21656 stopped the flood by muting the entire route-trigger execution error bucket — but muting everything also silenced genuine platform failures we *do* want to be alerted on. Splitting the bucket keeps the user-code noise out of Sentry (the original goal) while making sure real platform errors still surface. Users who want to return a specific status/body when their function fails can still catch the error and return a `Response` — that path is unchanged. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21715?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. --> |
||
|
|
a7760c04ab |
Partners app: profile picture (additive file field), derived region & deployment, scope cleanup (0.5.4) (#21709)
## Summary (twenty-partners app, v0.5.4)
- **Profile picture upload (additive)**: `profilePicture` stays a URL
(LINKS) — existing partners keep their picture — and a new
`profilePictureFile` (FILES) field is added for uploads. The read logic
functions (`list-available-partners`, `get-partner-by-slug`) select both
and **prefer the uploaded file, falling back to the legacy URL**,
returning the existing `{ primaryLinkUrl }` shape so the public
directory and the website are unchanged.
- **Region** auto-derived from the partner's country on application
creation (static lookup).
- **Deployment expertise** derived: defaults to `CLOUD`, adds
`SELF_HOST` when the partner covers Hosting & Infrastructure.
- **Partner.website** now set from the submitted domain.
- Removed 5 unused `partnerScope` categories (0 production usage); seed
remapped.
- Removed one-off data scripts (`import-from-tft`,
`migrate-partner-scope`, `partner-scope-map`).
Rebased on `main` (includes #21615 company-reuse).
## Why additive, not a field-type change
Twenty treats a field's `type` as **immutable**: an app upgrade silently
ignores a LINKS→FILES change (`fieldMetadata.type` is `toCompare: false`
in the server's flat-entity config). An in-place flip would leave the
column LINKS on prod while the display queries asked for a FILES `url`,
**breaking the partner directory**. The additive `profilePictureFile`
upgrades cleanly with no data loss; existing URLs keep working via the
legacy field + fallback. Removing the 5 unused enum options is also a
clean upgrade (0 records use them).
## Deploy notes
- Version `0.5.4`. Fully additive schema change → installs in place, no
data migration required.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21709?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. -->
|
||
|
|
9c9c34fccf |
Remove twenty-ui-deprecated and migrate frontend to twenty-ui (#21596)
Migrates `twenty-front`, `twenty-sdk`, and `twenty-front-component-renderer` from `twenty-ui-deprecated` to `twenty-ui` (mechanical import swap — the packages have API parity) and deletes the deprecated package along with its workspace/CI/config wiring. Also adds `@linaria/react`/`@linaria/core` as direct deps of `twenty-front` (it used them transitively via the deprecated package). Note: move the required status check from `ci-ui-status-check` to `ci-new-ui-status-check`. Argos: the Storybook box-model/button-reset baseline shift (the bulk of the visual diffs) is isolated in #21665 — Storybook now loads twenty-ui's global `reset.scss`, which the production app already ships. Once #21665 merges and this branch is rebased, the remaining Argos diffs are component-level visual-parity items only. |