Commit Graph

84 Commits

Author SHA1 Message Date
Félix Malfait c938fbf4d6 feat(twenty-front): relation traversal in filter dropdown (stacked) (#20533)
**Stacked on #20527** 




https://github.com/user-attachments/assets/48995655-401a-4c35-8094-e88da8408bdd





## Summary

Surfaces the one-hop relation traversal added in #20527 through the
existing **composite sub-field dropdown pattern**. Clicking a
MANY_TO_ONE relation field in the "+ Filter" picker now opens the same
second-level dropdown that composite fields (FULL_NAME, ADDRESS,
CURRENCY, etc.) already use — populated with the target object's
filterable fields. Picking one (e.g. `Company → Name`) builds a filter
that serializes to the nested GraphQL filter the backend now accepts: `{
company: { name: { ilike: "%X%" } } }`.

No new components. The whole feature reuses
`AdvancedFilterSubFieldSelectMenu` + the existing
`subFieldNameUsedInDropdownComponentState` + the existing `MenuItem
hasSubMenu` indicator. Only the conditions that gate the sub-menu (and
the sub-menu's content for relations) were broadened.

## What landed

| File | Change |
|---|---|
| `ObjectFilterDropdownFilterSelectMenuItem` | Sub-menu chevron now
shows on MANY_TO_ONE relations (`isManyToOneRelationField` util). |
| `AdvancedFilterFieldSelectMenu` | Relation clicks open the sub-menu
alongside composite clicks. |
| `AdvancedFilterSubFieldSelectMenu` | New branch: when the sub-menu
type is `'RELATION'`, render the target object's filterable fields via
`useFilterableFieldMetadataItems(targetObjectMetadataId)`. Composite
logic untouched. |
| `objectFilterDropdownSubMenuFieldType` state | Widened to accept a
`'RELATION'` sentinel. Role-permissions sub-field menu narrows it back
out (it doesn't traverse relations). |
| `useSelectFieldUsedInAdvancedFilterDropdown` | New optional
`targetFieldMetadataItem` arg. When present, the stored RecordFilter's
`type` is the target field's type so the operand picker and value input
render the target's operands (`'TEXT'` operators when filtering
`company.name`, etc.). |
| `turnRecordFilterIntoGqlOperationFilter` (shared) | When the filter
targets a `RELATION` field with a `subFieldName`, synthesize a
field-metadata for the target, recurse to build the inner filter, then
wrap it under the relation field's name → `{ relationName: {
targetFieldName: { ...operator } } }`. |

`RecordFilter.subFieldName` stays narrowly typed as
`CompositeFieldSubFieldName` so the wide downstream consumers
(`shouldShowFilterTextInput`, composite handlers in the serializer,
etc.) don't change. The relation target field's name is stored through a
narrowly-scoped cast at the dropdown's storage point — the serializer
checks `filter.type === 'RELATION'` before interpreting it as a target
field name, so the cast can't be mis-read by composite-only code paths.

## Test plan

- [ ] Open a table view on People, click "+ Filter", click "Company" →
sub-menu opens with Company's filterable fields
- [ ] Pick "Name" → operand picker shows TEXT operators (Contains,
Equals, …)
- [ ] Type "Airbnb" → filter applies, table shows people whose company
name contains "Airbnb"
- [ ] Verify network tab: the GraphQL filter variable is `{ company: {
name: { ilike: "%Airbnb%" } } }`
- [ ] Same flow with a composite target field (e.g. `Company →
annualRecurringRevenue → amountMicros`) — should work end-to-end
(backend supports composite-within-relation; #20527 has an integration
test covering this)
- [ ] Composite fields (FULL_NAME, ADDRESS) still open their normal
sub-menu and filter correctly — no regression
- [ ] Role-permissions field-select sub-field menu is unaffected (it
bails out early on the RELATION sentinel)

## Out of scope

- ONE_TO_MANY traversal (no backend support yet)
- Aggregates (`people.count > 5`)
- Persisting relation-traversal filters into a saved view (ViewFilter
has no `relationPath` column yet; that's a separate slice)
- REST API DSL changes
- AI Tools

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-15 16:42:47 +02:00
Abdul Rahman 93df64b9b0 Show logic function label instead of technical name in workflow UI (#20470)
### Before
<img width="1304" height="812" alt="Screenshot 2026-05-12 at 7 02 32 AM"
src="https://github.com/user-attachments/assets/94ca4b1d-69c0-4059-8c45-dd8eae8e2a29"
/>



### After
<img width="1296" height="782" alt="Screenshot 2026-05-12 at 6 53 26 AM"
src="https://github.com/user-attachments/assets/47f2e291-73df-4471-9174-bd5aca23e228"
/>
2026-05-12 05:46:56 +00:00
Priyanshu Bartwal c227b0d06a Fix(UI): Side panel having two scrollbars (#20456)
Fixes: #20417

Screenshot:
<img width="411" height="945" alt="image"
src="https://github.com/user-attachments/assets/925b3ac9-49f2-4fcc-920e-3b9dc34ac466"
/>
2026-05-11 18:42:55 +00:00
nitin bbd9720ab3 [Dashboards] [Warning] Remove gauge chart support and delete existing widgets (#20172)
## Summary

Removes gauge chart from the chart-type picker and deletes existing
gauge widgets via a workspace migration. The gauge was rendering a
hardcoded `0.7 / "Progress"` stub regardless of configuration -- never
wired to real data.

The contract stays in place. We keep
`WidgetConfigurationType.GAUGE_CHART`, the DTO, the GraphQL union
member, and the gauge folder -- so stored gauge JSON still resolves
through the schema. The render path falls through to `default: return
null`, so any un-migrated gauge widget renders as an empty cell, not a
crash.

This PR just removes existing gauge widgets if there are any (via
`upgrade:2-3:delete-gauge-widgets`). The deliberate cleanup -- deleting
the type definitions, the gauge folder, the DTO -- comes in a follow-up
PR after the migration has run.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-05-05 21:36:45 +00:00
Félix Malfait 53fdac1417 feat(apps): split AI tool and workflow action triggers in LogicFunction manifest (#20208)
## Summary

Replaces the bolted-on `isTool` + `toolInputSchema` fields on
`LogicFunctionManifest` with two distinct, opt-in triggers that align
with the existing `cron` / `databaseEvent` / `httpRoute` trigger
pattern:

- **`toolTriggerSettings`** — exposes the function as an AI tool (chat /
MCP / function calling). Uses standard JSON Schema (the format LLMs
natively understand).
- **`workflowActionTriggerSettings`** — exposes the function as a step
in the visual workflow builder. Uses Twenty's rich `InputSchema` so the
builder can render proper `FieldMetadataType`-aware editors, variable
pickers, labels, and an optional `outputSchema`.

A function can opt into none, one, or both. Each surface gets the schema
format appropriate for it.

### Why

`isTool: true` previously exposed the function as both an AI tool AND a
workflow node, with the same JSON Schema feeding both — but the workflow
builder really wants Twenty's `InputSchema` (with `CURRENCY`,
`RELATION`, `EMAILS`, etc.) and the AI surface really wants standard
JSON Schema. Today the workflow builder hacks around this by treating
JSON Schema as `InputSchema`, which silently breaks for any
non-primitive field type. Splitting the triggers fixes that and lets
each surface evolve independently.

### Migration

- **Fast** instance command adds the two new nullable columns.
- **Slow** instance command backfills `toolTriggerSettings` +
`workflowActionTriggerSettings` from `isTool=true` rows (preserving
today's both-surfaces behaviour) then drops the legacy columns.

### Stacked

Stacked on top of #20181. Merge that first, then this.

## Test plan

- [ ] CI green (oxlint, typecheck, jest, vitest)
- [ ] Run `--include-slow` upgrade against a workspace with existing
`isTool=true` logic functions; verify both new columns populated and old
columns dropped
- [ ] Verify AI chat sees migrated tool functions (Linear create-issue,
Exa search) and can call them with the JSON Schema
- [ ] Add an AI-tool function from the Settings UI (toggles
`toolTriggerSettings`) and verify it shows up in chat
- [ ] Add a workflow-action function from the Settings UI (toggles
`workflowActionTriggerSettings`) and verify it appears in the workflow
node picker
- [ ] In the workflow builder, edit a `LOGIC_FUNCTION` step and verify
input fields render (no more JSON-Schema-as-InputSchema hack)
- [ ] Try defining a function with no triggers in the SDK and verify
`defineLogicFunction` rejects it

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: martmull <martmull@hotmail.fr>
2026-05-05 14:56:09 +02:00
Paul Rastoin 820f97f53d [Headless Front component] Support multiple selected record (#20268)
# Introduction

Support multiple selected record ids for headless front components

### Changes

**Added:**
- `recordIds: string[]` field to `FrontComponentExecutionContext`
- `useRecordIds()` hook to get all selected record IDs

**Deprecated:**
- `recordId` field - use `recordIds` instead
- `useRecordId()` hook - use `useRecordIds()` instead

Backward compatibility is preserved
2026-05-05 14:53:22 +02:00
Abdul Rahman 19ee9444ed add UpsertViewWidget resolver (#20053) 2026-04-29 09:41:02 +00:00
Abdul Rahman a710c105cf Fix orphan views by deferring record table widget view creation to dashboard save (#20006) 2026-04-28 10:06:26 +00:00
nitin 097432d3a2 [Command Menu] Refactor layout customization conditional availability [Warning] (#19974)
closes
https://discord.com/channels/1130383047699738754/1494312529286004837
2026-04-24 10:18:02 +00:00
Abdul Rahman e3d7d0199d Fix side panel hotkeys breaking when opening records from table (#19849)
## Summary

- Fix side panel hotkeys (Ctrl+K, Escape, etc.) breaking when opening
records from the record index table
- Ensure `side-panel-focus` is always restored in the focus stack when
navigating within an already-open side panel
- Remove stale `globalHotkeysConfig` on `record-index` focus item that
persisted after the side panel closed

## Problem

When clicking records in the table to open them in the side panel,
`useLeaveTableFocus` called `resetFocusStackToRecordIndex` which wiped
the entire focus stack, including the `side-panel-focus` entry. Since
`openSidePanel` early-returned when the panel was already open,
`side-panel-focus` was never restored. Additionally,
`resetFocusStackToRecordIndex` set `enableGlobalHotkeysWithModifiers:
false` on the remaining `record-index` item when the side panel was
open, and this stale config persisted after the panel closed,
permanently blocking all hotkeys.

## Fix

- **`useNavigateSidePanel.ts`**: Move `pushFocusItemToFocusStack` before
the `isSidePanelOpened` early-return so the side panel's focus entry is
always present in the stack
- **`useResetFocusStackToRecordIndex.ts`**: Always set
`enableGlobalHotkeysWithModifiers: true` on the `record-index` item.
Hotkey scoping when the side panel is open is handled by
`side-panel-focus` sitting on top of the focus stack



https://github.com/user-attachments/assets/ad25befb-338d-4166-9580-18d4e92d6f9b
2026-04-21 07:44:00 +00:00
Félix Malfait 30b8663a74 chore: remove IS_AI_ENABLED feature flag (#19916)
## Summary
- AI is now GA, so the public/lab `IS_AI_ENABLED` flag is removed from
`FeatureFlagKey`, the public flag catalog, and the dev seeder.
- Drops every backend `@RequireFeatureFlag(IS_AI_ENABLED)` guard (agent,
agent chat, chat subscription, role-to-agent assignment, workflow AI
step creation) and the now-unused `FeatureFlagModule`/`FeatureFlagGuard`
wiring in the AI and workflow modules.
- Removes frontend gating from settings nav, role
permissions/assignment/applicability, command menu hotkeys, side panel,
mobile/drawer nav, and the agent chat provider so AI UI is always on.
Tests and generated GraphQL/SDK schemas updated accordingly.

## Test plan
- [x] `npx nx typecheck twenty-shared`
- [x] `npx nx typecheck twenty-server`
- [x] `npx nx typecheck twenty-front`
- [x] `npx nx lint:diff-with-main twenty-server`
- [x] `npx nx lint:diff-with-main twenty-front`
- [x] `npx jest --config=packages/twenty-server/jest.config.mjs
feature-flag`
- [x] `npx jest --config=packages/twenty-server/jest.config.mjs
workspace-entity-manager`
- [ ] Manual smoke test: AI features still accessible without any flag
row in `featureFlag`

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 09:49:46 +02:00
Félix Malfait 6117a1d6c0 refactor: standardize AI acronym to Ai (PascalCase) across internal identifiers (#19837)
## Summary

The "AI" acronym was rendered inconsistently across the codebase. The
backend AI module had settled on PascalCase `Ai` (`AiAgentModule`,
`AiBillingService`, `AiChatModule`, `AiModelRegistryService`, etc.),
while frontend components, several DTOs, a few types, and shared
identifiers still used all-caps `AI` (`AIChatTab`,
`AISystemPromptPreviewDTO`, `SettingsPath.AIPrompts`, ...). CLAUDE.md
specifies PascalCase for classes; this PR normalizes everything internal
to `Ai`.

**This is a pure internal rename.** The GraphQL schema is untouched —
`@ObjectType` decorator string arguments, resolver method names (which
become Query/Mutation field names), gql template contents, and the
`generated-metadata/graphql.ts` file are preserved verbatim. The only
visible change is TypeScript identifiers and file names.

## Also folded in (adjacent cleanups)

- **`AgentModelConfigService` → `AiModelConfigService`**. Lives in
`ai-models/` and is used by multiple AI code paths, not just the Agent
entity. The "Agent" prefix was misleading.
- **`generate-text-input.dto.ts` → `generate-text.input.ts`**. The
`ai-agent/dtos/` folder already uses `<entity>.input.ts` convention for
Input classes (`create-agent.input.ts` etc.); the old path mixed
`.dto.ts` file extension with a class that has no DTO suffix. File
rename only; class stays `GenerateTextInput`.
- **Removed stale TODO** in `ai-model-config.type.ts` that asked for the
`AiModelConfig` rename that this PR performs.

## Rename methodology

Bulk rename via perl with anchored regex
`(?<!['"])(?<![A-Z.])AI([A-Z])(?=[a-z])/Ai$1/g`:

- **Lookbehind for non-uppercase** skips adjacent acronyms (`MOSAIC`,
`OIDCSSO`) and leaves `AIRBNB_ID` alone.
- **Lookbehind for non-quote** protects most string literals.
- **Lookahead for lowercase** restricts matches to PascalCase
identifiers (`AIChatTab`), leaving SCREAMING_SNAKE constants untouched.

Strict file-scope exclusions: `generated-metadata/**`, `generated/**`,
`locales/**`, `migrations/**`, `illustrations/**`, `halftone/**`, and
the two gql template files (`queries/getAISystemPromptPreview.ts`,
`mutations/uploadAIChatFile.ts`).

Post-rename reverts for identifiers where the regex was too eager:
- Backend resolver method names kept: `getAISystemPromptPreview`,
`uploadAIChatFile` (they are GraphQL field names).
- `@ObjectType('AdminAIModels')` / `('AISystemPromptPreview')` /
`('AISystemPromptSection')` kept as-is.
- Backend classes `ClientAIModelConfig` / `AdminAIModelConfig` kept
as-is (they use `@ObjectType()` with no argument, so the class name IS
the schema name).
- External-library symbols restored: `OpenAIProvider`,
`createOpenAICompatible`, `vercelAIIntegration`.

File renames use a two-step rename to work on macOS case-insensitive
filesystems: `git mv X.tsx X.tsx.tmp && git mv X.tsx.tmp renamed.tsx`.

## Diff audit

- 0 changes to migrations
- 0 changes to locale `.po` / `.ts` files
- 0 changes to `generated-metadata/graphql.ts`
- 0 changes to website illustration files (base64 blobs preserved)
- 0 renames inside user-facing translation strings (`t\`…\``,
`msg\`…\``, `<Trans>…</Trans>`)

## Test plan

- [x] `npx nx typecheck twenty-server` — PASS
- [x] `npx nx typecheck twenty-front` — PASS
- [x] `npx jest ai-model admin agent-role` — 79/79 PASS
- [x] `npx oxlint --type-aware` on 118 changed files — 0 errors
- [x] `npx prettier --check` on 118 changed files — clean
- [ ] CI
2026-04-19 13:29:35 +02:00
neo773 be9616db60 chore: remove draft email feature flag (#19842) 2026-04-18 21:12:01 +02:00
Weiko 13b32a22b6 Add page layout tab icon picker (#19818)
Adds the ability to change the icon of a record page layout tab from the
side panel in tab edit mode, and sets a default icon for newly-created
record page tabs (no default for dashboards).

<img width="916" height="312" alt="Screenshot 2026-04-17 at 19 55 51"
src="https://github.com/user-attachments/assets/d9f57e89-d40d-483e-b508-5d7318df1ef5"
/>
2026-04-17 18:19:46 +00:00
Weiko df9c4e26b5 Disable reset to default when custom tab or widget (#19814)
## Context
"Reset to default" action is rejected by the backend for custom entities
because there is no "default" concept for them

## Implementation
Grey out the Reset to default action on record page-layout tabs and
widgets when the entity either has no applicationId yet (unsaved draft —
previously slipped through the existing check), or belongs to the
workspace custom application.

<img width="926" height="370" alt="Screenshot 2026-04-17 at 18 50 31"
src="https://github.com/user-attachments/assets/c7c163f4-17a6-4b69-a66d-90f9085d27a2"
/>
2026-04-17 17:30:57 +00:00
Lazare Rossillon d54092b0e2 fix: add missing LayoutRenderingProvider in SettingsApplicationCustomTab (#19679)
## Summary

- `SettingsApplicationCustomTab` renders `FrontComponentRenderer` which
calls `useFrontComponentExecutionContext` →
`useLayoutRenderingContext()`, but the settings page never provided a
`LayoutRenderingProvider`
- Every other render site (side panel, command menu, record pages) wraps
`FrontComponentRenderer` with this provider — it was just missed here
- Opening the "Custom" tab in Settings → Applications crashes with:
`LayoutRenderingContext Context not found`
- Fix: wrap with `LayoutRenderingProvider` using `DASHBOARD` layout type
and no target record, matching the pattern used in
`SidePanelFrontComponentPage`

## Test plan

- [ ] Open Settings → Applications → any app with a custom settings tab
- [ ] Click the "Custom" tab — should render the front component without
crashing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-04-16 10:01:28 +00:00
martmull 381f3ba7d9 Fix app design 1/2 (#19735)
comply with
https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=96977-349627&m=dev

## After
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 40
37"
src="https://github.com/user-attachments/assets/6d80191a-79a9-4f0f-aa4f-0e447fff4f6d"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 40
22"
src="https://github.com/user-attachments/assets/4f763272-027e-4246-b455-7d46babf7d8c"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 39
11"
src="https://github.com/user-attachments/assets/b9b35e18-8068-447e-821d-5ec28bb5bd16"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 39
05"
src="https://github.com/user-attachments/assets/57d9318a-902f-4fd7-a2a3-5795ebe0b9dc"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 39
02"
src="https://github.com/user-attachments/assets/78a33fa8-6bdd-484e-a82d-bd0f7592a623"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 38
58"
src="https://github.com/user-attachments/assets/f7987aed-c6e1-4032-a611-86817655137d"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 38
55"
src="https://github.com/user-attachments/assets/d1c451ab-1d2d-41e4-a059-cf4303ecabe7"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 38
48"
src="https://github.com/user-attachments/assets/593cae36-2320-443f-a955-93b211a6ee3f"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 37
40"
src="https://github.com/user-attachments/assets/c9f602b1-8de3-4e82-a3a6-344594a0c153"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 37
34"
src="https://github.com/user-attachments/assets/b54ddddf-5dda-46c8-ace3-cffe6015825a"
/>

## before

<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42
18"
src="https://github.com/user-attachments/assets/c0976a0a-0124-48ec-8e7c-78627cea7063"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42
16"
src="https://github.com/user-attachments/assets/d2db926c-4040-411d-9091-8b60e7c519e6"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42
13"
src="https://github.com/user-attachments/assets/2d69f2ff-f26e-4249-91a3-2cf3d261e840"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42
07"
src="https://github.com/user-attachments/assets/1028aabc-77ac-4c51-a8c3-9a194faba87f"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 42
01"
src="https://github.com/user-attachments/assets/1caa9f5e-3eaa-433c-9d3b-e0f094f16e8e"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41
56"
src="https://github.com/user-attachments/assets/f42b6976-3a8f-4591-9283-bda79bdb424b"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41
53"
src="https://github.com/user-attachments/assets/93d00df8-0091-4dfa-9ac0-f6f376be5962"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41
43"
src="https://github.com/user-attachments/assets/9deae7e5-39c1-4518-a463-6d79bc5bf132"
/>
<img width="1512" height="909" alt="Capture d’écran 2026-04-16 à 09 41
37"
src="https://github.com/user-attachments/assets/3e21b521-c47d-482c-ad41-66abfe973772"
/>
2026-04-16 09:47:44 +00:00
Raphaël Bosi 3b85747d3f Go back to the original command K button (#19727)
## Before


https://github.com/user-attachments/assets/868e5293-8843-44c8-8011-b7130e97fa95


## After


https://github.com/user-attachments/assets/89cf46cc-1fb1-4e78-bfef-53e1d04e8ebf
2026-04-15 15:53:26 +00:00
Baptiste Devessier 8cb803cedf Various bug fixes Record page layouts (#19719)
Fixes:

- Can't add multiple widgets in a row
- Ensure newly created is always focused
2026-04-15 15:12:57 +00:00
Raphaël Bosi a88d1f4442 Introduce standalone page (#19675)
Add support for standalone pages: a new `PageLayout` type
(`STANDALONE_PAGE`) that can be rendered independently at
`/page/:pageLayoutId`, not tied to any record or object context.

- New `STANDALONE_PAGE` page layout type
- New `PAGE_LAYOUT` navigation menu item type: adds a `pageLayoutId`
foreign key to `NavigationMenuItemEntity`, allowing sidebar items to
link directly to standalone pages
- New `GLOBAL_OBJECT_CONTEXT` command menu availability type: separates
object-context-dependent commands (Create Record, Import, Export, See
Deleted, Create View, Hide Deleted) from truly global ones, so
standalone pages only show relevant commands
- Frontend routing & rendering: adds a `/page/:pageLayoutId` route with
its own page component, header, and command menu
- Widget rendering refactor
- Instance commands: two fast 1.22 migrations: `pageLayoutId` column +
`STANDALONE_PAGE` enum, and `GLOBAL_OBJECT_CONTEXT` availability type
enum
- Workspace command: backfills existing command menu items from `GLOBAL`
to `GLOBAL_OBJECT_CONTEXT` where appropriate
- Dev seeds: adds a sample "Star History" standalone page with an iframe
widget for local development
2026-04-14 15:52:45 +00:00
Weiko 47bdcb11d8 Move is active to fe (#19649)
## Context
Moving isActive filtering to the frontend for page layout tabs and
widgets, hiding inactive entities from the UI while keeping them in
state for future reactivation

Next we will implement deactivated standard tab re-activation during tab
creation (cc @Devessier)
<img width="234" height="303" alt="📋 Menu (Slots)"
src="https://github.com/user-attachments/assets/17a25ac6-55e2-4778-b7f0-e7554ed69704"
/>
2026-04-14 10:18:09 +00:00
Félix Malfait 69d228d8a1 Deprecate IS_RECORD_TABLE_WIDGET_ENABLED feature flag (#19662)
## Summary
- Removes the `IS_RECORD_TABLE_WIDGET_ENABLED` feature flag, making the
record table widget unconditionally available in dashboard widget type
selection
- The flag was already seeded as `true` for all new workspaces and only
gated UI visibility in one component
(`SidePanelPageLayoutDashboardWidgetTypeSelect`)
- Cleans up the flag from `FeatureFlagKey` enum, dev seeder, and test
mocks

## Analysis
The flag only controlled whether the "View" (Record Table) widget option
appeared in the dashboard widget type selector. The entire record table
widget infrastructure (rendering, creation hooks, GraphQL types,
`RECORD_TABLE` enum in `WidgetType`) is independent of the flag and
fully implemented. No backend logic depends on this flag.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-04-13 21:13:15 +00:00
Raphaël Bosi fb772c7695 Sync command menu with main context store (#19650)
## PR description

- The command menu in the side panel now reads directly from
`MAIN_CONTEXT_STORE_INSTANCE_ID` instead of snapshotting the main
context store into a separate side-panel instance when opening. This
keeps the command menu always in sync with the current page state
(selection, filters, view, etc.).
- Removed the broadening/reset-to-selection feature (Backspace to clear
context, "Reset to" button) since the command menu no longer maintains
its own copy of the context.

## Video QA


https://github.com/user-attachments/assets/5d5bc664-b6d4-431d-a271-6ce23d8a4ae0
2026-04-13 16:08:15 +00:00
Raphaël Bosi 1382790c80 Fix side panel close button title (#19638)
## Before
<img width="830" height="1486" alt="CleanShot 2026-04-13 at 15 07 19@2x"
src="https://github.com/user-attachments/assets/757a52bb-f06e-4f04-950a-087cbdec0653"
/>


## After
<img width="838" height="1478" alt="CleanShot 2026-04-13 at 15 06 56@2x"
src="https://github.com/user-attachments/assets/ed9d99ab-fbda-4548-b09a-e7825d86b5dd"
/>
2026-04-13 13:39:51 +00:00
Baptiste Devessier 6829fc315a Implement full tab widget frontend (#19568)
https://github.com/user-attachments/assets/488e0145-ff49-4205-8fd3-0eb4f614c054
2026-04-13 13:24:20 +00:00
Weiko 9e385e44f9 Fields widget draft view (#19562)
- Defer backend view creation for FIELDS widgets to prevent orphan views
when users cancel or leave before saving. Views are now created
optimistically in Jotai state (client-side UUID) and only persisted to
the database when the page layout is saved.
- Align the frontend's default field groups fallback with the backend
logic: split into General/Other groups, hide relation fields by default,
and filter invisible fields consistently across all code paths.
2026-04-12 20:26:47 +02:00
Félix Malfait 55b1624210 Convert AI chat state atoms to component family states (#19585)
## Summary
Refactored AI chat state management to use component family states
instead of global atoms. This change enables proper state isolation per
chat thread, preventing state leakage between different chat instances.

## Key Changes

- **Converted global atoms to component family states:**
  - `agentChatErrorState` → `agentChatErrorComponentFamilyState`
- `agentChatIsStreamingState` →
`agentChatIsStreamingComponentFamilyState`
- `agentChatFirstLiveSeqState` →
`agentChatFirstLiveSeqComponentFamilyState`
- `agentChatHandleEventCallbackState` →
`agentChatHandleEventCallbackComponentFamilyState`
  - `agentChatUsageState` → `agentChatUsageComponentFamilyState`
- `currentAIChatThreadTitleState` →
`currentAIChatThreadTitleComponentFamilyState`

- **Updated state access patterns:**
- Introduced `useAtomComponentFamilyStateCallbackState` hook to get
family state callbacks
- Changed from direct atom access to family key-based access using `{
threadId }` as the family key
- Updated all components and hooks to use the new family state patterns

- **Removed instance ID dependency:**
  - Eliminated `AGENT_CHAT_INSTANCE_ID` constant usage
- Simplified state access by using thread ID directly as the family key

- **Updated affected components and hooks:**
- `useAgentChatSubscription`: Now creates family state atoms per thread
- `AgentChatMessagesFetchEffect`: Uses family state callbacks for event
handling
- `AgentChatThreadInitializationEffect`: Sets family state values per
thread
  - `useAIChatThreadClick`: Updates family states when switching threads
- `AIChatErrorUnderMessageList`, `AIChatLastMessageWithStreamingState`,
`AIChatEmptyState`, `AIChatStandaloneError`, `SendMessageButton`,
`AIChatContextUsageButton`, `SidePanelAskAIInfo`: Updated to use family
state values with thread ID

## Implementation Details

- All new component family states use
`AgentChatComponentInstanceContext` for proper component-level isolation
- Family key structure: `{ threadId: string | null }`
- Removed `disposed` flag logic as it's no longer needed with proper
state isolation
- Updated dependency arrays in hooks to include family callback
functions

https://claude.ai/code/session_01D8syiJtCXu5bEHSr5KYkCt

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-04-12 07:39:12 +02:00
Abdul Rahman 83653a463c fix: side panel close animation cleanup not firing due to invalid CSS transition (#19556)
## Summary

Fixes the side panel close animation cleanup
(`sidePanelCloseAnimationCompleteCleanup`) never running during the
close transition.

This eventually fixes the issue where in navbar edit mode, clicking on a
nav item sometimes doesn't get selected, the side panel opens but shows
`Select a navigation item to edit` instead of the selected item's
settings. The root cause was stale `isSidePanelClosing` state from a
previous close that wasn't cleaned up, causing the next open to run
cleanup synchronously (without emitting the close event), which
interfered with the navigation item selection flow.

### Root cause

The CSS `transition` on `StyledSidePanelWrapper` used
`${themeCssVariables.animation.duration.normal}s`, which Linaria
converts to a CSS custom property value like `width
var(--t-animation-duration-normal)s`. After CSS variable substitution,
`0.3` and `s` become two separate tokens, not a valid `<time>` dimension
(`0.3s`). The browser rejects the entire transition declaration, falls
back to `all 0s`, and the width change happens instantly with no
`transitionend` event.

### Fix

- Use `calc(var(--t-animation-duration-normal) * 1s)` to properly
construct the `<time>` value (consistent with ~30 other usages in the
codebase).
- Filter `handleTransitionEnd` to only process `width` transitions on
the wrapper element itself, ignoring bubbled child `background-color`
transitions.


## Before


https://github.com/user-attachments/assets/496ccbff-dc96-487d-a994-451dbf2a5165




## After


https://github.com/user-attachments/assets/78255240-1d7d-42cd-9b56-25627613883a
2026-04-10 15:58:25 +00:00
Abdul Rahman 34a903b4fa Object icon visual parity (#19374)
## Summary

Aligns **object metadata** icons with the **tinted tile** look
everywhere we show a workspace object, and **retires** the
navigation-only `NavigationMenuItemStyleIcon` wrapper in favor of
**shared** UI primitives under `@/ui/display` and `@/object-metadata`.

## What changed

### Global tinted icon building blocks (`@/ui/display`)

- **`TintedIconTile`** / **`StyledTintedIconTileContainer`** support
optional **`size`** and **`stroke`**, and grow the tile when **`size`**
is set so layouts match previous `theme.icon` usage.
- Shared helpers and constants for theme color parsing and tinted
backgrounds/borders/icon color (e.g.
**`getTintedIconTileStyleFromColor`**, **`parseThemeColor`**,
**`getColorFromTheme`**, related constants).

### Object metadata icon (`@/object-metadata`)

- **`ObjectMetadataIcon`** composes **`TintedIconTile`** with
**`getObjectColorWithFallback`**, forwards optional **`size`** /
**`stroke`** for **visual parity** with old `getIcon` + explicit sizing.
- **`getSelectOptionIconFromObjectMetadataItem`** returns an
**`IconComponent`** for selects/menus that expect a component, not a
React node.

### Navigation module cleanup

- **Removed** **`NavigationMenuItemStyleIcon`**; call sites use
**`ObjectMetadataIcon`**, **`TintedIconTile`**, and/or the shared
**`getTintedIconTileStyleFromColor`** pipeline so the same treatment is
**not** tied to the navigation package.
- **`NavigationMenuItemIcon`**, view/link overlays, DnD handle, and
sidebar editor flows updated to use the shared pattern where they render
object (or tinted) icons.

### Product surfaces updated (non-exhaustive)

- **Settings:** role object picker/rows, data model
tables/graph/overview, object preview summary, webhooks entity list,
morph relation multiselect.
- **Workflows:** create/update/delete/upsert/find records, triggers,
filters, variables dropdowns, AI agent object rows, object dropdowns.
- **Shell:** side panel object filter / data sources / folder chrome
where object icons appear.
- **Records:** index header icon, show breadcrumb styling.
- **Activity:** timeline event icon when linked object metadata applies.
- **`NavigationDrawerItem`:** tinted branch aligned with shared
**`TintedIconTile`** behavior.

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2026-04-10 10:14:20 +00:00
Raphaël Bosi 4b3a46d953 Refactor command menu items deprecated code (#19508)
- Removes the intermediate `CommandMenuItemConfig` /
`CommandConfigContext` / `CommandMenuItemDisplay` abstraction layers,
replacing them with a single `CommandMenuItemRenderer` that renders
directly from the command menu items from the backend
- Eliminates the server-items/ subdirectory by moving its contents
(hooks/, contexts/, states/, display/, edit/) up into the parent
command-menu-item/ module, removing an unnecessary nesting level.
2026-04-10 09:28:27 +00:00
Baptiste Devessier e3077691d1 Edit visibility restriction (#19499)
https://github.com/user-attachments/assets/98496f15-2e58-46a8-b233-9cf46b7b9600
2026-04-09 19:33:15 +00:00
Weiko afdd914b83 Add rich-text field widget (#19512)
## Context
- Extend the FIELD widget to support RICH_TEXT fields alongside existing
RELATION/MORPH_RELATION fields
- Add EDITOR display mode that renders a full rich text editor, and
FIELD display mode that shows a compact single-line preview
- Enforce mutual exclusivity: EDITOR is only available for RICH_TEXT,
CARD only for RELATION, FIELD for both
- Refactor widget configuration into a centralized FIELD_WIDGET_CONFIG
constant and shared useFieldWidgetEligibleFields hook for better
scalability. Later we might use discriminative union from graphql
scehma)

<details>
<summary>
EDITOR mode
</summary>
<img width="1095" height="731" alt="Screenshot 2026-04-09 at 17 31 41"
src="https://github.com/user-attachments/assets/cebffd0e-07ea-4f74-a3dc-ef987daa17ea"
/>
</details>
<details>
<summary>
FIELD mode
</summary>
<img width="986" height="378" alt="Screenshot 2026-04-09 at 17 35 00"
src="https://github.com/user-attachments/assets/c90a8046-fdd0-4321-8ba6-f47d89e9d42a"
/>
</details>
<details>
<summary>
Open FIELD mode
</summary>
<img width="758" height="480" alt="Screenshot 2026-04-09 at 17 35 04"
src="https://github.com/user-attachments/assets/c53cc120-0b6f-47a0-808c-27b26f9f53ec"
/>
</details>
2026-04-09 16:52:30 +00:00
Etienne 34972f6167 Record table widget - Follow up (#19479)
closes https://github.com/twentyhq/core-team-issues/issues/2360
2026-04-09 12:50:10 +00:00
Weiko 238018dc7b Reset Tab Page Layout (#19453)
## Context
- Add "Reset to default" for page layout tabs and widgets — backend
mutation resets overrides, reactivates deactivated children, and deletes
custom children
- Fix override write bug where mutating an overridable property (e.g.
widget title) on a standard-app entity incorrectly overwrote the base
column instead of writing to the overrides JSONB —
PageLayoutUpdateService now uses resolveFlatEntityOverridableProperties
for accurate diffing and routes properties through
sanitizeOverridableEntityInput
- Deprecate isOverridden - We need more time to think about this
feature. Currently this adds too much complexity for a very small
benefit



https://github.com/user-attachments/assets/a84546c8-1e15-4d9e-a489-0825cf8b8ed2
2026-04-08 18:43:57 +02:00
Baptiste Devessier 1f3965e5f8 Add Manage and Placement sections in widget side panel page for record page layouts (#19310)
https://github.com/user-attachments/assets/f6120c2e-95e7-4b9b-abb5-69a10c3f2f3b
2026-04-07 14:21:35 +00:00
Weiko a2188cb0eb Reset Fields widget implementation (#19283)
## Context
This PR implements the first steps for overridable entities resets.

<img width="1277" height="568" alt="Screenshot 2026-04-02 at 18 58 04"
src="https://github.com/user-attachments/assets/4c7f93b1-c453-4905-a919-cd6af11e0e16"
/>
2026-04-07 09:34:44 +00:00
Félix Malfait 83d30f8b76 feat: Send email from UI — inline reply composer & SendEmail mutation (#19363)
## Summary

- **Inline email reply**: Replace external email client redirects
(Gmail/Outlook deeplinks) with an in-app email composer. Users can reply
to email threads directly from the email thread widget or via the
command menu.
- **SendEmail GraphQL mutation**: New backend mutation that reuses
`EmailComposerService` for body sanitization, recipient validation, and
SMTP dispatch via the existing outbound messaging infrastructure.
- **Side panel compose page**: Command menu "Reply" action now opens a
side-panel compose email page with pre-filled To, Subject, and
In-Reply-To fields.

### Backend
- `SendEmailResolver` with `SendEmailInput` / `SendEmailOutputDTO`
- `SendEmailModule` wired into `CoreEngineModule`
- Reuses `EmailComposerService` + `MessagingMessageOutboundService`

### Frontend
- `EmailComposer` / `EmailComposerFields` components
- `useSendEmail`, `useReplyContext`, `useEmailComposerState` hooks
- `useOpenComposeEmailInSidePanel` + `SidePanelComposeEmailPage`
- `EmailThreadWidget` inline Reply bar with toggle composer
- `ReplyToEmailThreadCommand` now opens side-panel instead of external
links

### Seeds
- Added `handle` field to message participant seeds for realistic email
addresses
- Seed `connectedAccount` and `messageChannel` in correct batch order

## Test plan

- [ ] Open an email thread on a person/company record → verify
"Reply..." bar appears below the last message
- [ ] Click "Reply..." → composer opens inline with pre-filled To and
Subject
- [ ] Type a message and click Send → email is sent via SMTP, composer
closes
- [ ] Use command menu Reply action → side panel opens with compose
email page
- [ ] Verify Send/Cancel buttons work correctly in side panel
- [ ] Test with Cc/Bcc toggle in composer fields
- [ ] Verify error handling: invalid recipients, missing connected
account


Made with [Cursor](https://cursor.com)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-07 08:43:48 +02:00
Félix Malfait 8acfacc69c Add email thread widget and message thread record page layout (#19351)
## Summary
- Move email thread display from side panel to a dedicated record page
with a new `EMAIL_THREAD` widget type
- Add message thread as a standard object with page layout, subject
field, and backfill command
- Add reply-to-email command menu item for message thread records
- Remove old side panel message thread components in favor of the new
widget-based approach

## Type fixes
- Add `EMAIL_THREAD` to `WidgetConfigurationType`, `WidgetType`, and all
configuration/validator maps
- Create `EmailThreadConfigurationDTO` and shared
`EmailThreadConfiguration` type
- Register EMAIL_THREAD in widget type validators, configuration
resolvers, and standard widget mappings

## Test plan
- [ ] Verify message thread record pages render with the email thread
widget
- [ ] Verify email thread preview navigates to the record page instead
of opening side panel
- [ ] Verify reply-to-email command appears for message thread records
- [ ] Verify typecheck passes for both twenty-front and twenty-server
- [ ] Run existing test suites to check for regressions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 12:02:04 +02:00
Raphaël Bosi 77315b7f6e Sync record selection with the edit mode (#19276)
## PR description

The previous approach used a detached flag to represent selection mode,
which could was not synced sync with actual UI selection state. This
change ties the edit mode's selection/fallback behavior directly to
whether records are truly selected in the record index.

- Removes `commandMenuItemEditSelectionModeState` (a standalone 'none' |
'selection' atom) and replaces it with
`mainContextStoreHasSelectedRecordsSelector`, which derives selection
state from the real record selection in the context store.
- Adds `useSelectFirstRecordForEditMode` to programmatically select the
first record (table row or kanban card) when toggling to selection mode,
and useResetRecordIndexSelection to clear selection when toggling off or
exiting edit mode.
- Ensures record selection is properly cleaned up when exiting layout
customization mode or closing the side panel.

## Video QA

### Table


https://github.com/user-attachments/assets/1d845809-8369-4872-b3a9-a0281b8e464b

### Board


https://github.com/user-attachments/assets/325c2d58-4ca0-408a-ab4a-66b97d9d70de
2026-04-02 16:35:14 +00:00
nitin 3c067f072c [AI] Improve tools tab (#19221) 2026-04-02 13:36:33 +00:00
nitin ade6ed9c32 [AI] agent node prompt tab new design + refactor (#19012) 2026-04-02 08:24:58 +00:00
Weiko 0b2f4cdb57 Add delete widget action in fields widget side panel (#19167)
## Context
Add a new "Manage" section in FIELDS widget side panel with a "Delete
widget" action

Next step: Deleting a non-custom fields widget should be reversible
("Reset to default" followup)

<img width="1286" height="398" alt="Screenshot 2026-03-31 at 15 17 31"
src="https://github.com/user-attachments/assets/9ee63c14-52f1-470e-9112-4538271dc6fc"
/>
2026-04-02 07:15:19 +00: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
Baptiste Devessier 20ac5b7e84 Field widget edition (#19209)
https://github.com/user-attachments/assets/2f1a5847-3375-414f-a2b8-ce4b533b5512

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-01 15:44:49 +00:00
BugIsGod 176e81cd76 fix: clear navigation stack immediately in goBackFromSidePanel (#19153)
Fixes: #19152 

## Summary
goBackFromSidePanel returned early without writing the new (empty) stack
to the store, leaving stale navigation state. And it caused
openRecordInSidePanel to think the record was already open and skip
reopening.
<img width="587" height="405" alt="image"
src="https://github.com/user-attachments/assets/eb36f4c6-43ca-4c08-891a-c592ff673837"
/>

## Before


https://github.com/user-attachments/assets/03d1e24a-6d1e-4efc-93c1-72be0bc31a89


## After
When close the side panel with Escape, now it can be opened by clicking
arrow button again.




https://github.com/user-attachments/assets/ae700d41-4f81-4d1a-af9a-f97f31f489a6

---------

Co-authored-by: Devessier <baptiste@devessier.fr>
2026-03-31 11:29:03 +00:00
Charles Bochet 4d74cc0a28 chore: remove IS_APPLICATION_ENABLED feature flag (#19081)
## Summary
- Remove the `IS_APPLICATION_ENABLED` feature flag — application
features are now always enabled
- Remove `@RequireFeatureFlag` decorators and `FeatureFlagGuard` from 7
application resolvers (registration, development, manifest, install,
upgrade, marketplace, oauth)
- Remove frontend feature flag checks: settings navigation visibility,
route protection wrapper, side panel widget type gating, and role
permission flag filtering
- Delete the integration test for disabled-flag behavior
- Clean up seed data, test mocks, and generated schema files
2026-03-29 11:43:35 +02:00
Charles Bochet 28de34cf20 chore: remove IS_DASHBOARD_V2_ENABLED feature flag (#19079)
## Summary
- Remove the `IS_DASHBOARD_V2_ENABLED` feature flag from the codebase
- Dashboard V2 features (gauge charts, line charts, pie charts) are now
always enabled
- Remove the validator gate that blocked gauge chart creation/update
without the flag
- Clean up all related code: seed data, dev-seeder service, widget
seeds, and test mocks
2026-03-29 10:45:54 +02:00
Abdul Rahman f47608de07 Clear navbar edit selection when closing the side panel (#18940)
In navbar edit mode, selecting an item for edit stored
selectedNavigationMenuItemIdInEditModeState (and related
pending-insertion state). Closing the side panel did not reset those
atoms, so the nav item stayed visually selected. Reset both when the
panel closes so the highlight matches the closed panel; reopening in
edit mode then starts from the generic “new item” entry unless the user
picks an item again.
2026-03-26 17:49:50 +01:00
nitin afeef8e04a feat: add command menu item to layout customization (#18764)
figma
https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=93630-365000&t=QesAkY3JOyI9D3UU-0


https://github.com/user-attachments/assets/cf3b354d-0b08-41ae-91ae-386054b5cb2e



https://github.com/user-attachments/assets/73bfd676-f823-44c9-a70d-107c20523664

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-26 16:04:03 +01:00
Lucas Bordeau 33a474c8e6 Add record table widget feature flag (#18960)
Introduce a new feature flag for the record table widget, enabling
conditional rendering and state management based on its status. Update
related components and tests accordingly.

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2026-03-25 16:47:20 +00:00