Commit Graph

131 Commits

Author SHA1 Message Date
Raphaël Bosi 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. -->
2026-06-18 10:31:29 +02:00
Raphaël Bosi 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.
2026-06-17 09:41:11 +00:00
Thomas Trompette ba94c3b857 feat(workflow): idempotent stop + retry failed runs from failing step (#21458)
https://github.com/user-attachments/assets/5a25396f-8959-4bd8-93cb-1187559ffe5f



## Summary

Two workflow-run improvements, with all non-trivial logic isolated in
pure, unit-tested utils.

### 1. Idempotent stop
`stopWorkflowRun` no longer throws when a run is already in a terminal
status (`COMPLETED` / `FAILED` / `STOPPED`) or already `STOPPING`; it
returns the run unchanged. This fixes:
- bulk stop aborting on the first non-stoppable run in a
mixed/select-all selection,
- the click-vs-processing race on a single run (run finishes between
click and mutation).

It also releases the cached not-started throttle slot when stopping a
`NOT_STARTED` run (prevents counter drift), and ends runs with no
`state` directly.

### 2. Retry a failed run from the failing step
New `retryWorkflowRun` mutation (same guards/passthrough as
`stopWorkflowRun`). It resets the failed step(s) to `NOT_STARTED`, flips
the run to `RUNNING`, and enqueues a `RunWorkflowJob` with the steps to
re-execute; downstream execution and status computation are unchanged.

Logic lives in pure utils:
- `build-retry-step-infos.util.ts` - decides per failed step what to
reset; delegates iterator-specific logic to
`build-retry-iterator-step-infos.util.ts` (an iterator that failed
mid-loop is restored to `RUNNING` with cursor preserved, an iterator
that failed itself restarts its whole loop).
- `get-runnable-step-ids.util.ts` - reuses the executor's
`shouldExecuteStep` to also resume branches that never started (avoids
hangs), excluding loop-interior steps.

The service method only orchestrates; the job's status check is a race
guard (retriability is enforced in the service before enqueue).

A "Retry" command menu item surfaces only for `FAILED` runs
(`someEquals(selectedRecords, "status", "FAILED")`).

### 3. Keep the run diagram visible across regenerations
The run diagram is regenerated on every run state change, producing
fresh nodes without the dimensions Reactflow had measured. Reactflow
hides unmeasured nodes until it re-measures them, so the diagram could
flicker and disappear when the last regeneration before going idle left
nodes unmeasured (reproducible after retrying a failed run). The
regenerated nodes now carry over the previously measured dimensions (by
id) so they stay rendered.

## Test plan
- [x] Unit tests for both retry utils (9 cases: plain failed step,
non-failed untouched, iterator mid-loop restore, iterator self-failure,
frontier parent gating, entry steps, loop-interior exclusion, parallel
branches)
- [x] `twenty-server` + `twenty-front` typecheck
- [x] `lint:diff-with-main` clean for both packages
- [x] Manual: retry a failed run repeatedly and confirm the diagram
stays visible
- [ ] Manual: stop a COMPLETED/mixed selection (no error), retry a
failed run and confirm it resumes from the failing step
2026-06-12 15:25:53 +00:00
Raphaël Bosi c596a5e342 Rename twenty-ui to twenty-ui-deprecated and twenty-new-ui to twenty-ui to prepare package release (#21315)
## Description

Promotes the next-gen UI library (formerly `twenty-new-ui`) to the name
**`twenty-ui`** (v0.1.0, publishable) and renames the old package to
**`twenty-ui-deprecated`**. Rewrites ~1,730 `twenty-ui` imports →
`twenty-ui-deprecated`, updates all configs/CI/Docker/deps, and migrates
twenty-front's `Toggle` to the new package (first consumer) as a
drop-in.

## Next steps
- Wire the `ui/v*` publish dispatch (`cd-deploy-tag.yaml` +
`.yarnrc.yml`), then tag `ui/v0.1.0` to publish.
- Continue migrating components from `twenty-ui-deprecated` →
`twenty-ui`.
2026-06-08 18:12:28 +02:00
Thomas Trompette 4e1cc2d831 fix: prevent workflow from disappearing after activation (#21231)
## Summary
- Fixes a regression from #21176 where activating a workflow caused it
to disappear until page refresh
- Root cause: when a draft is activated (status DRAFT→ACTIVE),
`useEffectiveDraftVersionId` incorrectly treated it as a discard because
the cached version was no longer DRAFT, filtering it from the versions
list
- Fix: only set `lastDiscardedDraftId` when `deletedAt` is actually set
on the cached version, not when the status simply changes

## Test plan
- [x] Open a workflow with a DRAFT version
- [x] Activate the workflow → verify it does NOT disappear
- [x] Discard a draft → verify header does NOT flicker between
DRAFT/ACTIVE
2026-06-04 16:42:05 +00:00
Marie 4b15b949f3 Provide additional logsobservability to workflow runs (per node) (#21142)
Surfaces per-step "Logs" tabs in the workflow run side panel so users
can see what each step actually did (model + tokens + tool calls for AI,
console output for serverless functions, request/response for HTTP,
recipients/body for Email).

<img width="546" height="501" alt="ai_agent_without_websearch"
src="https://github.com/user-attachments/assets/c6ca3518-9489-4484-a570-3d0569ff3b03"
/>

## Storage

- New `stepLogs` JSONB column on the `workflowRun` workspace entity,
typed as `Record<string, WorkflowRunStepLog>` (keyed by step id).
- Schema lives in `twenty-shared`: `workflowRunStepLogSchema` with a
discriminated `details.type` union for `AI_AGENT | CODE | HTTP_REQUEST |
EMAIL` — frontends and backends consume the same Zod-inferred type.
- Field is added to existing workspaces via a workspace upgrade command
(`2-9 add-workflow-run-step-logs-field`); the standard-object metadata
declares it for new workspaces.
- Writes happen atomically per step in
`WorkflowRunStepLogWorkspaceService.setStepLog` using `jsonb_set`. That
lets concurrent steps in the same run write their own keys without
contending with the existing lock around `workflowRun.state`.
- Per-step payload is hard-capped at 256 KB; anything larger is dropped
with a `logger.warn`, so a pathological tool call can never bloat a row.
See below for more information.

## How logs are produced

**Aalmost everything was already being collected; this PR mostly
persists and renders it.**

- **AI agent** — `AgentAsyncExecutorService` already tracked token
usage, model id, native web-search count, and the AI SDK's `steps[]`. We
map those into the log via `mapAiStepsToToolCallLogs` (`searchVector`
stripped from record outputs, per-call input/output capped at 32/64 KB,
max 200 tool calls per step). The only new measurement is a wall-clock
`durationMs` taken around `executeAgent`, and we now fold native
web-search cost into the displayed `totalCostInDollars` (it was already
billed, just not shown).
- **Code / serverless function** — reuses the `console.log` output the
function runner already returns (`logsByLevel`);
`build-code-step-log.util` only repackages it.
- **HTTP request** — built from the action's existing input/output via
`build-http-request-step-log.util`. No new signals collected.
- **Email (send / draft)** — added `sanitizedHtmlBody` + `plainTextBody`
to the existing tool outputs (a small additive change), then
`build-email-step-log.util` consumes them.

No additional AI inference or external calls are made for logging — the
cost is a small CPU overhead per step plus the JSONB write.

## Security

The log surface intentionally shows whatever the workflow touched, which
made redaction and sanitization the main design concern.

- **HTTP — secrets in headers**: existing `SENSITIVE_HEADER_NAMES` set
(Authorization, Cookie, …) replaced with `[redacted]` in both request
and response.
- **HTTP — secrets in URLs**: `SENSITIVE_URL_PARAM_NAMES` (e.g.
`api_key`, `token`, `access_token`) replaced in the query string via
`URL`-based parsing.
- **HTTP — secrets in bodies**: `SENSITIVE_BODY_KEY_REGEX` deep-walks
JSON request/response bodies (object input or stringified JSON) and
redacts matching keys. Applied to the `error` field too, since
transport-layer errors sometimes embed structured payloads.
- **Email — XSS risk in body preview**: tool outputs now expose a
server-side `sanitizedHtmlBody`; the log builder prefers it over the raw
user-authored `input.body`, with `plainTextBody` as a second fallback.
The original raw body is only used if sanitization didn't happen (e.g.
tool failed before composing).
- **AI — internal/noisy data**: `searchVector` (Postgres tsvector
strings) is stripped from record outputs returned by Twenty tools to
avoid leaking internal full-text-search payloads.
- **DB bloat / runaway agents**: 256 KB per-step cap + 32 KB / 64 KB
per-tool-call input/output cap + 200 tool calls per step.

<img width="547" height="307" alt="logic_function"
src="https://github.com/user-attachments/assets/dd4a3d16-67f2-434b-95b3-bdcaf9ed053d"
/>

## More details on Log size & truncation

Logs are stored in `workflowRun.stepLogs` (JSONB), keyed by `stepId`.

### Per-step cap

Each step's log is hard-capped at **256 KB** (`MAX_STEP_LOG_BYTES` in
`WorkflowRunStepLogWorkspaceService.setStepLog`).

For ~99% of workflows this is roomy — typical real-world sizes:
- Code / serverless function: 1–20 KB
- HTTP request: 5–70 KB
- Email: 5–30 KB
- AI agent (a handful of tool calls): 5–50 KB

### Two layers of bounding

1. **Per-field truncation** in each builder (before writing):
   - **Code**: ≤ 500 entries, ≤ 4 KB per message, ≤ 8 KB stack trace
   - **HTTP**: ≤ 32 KB per body (request + response), UTF-8 byte-aware
   - **Email**: ≤ 8 KB body preview, UTF-8 byte-aware
- **AI agent**: ≤ 32 KB tool input, ≤ 64 KB tool output, ≤ 200 tool
calls/step

2. **Global per-step safety net** at write time: if the assembled
`stepLog` still exceeds 256 KB, the write is **dropped entirely** with a
`logger.warn`. The workflow itself keeps running unaffected.

### What this means in practice

- **Safe**: workflow execution, step results, downstream steps — never
blocked by log size.
- **Safe**: iterators (each iteration overwrites the previous log for
that `stepId`, so they can't accumulate).
- **Safe**: step retries (same `stepId` is overwritten, not appended).
- **Possible**: an AI agent step with many large tool outputs (e.g., 50+
heavy `web_search` calls) can exceed 256 KB → the **entire** step's log
is dropped, side panel shows "No logs were recorded for this step". The
user has no explicit signal that the log was dropped due to size (only
server-side warn).
- **Possible** (theoretical): a workflow with hundreds of distinct steps
could push the row toward Postgres's internal ~256 MB jsonb limit.
Beyond that, individual `jsonb_set` writes would error and be swallowed
by the action's try/catch — workflow still completes.

### Possible future hardening (not in this PR)

- Replace "drop entire log" with a stub that preserves the summary card
(cost, duration, status) and marks `truncated.reason = 'size_cap'`.
- Surface size-drops in the UI (similar to the existing
`<StyledTruncatedNotice>`).
- Emit a metric so dropped logs are observable in dashboards.
2026-06-03 16:53:47 +00:00
Thomas Trompette ac0368e876 fix: eliminate workflow editing flicker on active-to-draft transitions (#21176)
## Before

https://github.com/user-attachments/assets/5108a9d8-2017-41d5-855c-98714cbd4237

## After

https://github.com/user-attachments/assets/0a78d1e1-354f-4f3f-8ec4-6f46517619e4

## Summary
- Fixes visual flickering/glitching in the workflow show page header and
canvas when editing an active workflow or discarding a draft
- Root cause: SSE events re-added discarded drafts to Apollo cache, and
multiple hook instances had independent state causing version
oscillation between DRAFT and ACTIVE
- Rewrites `useWorkflowWithCurrentVersion` with a module-level
`discardedDraftId` variable shared across all instances, Apollo cache
seeding in mutation callbacks, and `lastValidResult` caching to prevent
null renders

## Test plan
- [x] Open a workflow show page with an ACTIVE workflow
- [x] Drag a node to change position → verify no flicker, status shows
DRAFT smoothly
- [x] Discard the draft → verify header does NOT flicker between
DRAFT/ACTIVE, position resets cleanly
- [x] Click on manual trigger and edit settings → verify the edit works
(draft created, settings saved)
- [x] Repeat discard + edit cycle multiple times to confirm stability
2026-06-03 15:38:38 +00:00
Thomas Trompette bc1b7f6fdf fix: resolve workflow form step auto-open race condition (#21053)
## Summary
- Fix intermittent failure where the Quick Lead workflow form step did
not auto-open
- Root cause: race conditions between SSE events, Apollo cache writes,
and the `runWorkflowVersion` mutation timing
- Add generic monotonicity guard in the SSE handler that drops stale
updates for all records (not just WorkflowRun)

## Changes
- **`useTriggerOptimisticEffectFromSseUpdateEvents.ts`**: Compare
incoming `updatedAt` with cached record before writing — skip if stale.
Moved `upsertRecordsInStore` after the guard so neither Apollo cache nor
Jotai store receive stale data.
- **`useRunWorkflowVersion.tsx`**: Await mutation before opening side
panel; register SSE listener eagerly before mutation
- **`useWorkflowRun.ts`**: Simplified back to plain `useFindOneRecord` +
schema parse (no extra state needed)
- **`generateWorkflowRunDiagram.ts`**: `shouldOpenStep` matches both
PENDING and RUNNING for form steps (backend RUNNING means "waiting for
user input")
- **`WorkflowRunVisualizerEffect.tsx`**: Pass `runStatus` directly
without status mapping
- **`WorkflowRunStepNodeDetail.tsx`**: Form is interactive when step is
PENDING or RUNNING
- **Deleted `latestWorkflowRunFamilyState.ts`**: No longer needed — the
generic SSE guard replaces it

## Test plan
- [x] Hard refresh, run Quick Lead workflow 10+ times — form should
always auto-open
- [x] Complete the form and verify all subsequent steps execute without
getting stuck
- [x] Verify the workflow diagram is always visible (never disappears)
- [x] Verify other record types still update correctly via SSE (e.g.
edit a person in another tab)
2026-05-29 14:18:24 +00:00
Thomas Trompette be4b466234 Remove broken total count from workflow version (#20324)
## Problem

Opening a workflow run with a form step in the side panel, closing the
form and reopening it crashes the app: `<SidePanelWorkflowStepInfo>`
blows up on `workflow.versions.find` because `versions` is `null` in the
Apollo cache.

## Root cause

`useWorkflowVersion` was selecting:

```ts
workflow: { id, name, statuses, versions: { totalCount: true } }
```
Twenty's GraphQL field generator doesn't support connection-level
scalars — { totalCount: true } is interpreted as fields on the inner
WorkflowVersion node, gets filtered out, and the query collapses to:
versions { edges { node { __typename } } }
The server returns versions: null for that empty-node selection. 

Why now
The selection has always been wrong, but two recent changes made it
consistently surface:

Apollo Client v4 upgrade (#18584): stricter normalized writes, null
always wins.
#20242: WorkflowRunSSESubscribeEffect in the form filler keeps SSE
flowing, which re-fires useWorkflowVersion more often, making the bad
query consistently the last writer.
2026-05-07 09:57:10 +02:00
Raphaël Bosi 9f95c4763c [COMMAND MENU ITEMS] Remove deprecated code (#19199)
This PR is the first one of a cleanup after upgrading command menu items
to V2.
2026-04-01 15:56:52 +00:00
Charles Bochet d5a7dec117 refactor: rename ObjectMetadataItem to EnrichedObjectMetadataItem and clean up metadata flows (#18830)
## Summary

- Renames `ObjectMetadataItem` to `EnrichedObjectMetadataItem` across
the entire frontend (~440 files) to clarify that this type includes
derived fields (`readableFields`, `updatableFields`, nested `fields[]`,
`indexMetadatas[]`) computed at read time from the metadata store
- Creates `splitObjectMetadataGqlResponse` that goes directly from a
GraphQL `ObjectMetadataItemsQuery` response to flat store items
(combining the old
`mapPaginatedObjectMetadataItemsToObjectMetadataItems` +
`splitObjectMetadataItemWithRelated` two-step flow into one call)
- Removes `ObjectMetadataItemWithRelated` type and all "WithRelated"
naming
- Renames `generatedMockObjectMetadataItems` to
`generateTestEnrichedObjectMetadataItemsMock` to make it clear this is
test-only enriched data
- Deletes `useLoadMockedObjectMetadataItems` hook (consolidated into
`useLoadMockedMinimalMetadata`)
- Ensures nothing destined for the metadata store computes
`readableFields`/`updatableFields` (preventing the localStorage bloat
from #18809)

## Type hierarchy (before → after)

**Before:**
```
ObjectMetadataItemsQuery → mapPaginated → ObjectMetadataItemWithRelated → enrich → ObjectMetadataItem
                                        → split → FlatObjectMetadataItem (store)
```

**After:**
```
ObjectMetadataItemsQuery → splitObjectMetadataGqlResponse → FlatObjectMetadataItem (store)
                         → mapPaginated + enrich (tests only) → EnrichedObjectMetadataItem
```

## Test plan

- [x] `npx nx typecheck twenty-front` passes
- [x] `npx nx test twenty-front` passes (767 suites, 4505 tests)
- [x] `npx nx lint twenty-front` passes
- [ ] CI checks pass


Made with [Cursor](https://cursor.com)
2026-03-22 15:53:47 +01:00
Charles Bochet 1be87eb97b chore: frontend dead code removal and naming cleanup (#18690)
## Summary

- **Delete 10 unused files**: 7 hooks (`useWorkflowRunUnsafe`,
`useGetViewById`, `useCreateViewFieldGroup`, `useDeleteViewFieldGroup`,
`useUpdateViewFieldGroup`, `useCreateManyViewFieldGroups`,
`useMoveViewColumns` + test), 1 component (`SettingsSummaryCard`), 1
utility (`createEventContext`)
- **Rename `objectMetadataItemsState` → `objectMetadataItemsSelector`**
across ~85 files to accurately reflect it is a derived selector (via
`createAtomSelector`), not a base Jotai atom

## Details

### Dead code removed

| Type | Name | Reason |
|------|------|--------|
| Hook | `useWorkflowRunUnsafe` | Never imported — duplicate of
`useWorkflowRun` without schema validation |
| Hook | `useGetViewById` | Never imported — `useViewById` is used
instead |
| Hook | `useCreateViewFieldGroup` | Never imported — CRUD done via
`usePerformViewFieldGroupAPIPersist` |
| Hook | `useDeleteViewFieldGroup` | Same as above |
| Hook | `useUpdateViewFieldGroup` | Same as above |
| Hook | `useCreateManyViewFieldGroups` | Same as above |
| Hook | `useMoveViewColumns` | Only imported by its own test — no
production usage |
| Component | `SettingsSummaryCard` | Never imported anywhere |
| Utility | `createEventContext` | Never imported anywhere |

### Rename

`objectMetadataItemsState` is created via `createAtomSelector` (it
derives from `objectMetadataItemsWithFieldsSelector`), so naming it
`*State` is misleading. Renamed to `objectMetadataItemsSelector` for
consistency with sibling selectors like
`objectMetadataItemsByNamePluralMapSelector`.
2026-03-17 00:49:37 +01:00
Félix Malfait b470cb21a1 Upgrade Apollo Client to v4 and refactor error handling (#18584)
## Summary
This PR upgrades Apollo Client from v3.10.0 to v4 and refactors error
handling patterns across the codebase to use a new centralized
`useSnackBarOnQueryError` hook.

## Key Changes

- **Dependency Update**: Upgraded `@apollo/client` from `^3.10.0` to
`^3.11.0` in root package.json
- **New Hook**: Added `useSnackBarOnQueryError` hook for centralized
Apollo query error handling with snack bar notifications
- **Error Handling Refactor**: Updated 100+ files to use the new error
handling pattern:
  - Removed direct `ApolloError` imports where no longer needed
- Replaced manual error handling logic with `useSnackBarOnQueryError`
hook
- Simplified error handling in hooks and components across multiple
modules
- **GraphQL Codegen**: Updated codegen configuration files to work with
Apollo Client v3.11.0
- **Type Definitions**: Added TypeScript declaration file for
`apollo-upload-client` module
- **Test Updates**: Updated test files to reflect new error handling
patterns

## Notable Implementation Details

- The new `useSnackBarOnQueryError` hook provides a consistent way to
handle Apollo query errors with automatic snack bar notifications
- Changes span across multiple feature areas: auth, object records,
settings, workflows, billing, and more
- All changes maintain backward compatibility while improving code
maintainability and reducing duplication
- Jest configuration updated to work with the new Apollo Client version

https://claude.ai/code/session_019WGZ6Rd7sEHuBg9sTrXRqJ

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-13 14:59:46 +01:00
nitin 36bcc71f3d refactor(command-menu-item): rename Actions to CommandMenuItem (#18489)
actions are being renamed to command menu item, they will be migrated to
server and will be served as headless front components

---------

Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
2026-03-09 14:03:12 +00:00
nitin 5853891b02 refactor!: rename Command Menu page/navigation layer to Side Panel (#18393) 2026-03-05 15:46:31 +01:00
Raphaël Bosi b11f77df2a [FRONT COMPONENTS] Introduce conditionalAvailabilityExpression to command menu items (#18319)
## PR Description

- Uses `expr-eval` to enable front components (SDK plugins) to define
conditional availability as declarative expressions.
- Moves shared types and constants to `twenty-shared`
- Introduces a `conditionalAvailabilityExpression` field on
`CommandMenuItemEntity`, allowing command menu items to store an
`expr-eval` compatible expression string that is evaluated against a
CommandMenuContext to determine if the item should be shown.
- Creates an esbuild transform plugin
`conditional-availability-transform-plugin` in `twenty-sdk` that
converts TypeScript conditional availability expressions into
`expr-eval` compatible syntax at build time, so SDK developers can write
natural TS expressions that get transformed to evaluable strings.
- Removes deprecated `forceRegisteredActionsByKey` state and its usage.
- Creates `useCommandMenuContext` hook that builds the full
`CommandMenuContext` object from React state, which is then passed to
`useCommandMenuItemFrontComponentActions` for evaluating conditional
availability expressions.
2026-03-04 16:33:58 +01:00
Charles Bochet bc4ae35bc4 Rework atom naming (#18240)
## Summary

- **Enhanced the `matching-state-variable` ESLint rule** to enforce
consistent naming for `ComponentState`, `FamilyState`, and
`ComponentFamilyState` hooks — previously it only covered `useAtomState`
and `useAtomStateValue`
- **The rule now checks 12 hooks** across three categories: value hooks
(`useAtomComponentStateValue`, `useAtomFamilyStateValue`, etc.), state
hooks (`useAtomComponentState`, `useAtomComponentFamilyState`), and
setter hooks (`useSetAtomState`, `useSetAtomComponentState`,
`useSetAtomFamilyState`, `useSetAtomComponentFamilyState`)
- **Fixed all 225 resulting lint violations** across 151 files, renaming
variables to match their state atom names (e.g. `currentViewId` →
`contextStoreCurrentViewId`, `selectedRecord` → `recordStore`). Cases
where the same state is accessed with different family keys/instance IDs
are suppressed with `eslint-disable-next-line`.

## Naming convention

| Hook | State argument | Valid | Invalid |
|------|---------------|-------|---------|
| `useAtomStateValue` | `fooState` | `const foo = ...` | `const bar =
...` |
| `useAtomComponentStateValue` | `fooComponentState` | `const foo = ...`
| `const bar = ...` |
| `useAtomFamilyStateValue` | `fooFamilyState` | `const foo = ...` |
`const bar = ...` |
| `useAtomComponentFamilyStateValue` | `fooComponentFamilyState` |
`const foo = ...` | `const bar = ...` |
| `useAtomState` | `fooState` | `const [foo, setFoo] = ...` | `const
[bar, setBar] = ...` |
| `useAtomComponentState` | `fooComponentState` | `const [foo, setFoo] =
...` | `const [bar, setBar] = ...` |
| `useAtomComponentFamilyState` | `fooComponentFamilyState` | `const
[foo, setFoo] = ...` | `const [bar, setBar] = ...` |
| `useSetAtomState` | `fooState` | `const setFoo = ...` | `const setBar
= ...` |
| `useSetAtomComponentState` | `fooComponentState` | `const setFoo =
...` | `const setBar = ...` |
| `useSetAtomFamilyState` | `fooFamilyState` | `const setFoo = ...` |
`const setBar = ...` |
| `useSetAtomComponentFamilyState` | `fooComponentFamilyState` | `const
setFoo = ...` | `const setBar = ...` |
2026-02-25 22:28:25 +01:00
Charles Bochet d81f006979 Rename Jotai state hooks and utilities to consistent Atom-based naming (#18209)
## Summary

Rename all Jotai-based state management hooks and utilities to a
consistent naming convention that:
1. Removes legacy Recoil naming (`useRecoilXxxV2`, `createXxxV2`)
2. Always includes `Atom` in the name to distinguish from jotai native
hooks
3. Follows a consistent ordering: `Atom` → `Component` → `Family` →
`Type` (`State`/`Selector`)
4. Includes the type qualifier (`State` or `Selector`) in all
value-reading hooks to avoid naming conflicts with jotai's native
`useAtomValue`

## Naming Convention

**Hooks**:
`use[Set]Atom[Component][Family][State|Selector][Value|State|CallbackState]`
**Utils**: `createAtom[Component][Family][State|Selector]`

## Changes

### Hooks renamed (definition files + all usages across ~1,500 files):

| Old Name (Recoil) | Intermediate (Atom) | Final Name |
|---|---|---|
| `useRecoilValueV2` | `useAtomValue` | **`useAtomStateValue`** |
| `useRecoilStateV2` | `useAtomState` | `useAtomState` |
| `useSetRecoilStateV2` | `useSetAtomState` | `useSetAtomState` |
| `useFamilyRecoilValueV2` | `useFamilyAtomValue` |
**`useAtomFamilyStateValue`** |
| `useSetFamilyRecoilStateV2` | `useSetFamilyAtomState` |
**`useSetAtomFamilyState`** |
| `useFamilySelectorValueV2` | `useFamilySelectorValue` |
**`useAtomFamilySelectorValue`** |
| `useFamilySelectorStateV2` | `useFamilySelectorState` |
**`useAtomFamilySelectorState`** |
| `useRecoilComponentValueV2` | `useAtomComponentValue` |
**`useAtomComponentStateValue`** |
| `useRecoilComponentStateV2` | `useAtomComponentState` |
`useAtomComponentState` |
| `useSetRecoilComponentStateV2` | `useSetAtomComponentState` |
`useSetAtomComponentState` |
| `useRecoilComponentFamilyValueV2` | `useAtomComponentFamilyValue` |
**`useAtomComponentFamilyStateValue`** |
| `useRecoilComponentFamilyStateV2` | `useAtomComponentFamilyState` |
`useAtomComponentFamilyState` |
| `useSetRecoilComponentFamilyStateV2` |
`useSetAtomComponentFamilyState` | `useSetAtomComponentFamilyState` |
| `useRecoilComponentSelectorValueV2` | `useAtomComponentSelectorValue`
| `useAtomComponentSelectorValue` |
| `useRecoilComponentFamilySelectorValueV2` |
`useAtomComponentFamilySelectorValue` |
`useAtomComponentFamilySelectorValue` |
| `useRecoilComponentStateCallbackStateV2` |
`useAtomComponentStateCallbackState` |
`useAtomComponentStateCallbackState` |
| `useRecoilComponentSelectorCallbackStateV2` |
`useAtomComponentSelectorCallbackState` |
`useAtomComponentSelectorCallbackState` |
| `useRecoilComponentFamilyStateCallbackStateV2` |
`useAtomComponentFamilyStateCallbackState` |
`useAtomComponentFamilyStateCallbackState` |
| `useRecoilComponentFamilySelectorCallbackStateV2` |
`useAtomComponentFamilySelectorCallbackState` |
`useAtomComponentFamilySelectorCallbackState` |

### Utilities renamed:

| Old Name (Recoil) | Final Name |
|---|---|
| `createStateV2` | **`createAtomState`** |
| `createFamilyStateV2` | **`createAtomFamilyState`** |
| `createSelectorV2` | **`createAtomSelector`** |
| `createFamilySelectorV2` | **`createAtomFamilySelector`** |
| `createWritableSelectorV2` | **`createAtomWritableSelector`** |
| `createWritableFamilySelectorV2` |
**`createAtomWritableFamilySelector`** |
| `createComponentStateV2` | **`createAtomComponentState`** |
| `createComponentFamilyStateV2` | **`createAtomComponentFamilyState`**
|
| `createComponentSelectorV2` | **`createAtomComponentSelector`** |
| `createComponentFamilySelectorV2` |
**`createAtomComponentFamilySelector`** |

## Details

- All definition files renamed to match new convention
- All import paths and usages updated across the entire `twenty-front`
package
- Jotai's native `useAtomValue` is aliased as `useJotaiAtomValue` in the
wrapper hook to avoid collision
- Legacy Recoil files in `state/utils/` and `component-state/utils/`
left untouched (separate naming scope)
- Typecheck passes cleanly
2026-02-25 01:55:46 +01:00
Charles Bochet eaf9fe27b2 Improve Jotai work (#18205) 2026-02-24 20:38:30 +01:00
Charles Bochet 0f2af6a6cb Jotai 13 (#18178)
## Recoil to Jotai Migration — PR 13: Workflow, Page Layout, AI,
Activities, Settings, SSE & Remaining Modules

### Summary

This is the 13th PR in the [14-PR migration
series](packages/twenty-front/src/modules/ui/utilities/state/jotai/MIGRATION_PLAN.md)
to replace Recoil with Jotai as the state management library in
`twenty-front`. This PR migrates the remaining consumer code across all
modules — updating ~1,076 files with ~10k insertions/deletions.

### What changed

**New Jotai infrastructure (13 new files):**
- `createComponentSelectorV2` and `createComponentFamilySelectorV2` —
instance-scoped derived atoms, replacing Recoil's component selectors
- 6 new hooks: `useRecoilComponentSelectorValueV2`,
`useRecoilComponentFamilySelectorValueV2`,
`useRecoilComponentSelectorCallbackStateV2`,
`useRecoilComponentFamilySelectorCallbackStateV2`,
`useRecoilComponentStateCallbackStateV2`,
`useRecoilComponentFamilyStateCallbackStateV2`
- New types: `ComponentSelectorV2`, `ComponentFamilySelectorV2`,
`SelectorCallbacksV2`
- Extended `buildGetHelper` to support the new selector patterns

**State definition migrations:**
- `createState()` → `createStateV2()` (Jotai `atom()`)
- `createFamilyState()` → `createFamilyStateV2()` (Jotai `atom()` with
family cache)
- Recoil `selector`/`selectorFamily` → Jotai derived `atom()` via V2
utilities

**Hook migrations (~2,400 removed, ~1,545 added V2 equivalents):**
- `useRecoilValue` → `useRecoilValueV2`
- `useRecoilState` → `useRecoilStateV2`
- `useSetRecoilState` → `useSetRecoilStateV2`
- `useRecoilCallback` → `useCallback` + `jotaiStore.get/set`
- `useRecoilComponentValue` → `useRecoilComponentValueV2`
- `useSetRecoilComponentState` → `useSetRecoilComponentStateV2`
- `useRecoilComponentFamilyValue` → `useRecoilComponentFamilyValueV2` /
`useFamilySelectorValueV2`
- ~397 direct `import from 'recoil'` removed

**Deleted files (9 files):**
- `dateLocaleState.ts` — consolidated
- `currentAIChatThreadTitleStateV2.ts` — renamed to replace the original
- `isCommandMenuOpenedState.ts` — removed
- `filesFieldUploadState.ts` — replaced by V2
- `recordStoreFamilySelector.ts`, `recordStoreFieldValueSelector.ts`,
`recordStoreRecordsSelector.ts` — replaced by V2 equivalents
- `settingsAllRolesSelector.ts` — replaced by `useSettingsAllRoles` hook
- `settingsRoleIdsStateV2.ts` — consolidated

**Modules migrated:**
- Workflow (diagram, steps, variables, filters)
- Page Layout (widgets, fields, graph, tabs)
- AI (chat, agents, browsing context)
- Activities (rich text editor, targets, timeline)
- Action Menu (single/multiple record actions)
- Command Menu (navigation, history, pages, workflow)
- Context Store
- Record Board, Record Table, Record Calendar, Record Index
- Record Field, Record Filter, Record Sort, Record Group
- Record Drag, Record Picker, Record Store
- Views (view bar, view picker, filters, sorts)
- Settings (roles, permissions, SSO, security, data model, developers,
domains)
- SSE (event streams, subscriptions)
- Spreadsheet Import
- Auth, Favorites, Front Components, Information Banner
2026-02-24 17:37:01 +01:00
Charles Bochet b51eb6471f Jotai 12 (#18160)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-23 12:01:37 +01:00
Raphaël Bosi 2455c859b4 Add SSE for metadata and plug front components (#17998)
Create the necessary tooling to listen to metadata events and plug it to
the front components. Now we have a hot reload like experience when we
edit a component in an app.

## Backend

- Split `EventWithQueryIds` into `ObjectRecordEventWithQueryIds` and
`MetadataEventWithQueryIds`
- Publish metadata event batches to active SSE streams in
`MetadataEventsToDbListener`

## Frontend

- Create a metadata event dispatching pipeline: SSE metadata events are
grouped by metadata name, transformed into
`MetadataOperationBrowserEventDetail` objects, and dispatched as browser
`CustomEvents`
- Add `useListenToMetadataOperationBrowserEvent` hook for consuming
metadata operation events filtered by metadata name and operation type
- Rename `useListenToObjectRecordEventsForQuery` to
`useListenToEventsForQuery`, now accepting both
`RecordGqlOperationSignature` and `MetadataGqlOperationSignature`
- Implement `useOnFrontComponentUpdated` which subscribes to front
component metadata events and updates the Apollo cache when the
component is modified
- Add `builtComponentChecksum` to the front component query and appends
it to the component URL for browser cache invalidation
2026-02-18 11:26:20 +00:00
Charles Bochet 9e21e55db4 Prevent leak between /metadata and /graphql GQL schemas (#17845)
## Fix resolver schema leaking between `/metadata` and `/graphql`
endpoints

### Summary
- Patch `@nestjs/graphql` to support a `resolverSchemaScope` option that
filters resolvers at both schema generation and runtime, preventing
cross-endpoint leaking
- Introduce `@CoreResolver()` and `@MetadataResolver()` decorators to
explicitly scope each resolver to its endpoint
- Move most resolvers (auth, billing, workspace, user, etc.) to the
metadata schema where the frontend expects them; only workflow and
timeline calendar/messaging resolvers remain on `/graphql`
- Fix frontend `SSEQuerySubscribeEffect` to use the default (metadata)
Apollo client instead of the core client

### Problem
NestJS GraphQL's module-based resolver discovery traverses transitive
imports, causing resolvers from `/metadata` modules to leak into the
`/graphql` schema and vice versa. This made the schemas unpredictable
and tightly coupled to module import order.

### Approach
- Added `resolverSchemaScope` to `GqlModuleOptions` via a patch on
`@nestjs/graphql`, filtering in both `filterResolvers()` (runtime
binding) and `getAllCtors()` (schema generation)
- Each resolver is explicitly decorated with `@CoreResolver()` or
`@MetadataResolver()`
- Organized decorator, constant, and type files under `graphql-config/`
following project conventions


Core GQL Schema: (see: no more fields!)
<img width="827" height="894" alt="image"
src="https://github.com/user-attachments/assets/668f3f0f-485e-43f0-92be-4345aeccacb6"
/>

Metadata GQL Schema (see no more getTimelineCalendarEventsFromCompany)
<img width="827" height="894" alt="image"
src="https://github.com/user-attachments/assets/443913db-e5fe-4161-b0e7-4a971cc80a71"
/>
2026-02-11 10:05:24 +00:00
Thomas Trompette c18f574aee Trigger refetch workflow on version creation (#17685)
https://github.com/user-attachments/assets/ec156aba-3a67-4eef-addf-86158c3f1837
2026-02-03 17:52:30 +00:00
Lucas Bordeau 2cff75d772 Refactored object operation dispatch for SSE (#17174)
This PR refactors object record operation dispatch through a browser
event instead of a state that was registering all operations.

This is a cleaner pattern as it is a synchronous event code path instead
of relying on a useEffect to watch state change, which is not ideal.

We introduce this change first to then rely on this new pattern to
dispatch SSE events in a following PR.
2026-01-15 16:38:21 +00:00
Thomas Trompette 48b0e2f11d Move run creation before opening side panel (#16956)
Fixes https://github.com/twentyhq/twenty/issues/16837

Current flow:
- create workflow run optimistically 
- open side panel => triggers query + event stream
- mutation that actually creates the run using the optimistic id

New flow
- create workflow run optimistically 
- mutation that actually creates the run using the optimistic id
- open side panel => triggers query + event stream
2026-01-06 14:06:07 +00:00
Félix Malfait b27a97f2c5 feat: enforce @/ alias for imports and fix all relative parent imports (#16787)
## Summary
This PR enforces the use of `@/` alias for imports instead of relative
parent imports (`../`).

## Changes

### ESLint Configuration
- Added `no-restricted-imports` pattern in `eslint.config.react.mjs` to
block `../*` imports with the message "Relative parent imports are not
allowed. Use @/ alias instead."
- Removed the non-working `import/no-relative-parent-imports` rule
(doesn't work properly in ESLint flat config)

### VS Code Settings
- Added `javascript.preferences.importModuleSpecifier: non-relative` to
`.vscode/settings.json` (TypeScript setting was already there)

### Code Fixes
- Fixed **941 relative parent imports** across **706 files** in
`packages/twenty-front`
- All `../` imports converted to use `@/` alias

## Why
- Consistent import style across the codebase
- Easier to move files without breaking imports
- Better IDE support for auto-imports
- Clearer understanding of where imports come from
2025-12-23 22:57:51 +01:00
Charles Bochet 8d1329953c Refactor upsertRecordsInStore to accept an object with partialRecords (#16707)
Fixes https://github.com/twentyhq/twenty/issues/16624

Original issue:
- while persisting a field (calling useUpdateOne), the response from the
backend is missing the taskTargets many to many (same for note). As we
optimistically update the cache, we lose the "Relations" in the UI
- I'm changing the behavior of useUpsertInRecordStore to accept
recordGqlFields to only update the fields we want in the record store
(this way, we are not losing the targets information in our case)
2025-12-19 15:22:54 +01:00
Thomas Trompette 95e0793f81 Compute output schema on frontend (#16530)
Fixes https://github.com/twentyhq/core-team-issues/issues/1382

Current issue : all step output schemas are computed and stored on
backend side. Which means that, when the database schema is updated -
like a field creation - steps needs to be deleted an recreated. Which is
invisible to users.

Solution : schema generation is moved on frontend side

1. Coming on the page the first time, the schema is populated for all
steps except a few ones that are handled differently (Code, Webhook,
http node, Agent)

2. A separated state allow to determine if a step needs a recomputation.

3. The user only needs a refresh to see the whole schema re-computed

Follow-up:
- check if remaining backend steps could be moved to runtime
computation. But Code will still require storage.
- Clean backend service that is not used anymore
2025-12-15 16:20:35 +01:00
Lucas Bordeau ec8773437e Improved table flash on reload (#16419)
This PR fixes https://github.com/twentyhq/core-team-issues/issues/1732

There is still some room for improvement but the main goal is reached :
removing the flash effect each time the table virtualization has to
recompute due to an update after initial loading.

# QA

## Create 


https://github.com/user-attachments/assets/1b4fc307-42ce-4ba6-b557-68ac7fcad40f

## Update with sort


https://github.com/user-attachments/assets/e0700f44-8926-4395-8ab8-b32773f21de8

## Update with filter


https://github.com/user-attachments/assets/d325f85a-1a7b-4366-aac3-250331be7575

## Soft delete


https://github.com/user-attachments/assets/2c980183-c637-4aa7-a0ca-244e61396b20

## Restore and destroy


https://github.com/user-attachments/assets/01af9ec7-b442-4686-a1d7-ea1fc543fe62

## Drag & drop


https://github.com/user-attachments/assets/bba76bdb-d4ec-433d-b44e-37fdb9952b06

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-12-11 15:14:20 +01:00
Thomas Trompette 1c14567bf1 Workflow statuses update on record table - use cache instead of web sockets (#16391)
Updated the 3 hooks - activate, deactivate and discard draft
(deleteVersion) - so those updates the cache.
Removed the update listeners.

Also wrapped a few actions to unsure workflowId is not undefined when
received by useWorkflowWithCurrentVersion hook. Otherwise, useFindRecord
with by performed with skip true, disconnecting tmp from the cache and
making it miss a statuses update.



https://github.com/user-attachments/assets/5fc355e8-cf18-4881-855b-744eb253b79e
2025-12-08 17:17:13 +01:00
Harshit Singh 065923d724 feat: Add Workflow duplicate step (#15622)
## Description

- This PR address issue
https://github.com/twentyhq/core-team-issues/issues/1800
- Added workflow duplicate option in workflow sidepanel
- Introduced DuplicateWorkflow.ts mutuation
- Updated graphql generated metadata with new duplicate type




## Visual Appearanc

<img width="1792" height="1031" alt="Screenshot 2025-11-07 at 4 46
22 PM"
src="https://github.com/user-attachments/assets/d75f99df-ae18-4b41-a5f4-21c50010cdbc"
/>


https://github.com/user-attachments/assets/3dbf73cf-f4dd-484c-94a3-29577cfbac25

---------

Co-authored-by: Devessier <baptiste@devessier.fr>
2025-11-12 10:53:25 +01:00
Thomas Trompette d1330a4a8c Improve workflow performances (#15528)
- on draft creation, do not fetch the full version. Avoid the fetch of
steps and trigger
- on activation, we were performing 8 queries/mutations synchronously +
2 additional for automated triggers. I refacto the call it it gets
reduced to 4 queries/mutations + 2 additional for automated triggers
2025-11-03 17:23:01 +01:00
Thomas Trompette 4effa5351c Allow to stop running workflow (#15270)
https://github.com/user-attachments/assets/599154e4-8743-471b-b05a-721b635bcf4e

On stoppage:
- if no running steps, mark pending as failed and end the workflow
- if running steps, set as stopping and exit. Going to the next step, as
the workflow is not running anymore, it will naturally stop
2025-10-23 13:01:15 +00:00
Thomas Trompette 91b54e7e68 Remove iterator feature flag (#15165)
As title
2025-10-17 09:19:39 +00:00
twill-hq[bot] ce045db15a Analyze Context of Issue #1586 Using GitHub MCP (#15058)
# Implement "Tidy Up" Action for Workflow Diagram

**Task Link:** https://twill.ai/twentyhq/ENG/tasks/6

## Summary

This PR adds a "Tidy Up" action to the workflow diagram interface,
allowing users to automatically organize and clean up the layout of
workflow nodes and connections.

## Changes Made

- **Added new workflow action**: Created
`TidyUpWorkflowSingleRecordAction` component to provide a UI action for
tidying up workflow diagrams
- **Refactored tidy-up logic**: Extracted core tidy-up functionality
into a reusable `useTidyUp` hook, separating layout logic from workflow
version persistence
- **Updated action menu configuration**: Integrated the new tidy-up
action into `WorkflowActionsConfig` with proper permissions and keyboard
shortcuts
- **Enhanced right-click menu**: Updated
`WorkflowDiagramRightClickCommandMenu` to use the refactored tidy-up
hook
- **Improved hook architecture**: Simplified `useTidyUpWorkflowVersion`
to delegate layout calculations to the new `useTidyUp` hook

## Key Features

- Users can now trigger workflow diagram tidy-up from the action menu
- Consistent tidy-up behavior across both right-click menu and action
menu interfaces
- Better separation of concerns between layout calculation and data
persistence

<details>
<summary>📸 Screenshots</summary>

Playwright test screenshots captured during development:

### command-menu-with-tidy-up-workflow.png


![command-menu-with-tidy-up-workflow.png](https://storage.googleapis.com/twill-screenshots-twill-469020/screenshots/1760356112795-command-menu-with-tidy-up-workflow.png)

### tidy-up-workflow-command-menu.png


![tidy-up-workflow-command-menu.png](https://storage.googleapis.com/twill-screenshots-twill-469020/screenshots/1760356113168-tidy-up-workflow-command-menu.png)

### tidy-up-workflow-error-toast.png


![tidy-up-workflow-error-toast.png](https://storage.googleapis.com/twill-screenshots-twill-469020/screenshots/1760356113407-tidy-up-workflow-error-toast.png)

</details>

---------

Co-authored-by: Twill <agent@twill.ai>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Thomas Trompette <thomas.trompette@sfr.fr>
2025-10-16 16:59:48 +00:00
Thomas Trompette 7b84db4708 Only fetch steps and trigger for the current workflow version (#15003)
Avoid fetching full steps and trigger for versions that are not the
current version. Because those won't be used anyway. Better for
performances.

Only difficulty was for the `createDraft` mutation. I needed to return
the full created version so I can store it in cache and use it as new
`currentVersion`. Otherwise the current version is considered as
incomplete for a short time, since workflow is fetched separately from
the current version.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-10-12 18:06:12 +02:00
Charles Bochet 37473175a0 Reduce relation loading overload on FE graphql queries (#14991)
## Problem

With Twenty usage growing, we are facing challenges on server side to
respond to the demand. The current bottleneck we are facing is server
CPU.
While investigating CPU performances, we figured out that loading all
relation fields was the biggest issue.

### About graphql query response size

**Example:** on `People Index` Table page:
we query: `person.company` and we query **all non relation fields** on
company relation field.
`{ person { company { id, name, domainName, employees, address ... } }`
 
 
However we only need **company.id, company.name and company.domainName**
to be able to render the Company Chip in the company column (RELATION)
in the table.
`{ person { company { id, name, domainName } }`

We initially assumed that the querying all non relation fields was not
an issue because it was not adding additional load on database (which is
also partially true: if a field is containing a huge json this will add
load on postgres as data needs to be transfered)
This assumption is wrong on CPU side:
- when loading one to many relations (company.people), this quickly ads
up and we end up with response of 20kB quite quickly, even without
adding any custom field on person.
- even when loading a many to one relation, if the user is storing big
data in a given field (note.body for instance, or workflowRun.state),
this starts also being an issue.
- Worst case scenario are starting to happen: on a production workspace
with 20 active workflows. Loading workflow table while displaying
workflow runs commands (20 workflows x 60 workflowRuns x
workflowRun.state which is a big JSON) result in a response of... 125MB.

### Why is it an issue?

When yoga (graphql engine) parses a response to send it back in server
response, it's using `JSON.parse `(or stringify depending on the case).
Parsing data is CPU instensive (as you need to validate, transform) and
this is more or less `O(n)`.
This means returning 125MB is very intense on the CPU and will likely
use 100% of the CPU for ~1sec in our production servers.

NodeJs (nodeV8) is single threaded. It's able to process multiple
requests in parallel but to do that, it will cut them in "microTasks"
and process each microTasks (that can belong to different requests) one
after the other. Exactly like a single threaded CPU would allocate some
time to a process and then to the next one, etc.

This means that this big response request will actually block the other
request.
As a result, all requests are slow and our infrastructure is multi
tenant so some customers are impacting others.

This is even a bigger issue when our health checks start to fail and
containers are starting being considered as unhealthy and killed by the
orchestrator


## How to fix this

1. On Front end side, we should only query what we need. In this PR, I'm
forcing the relations to only query: `id`, `labelIdentifier`,
`imageIdentifier`
2. (later) as we are API first, we also need to do something to protect
the servers from this side too. To do that, Graphql APIs can associate a
cost to each graphql field (maybe 1 for a test fixed, 2 for a JSON
field, 10 for relations, etc...) and we can throttle based on that.

## Changes in this PR

It's mainly about refactor the tooling to generate `RecordGqlFields`
(`generateDepthOneGqlFields`). this tooling is used to generate the list
of fields we want to query. Most of the time we want to query a record
with one level of nesting.
1) Refactor to remove duplicate code => `generateDepthOne` become
`generateDepth` and can handle both `depth = 0 | 1`

2) Introduce `generateDepthRecordGqlFieldsFromFields.ts` which is the
base and can give you a list of gqlFields based on FieldMetadataItems

3) Introduce `generateDepthRecordGqlFieldsFromObject` which is a
shortcut for an object
4) Introduce `generateDepthRecordGqlFieldsFromRecords` which is the
intersection between `generateDepthRecordGqlFieldsFromObject` and a
given record (useful for cache tooling)
5) Replace all usages + introduce a hook
`useGenerateDepthRecordGqlFieldsFromObject` to ease usage
2025-10-10 14:31:12 +02:00
Charles Bochet 0fd6085b53 Prevent querying too much on worklow index page (#14916)
## Performance short term fix

Done in this PR:
workflowVersions and workflowRuns are heavy object. I'm preventing
loading their data when they are loaded as relations. This will reduce
the work on backend

## Long term fix

Todo:
We should make sure workflow data is ligher
2025-10-06 23:53:58 +02:00
Charles Bochet c5cdf45f85 Improve performances (#14869)
## Improvements

- Add logs to all gql operations and rest calls to help debug CPU issues
on the backend. These are temporary and should be removed
- Remove nested relations from workflowVersions load (used to add manual
triggers in the side bar). On some workspaces this call result in a
response of 4MB which is heavy on CPU
- Investigated Redis Usage ==> made a few improvements, we are should
still migrate to the new cache service once available
- investigated db calls in messaging / calendar fetch list + workflow
enqueue run cron jobs. Everything seems to be properly batched
2025-10-03 17:22:03 +02:00
Thomas Trompette b7cdf9183a Search record action - allow to search more than 1 record (#14769)
https://github.com/user-attachments/assets/426da6af-6c6b-4d49-9034-0aa041cb331f
2025-09-30 12:07:25 +02:00
Thomas Trompette 4329501743 Workflow run over a list of records + backfill availability on manual triggers (#14761)
- Allow to run workflow on a list of records
- Use new availability in manual trigger when iterator feature flag is
enabled
- Build a command to backfill availability in workflow trigger



https://github.com/user-attachments/assets/d685c01f-4059-4647-92a1-f5f529b560cf
2025-09-29 13:45:51 +00:00
Abdul Rahman 3cada58908 Migrate from Zod v3 to v4 (#14639)
Closes [#1526](https://github.com/twentyhq/core-team-issues/issues/1526)

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-09-24 18:29:05 +02:00
Abdul Rahman 216d72b5d7 AI SDK v5 migration (#14549)
Co-authored-by: Félix Malfait <felix@twenty.com>
2025-09-22 22:13:43 +02:00
Thomas Trompette 3bfb5bbd86 Add duplicate step action (#14392)
https://github.com/user-attachments/assets/ac89440c-6f84-4d2a-b88a-da7ddfb181eb
2025-09-10 15:25:40 +00:00
Thomas Trompette 2b59198a1b Migrate output schema to V2 (#14311)
Previous refacto was creating output schema V2 which has more specific
schemas based on the step type. Before we were using one common schema,
which was too complex when searching for variable informations.

This PR migrate the deprecated schemas and remove the old code:
- mark previous `BaseOutputSchema` as deprecated
- remove other previous schemas
- use V2 everywhere
- icon should not be stored in schema. Instead it should be generated
based on the fieldmetadata or the item type
2025-09-09 18:37:00 +02:00
Thomas Trompette 7094f0ee08 Remove is branch enabled feature flag (#14357)
As title
2025-09-09 10:07:20 +02:00
Abdul Rahman 9098df7ddf Add preconfigured Workflow creation agent (#13855)
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
2025-08-27 14:20:38 +02:00
Thomas Trompette b9b5f9a347 Remove workflow filters from lab (#14017)
As title
2025-08-21 14:30:36 +02:00
Thomas Trompette 50c4c50169 Remove workflow context + clean action config (#13919)
- Remove create related version and runs from workflow action config
- Remove WorkflowStepContextProvider. Use states instead
2025-08-14 10:17:07 +00:00