904957ea1e7b4655c5dc569047da01b3a3744fc7
61 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a4ed561e11 |
Add focus-safe side panel shortcuts (#22499)
## Summary - Add side-panel-owned Escape and Backspace behavior for the side-panel search input. - Keep side-panel Escape scoped to side-panel focus and avoid left-content fallback behavior. - Add a Side Panel group to the keyboard shortcut menu. - Reuse the side-panel focus id for AI chat thread-list shortcuts. ## Demo https://github.com/user-attachments/assets/80a632d6-7ff7-496b-905f-a3f95f9cfc14 --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
e609320666 | Squirclesssss 🟦🔵 (#22535) | ||
|
|
a215d3cbdf |
fix: prevent crash when leaving dashboard edit mode with side panel open (#22292)
## Problem
When creating or editing a dashboard and opening a page layout side
panel (clicking the **"Add widget"** box, or editing a widget via grid
edition), clicking another nav item like **"Opportunities"** crashes the
whole app ("Sorry, something went wrong").
### Steps to reproduce
1. Go to `/objects/dashboards` and click **"+ Add new"** to create a
dashboard.
2. Click the **"Add widget"** box (or add a widget via grid edition and
open its settings).
3. Click another nav item such as **"Opportunities"**.
4. The app crashes.
## Root cause
The side panel stays mounted during its close animation, but the main
context store has already switched to the new page (the record index has
no single targeted record). The still-mounted page layout side panel
page (`SidePanelPageLayoutDashboardWidgetTypeSelect`, chart settings,
etc.) re-renders and calls `usePageLayoutIdFromContextStore`, which
throws `Error: Only one record should be selected`. With no local error
boundary, the throw propagates to the top-level boundary and crashes the
app.
## Fix
`SidePanelRouter` now skips rendering page layout side panel pages
whenever the main context store has no single targeted record — the same
condition `usePageLayoutIdFromContextStore` requires to not throw.
During navigation the panel closes cleanly instead of crashing.
This is safe because `usePageLayoutIdFromContextStore` unconditionally
throws without a single-record selection, so the guard can only skip
pages that would otherwise crash — it cannot break a currently-working
flow. The guard uses the existing `isPageLayoutSidePanelPage` helper, so
it covers all page layout side panel pages (widget type select, chart /
iframe / record table settings, record page field settings, etc.).
## Testing
- Reproduced the crash in the running app, applied the fix, and
confirmed navigating to Opportunities from both the **widget type
select** and the **chart settings** panels now lands on the
Opportunities list with no console or page errors.
- `oxlint --type-aware` and `nx typecheck twenty-front` both pass.
|
||
|
|
864ea452b4 |
fix mobile side panel close (#22169)
The side panel close (X) button was hidden on all mobile views, while the back button only renders when there is navigation history. When the side panel is opened at the root (e.g. viewing a record directly with a single-item navigation stack), neither button was shown, leaving no way to dismiss the panel on mobile. Keep the close button available on mobile whenever there is no back button to fall back on, so the panel is always dismissable. ## Before https://github.com/user-attachments/assets/61891d25-26b8-4ba4-8b05-73fd44f92d89 ## After https://github.com/user-attachments/assets/41342722-bcaf-420b-83bb-3cafaec49516 |
||
|
|
5f22908588 |
Decouple twenty-ui Avatar from app server-URL config (#21968)
Makes `twenty-ui`'s `Avatar` render the `avatarUrl` it receives instead of building it from `window._env_`/`window.location` at module load, so the library no longer depends on the app environment. URL resolution moves to `twenty-front` via a `getAbsoluteImageUrl` helper applied at the call sites. Part of making twenty-ui a standalone library. |
||
|
|
b8ea742a88 |
fix(front): respect user number format for counts and aggregates (#21894)
## Problem
Several user-facing numbers were rendered raw (e.g. `153909`) instead of
honoring the workspace member's **Number format** preference (e.g. `153
909` with `Spaces and comma`). The formatting utilities already existed
(`formatNumber` / `useNumberFormat`) but were not applied on these
surfaces.
## Root cause
`transformAggregateRawValueIntoAggregateDisplayValue` — the shared
helper behind every table/board/chart aggregate — returned the `COUNT`
branch as a raw string and never threaded the user's locale format into
`formatNumber` for the other branches (so they silently fell back to
`COMMAS_AND_DOT`).
Its existing `numberFormat` param actually held the chart `SHORT`/`FULL`
abbreviation setting, so it is renamed to `chartNumberFormat`, and a new
`numberFormat: NumberFormat` now carries the locale separators.
## Surfaces fixed
- Record table footer aggregates, including the raw **"Count all"**
total
- Record board column / group-section aggregates
- Aggregate chart and pie-chart center metric (including their raw
`COUNT` early-returns)
- View picker `<view> · <count>` total
- Record show breadcrumb pagination `(x/y)`
- Record index header and side panel `N selected` counts
The board-column header needs no change — it now receives an
already-formatted string from the transform.
## Out of scope (intentionally left raw)
The editable `SettingsCounter` input (formatting would break parsing),
the advanced-filter pill, the `+N` overflow badge, and the AI routing
debug display.
## Testing
- New + existing unit tests pass
(`transformAggregateRawValueIntoAggregateDisplayValue`, `formatNumber`,
`useNumberFormat`), with added locale-aware coverage (`SPACES_AND_COMMA`
→ `153 909`, `DOTS_AND_COMMA` → `153.909`).
- `nx typecheck twenty-front`, oxlint and oxfmt on the diff all pass.
> Note: two i18n strings change placeholder shape (`{count} selected` →
`{0} selected`); a `lingui:extract` will refresh the catalogs (runtime
falls back to source text meanwhile).
https://claude.ai/code/session_013XNL2Xa11Bw7fsnPFQgsGX
---
_Generated by [Claude
Code](https://claude.ai/code/session_013XNL2Xa11Bw7fsnPFQgsGX)_
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21894?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
|
||
|
|
454758471f |
Fix side panel command menu header controls (#21747)
## Summary Tested the 3 behaviors of the issue locally. Animation is not perfect on the closing but I think this is a great v1 - Move the side-panel close action to the right side of the top bar while keeping back navigation on the left. - Keep the nav side-panel button as the command-menu entry point for direct side-panel pages and hide it while command-menu pages/history are active. - Reset command-menu search/filter state when opening the root command menu from the nav button. Fixes twentyhq/core-team-issues#2504 ## Videos ### Before https://github.com/user-attachments/assets/08c1b6b3-5fbd-4154-a85d-5072a3b7690e ### After https://github.com/user-attachments/assets/11682dea-f21c-47b5-91a8-869f30b09d96 <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21747?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. --> |
||
|
|
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. -->
|
||
|
|
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. |
||
|
|
079040f1c0 |
Fix selectable list arrow focus (#21679)
## Summary Tested on all select fields one by one - Keep searchable selectable-list inputs focused while ArrowUp/ArrowDown moves the selected item. - Remove the old global "grid focused" mode and blur/refocus recovery path. - Scroll the selected item into view with `block: 'nearest'`, which restores keyboard scrolling in long relation pickers without forcing the row to the top. - Add focused regressions for command-menu input focus and selected-item scrolling. ## Root Cause `SelectableList` hotkeys blurred the active input before arrow navigation and stored a global grid-focused state. That let ArrowDown move selection, but focus could fall back to the underlying page/table instead of remaining in the command menu input. ## Recording ### Before https://github.com/user-attachments/assets/a802cbc3-4cfd-4466-bc22-274935a77715 ### After https://github.com/user-attachments/assets/9c315d2e-dcb1-424d-80c9-a5942eb1b6bd ## QA Note I checked all inputs one by one with a 2h30 agent in goal mode. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21679?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. --> |
||
|
|
4b3f38f8f2 |
Show app logo and name in workflow step side panel header (#21689)
Show app logo and name in workflow step side panel header ## Before <img width="798" height="106" alt="CleanShot 2026-06-16 at 17 59 39@2x" src="https://github.com/user-attachments/assets/a8881d1c-355c-4e3a-9379-0c3a7cfbf42f" /> ## After <img width="800" height="102" alt="CleanShot 2026-06-16 at 18 05 27@2x" src="https://github.com/user-attachments/assets/43820d1a-6445-42d8-8f45-963e8741cbb7" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21689?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. --> |
||
|
|
7c0136b97b |
feat(deps): migrate frontend to React 19 (#21531)
## What Migrates the frontend stack from **React 18.3 → 19.2**. The website, sdk, companion and emails packages were already on React 19; this brings the remaining holdouts (`twenty-front`, `twenty-ui`, `twenty-ui-deprecated`, `twenty-front-component-renderer`) and `twenty-server`'s email rendering onto 19, and pins a single React version repo-wide. ## Why React 18.x is now the legacy line. Staying current keeps us on the patched/maintained branch and unblocks downstream library majors (react-router 7, mantine 9, etc.) that require React 19 peers. ## Dependency bumps (required by React 19 peers / removed APIs) | Package | From | To | Reason | |---|---|---|---| | react / react-dom | 18.3.1 | 19.2.3 | core | | @hello-pangea/dnd | 16 | 18 | peer `^18 \|\| ^19` | | react-datepicker | 6 | 9 | v<7 used removed `findDOMNode`; drops `@types/react-datepicker` | | react-data-grid | beta.13 | beta.59 | peer `^19.2`; new render API | | graphiql (+ @graphiql/react, plugin-explorer) | 3 / 0.23 / 1 | 5 / 0.37 / 5.1 | peer `^18 \|\| ^19` | | react-helmet-async | 1.3 | **@dr.pogodin/react-helmet** 3.2 | upstream caps peer at `^18`; drop-in React 19 fork | A `resolutions` pin enforces a single React (19.2.3) + `@types/react` (19.2.14) across the monorepo to avoid duplicate copies / type-identity splits. Versions are the aged lockfile patches (clears the `npmMinimalAgeGate`). ## Code changes - **Global `JSX` shim** (`react-jsx-global.d.ts` per package): React 19 moved the `JSX` namespace under `React.JSX`; several deps' published types (notably `@linaria/react`'s `styled.d.ts`, which types every `styled.x` via `keyof JSX.IntrinsicElements`) still reference the global namespace. Without the shim, every styled component degrades to `any` props. - **Ref nullability**: `useRef<T>(null)` now returns `RefObject<T | null>`; widened consumer prop/hook ref types accordingly (incl. the shared `useListenClickOutside`). - **react-datepicker v9**: `onChange`/`onSelect` accept `Date | null`, `calendarStartDay` typing, `ReactDatePickerProps`→`DatePickerProps`, relaxed the dynamic `selectsMultiple` discriminated union. - **react-data-grid beta.59**: `formatter`→`renderCell`, `editor`→`renderEditCell`, `headerRenderer`→`renderHeaderCell`, `components`→`renderers`, `onRowClick`→`onCellClick`, object-shaped `useRowSelection`, Set-based selection. - **dnd style cast**: `@radix-ui/react-popper` augments `CSSProperties` with a `--radix-*` index signature that dnd's closed `DraggingStyle` doesn't satisfy → cast at the spread. ## Status / testing - ✅ `typecheck` green: twenty-front, twenty-ui, twenty-ui-deprecated, twenty-front-component-renderer, twenty-server - ⏳ build / lint / unit tests / storybook+argos / runtime smoke-test in progress Draft until local + CI verification completes. Notable behavior to QA manually: spreadsheet import (data-grid), date pickers, drag-and-drop boards/lists, GraphQL playground, page titles/favicon. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21531?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. --> |
||
|
|
d06e687b77 |
Fix settings UI polish pass 2 (#21540)
## Summary This PR groups the requested second UI polish pass across the app shell, settings pages, data model, community, AI tools, and developer setup surfaces. ### Shell and navigation polish - Lets the app content card fill the available viewport and removes the bottom-left radius on the main container. - Uses an outside-looking container border treatment so the rounded top-left edge matches the Figma frame without a double border. - Restores the separator between the AI chat side panel and the record container. - Updates navigation drawer background/skeleton tones to use gray/3 consistently. - Simplifies the full-screen backend error fallback so the white screen fills the viewport. ### Settings page polish - Moves the layout customization action from the settings navbar into a settings card under the hero. - Tightens layout page copy now that the customization surfaces are not directly manageable yet. - Aligns role detail title sizing and hover treatment with other settings headers. - Supports settings card brand icon colors, then restores Discord and X brand logos on the Community page. - Adds the Community discovery cover, moves community/social content to the top, and reorganizes Partners and Features. - Keeps setting card subtitle and separator behavior available for the updated settings cards. ### Data model and object settings polish - Adds a shared data-model table body wrapper so object, field, and relation tables all keep the expected bottom border. - Displays the object icon in the object settings navbar. - Shares the new-field wizard parent object icon wrapper and keeps the object icon at 64% opacity in wizard headers. - Updates the new-field type selector icon treatment to match the wizard header opacity behavior. ### Integrations, tools, and API polish - Reworks the MCP setup config card with light syntax coloring and a top-right copy icon button instead of the bottom copy section. - Fixes email import icons to match the Figma asset shape. - Ensures AI tool table icons and chevrons never fall back to black. ## Validation - `npx nx lint:diff-with-main twenty-front` - `npx tsc -p packages/twenty-front/tsconfig.json --noEmit` - `git diff --check` - Manual local visual pass on `apple.localhost:3001` for companies, settings/community, data model, object settings, new-field wizard, layout settings, account import, MCP setup, AI tools, and role detail pages. ## Visual QA ### Shell and settings frame <img width="1308" height="1397" alt="Shell and settings frame before/after board" src="https://raw.githubusercontent.com/twentyhq/twenty/pr-assets/ui-fixes-2/visual-qa/ui-fixes-2/shell-settings-frame.png" /> ### Community and data model <img width="1308" height="1806" alt="Community and data model before/after board" src="https://raw.githubusercontent.com/twentyhq/twenty/pr-assets/ui-fixes-2/visual-qa/ui-fixes-2/community-data-model.png" /> ### Integrations and tools <img width="1308" height="1397" alt="Integrations and tools before/after board" src="https://raw.githubusercontent.com/twentyhq/twenty/pr-assets/ui-fixes-2/visual-qa/ui-fixes-2/integrations-tools.png" /> <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21540?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. --> |
||
|
|
9d7f0c405f |
fix(twenty-front): new layout fast-follows — command menu, field options & logs (#21429)
Fast-follows for the new layout — the remaining open sub-issues of
twentyhq/core-team-issues#2478.
## Changes
- **Command menu items should not have extra right padding**
(twentyhq/core-team-issues#2500)
`SidePanelList` set `width: calc(100% - spacing[4])` on top of its own
8px left/right padding. Under the global `box-sizing: border-box`, that
extra `-16px` shrinks the list and, because it's left-aligned, dumps the
whole gap on the right. Switched to `width: 100%` so item highlights
inset 8px symmetrically. This is shared by every side-panel list — they
all had the same right-only gutter, so they're all corrected the same
way.
- **Field options should not be cropped and should keep row gaps**
(twentyhq/core-team-issues#2503)
The option row used a fixed `height: spacing[6]`, so under border-box
the `6px` vertical padding was absorbed and consecutive rows sat flush.
Changed `height` → `min-height` so the padding separates the rows again.
- **Logs table with filters should use Background secondary**
(twentyhq/core-team-issues#2505)
The Logs filter card and the upgrade card defaulted to a transparent
background, showing the white page through. Passed
`backgroundColor={themeCssVariables.background.secondary}`, matching
`SettingsTableCard`. The results table stays on the primary surface, per
the Figma reference.
- **Command menu back chevron** (twentyhq/core-team-issues#2504)
`SidePanelTopBar` showed a back chevron whenever the nav stack had more
than one entry. A command-menu page is the root of a fresh command-menu
session, so it now only shows the chevron when it was opened from
another command-menu page. Every other side-panel page keeps standard
history-based back navigation, so workflow / page-layout / record stacks
are unaffected.
## Verification
- oxlint (`--type-aware`, full `src/`): 0 errors
- oxfmt: clean
- tsgo typecheck: no errors in the changed files (the one reported error
is pre-existing in `RestPlayground.tsx`, which this PR does not touch)
- Verified live at apple.localhost:
- #2500 — command menu item highlight insets measured 8px left / 8px
right (was 8 / 24)
- #2503 — option rows render at 38px tall with ~14px gaps, text no
longer cropped
- #2505 — filter + upgrade cards compute to background-secondary;
results table stays on primary
- #2504 — direct command menu shows the close-X with no chevron; pages
opened from the command menu still show the chevron
## Open question for review (#2504)
The issue also describes the page-header three-dots toggle: *"the three
dots icon button should remain visible if the side panel is on a page
that is not a child of the command menu (AI chat, or a page opened
directly)."* Today that toggle morphs to an X for any non-command-menu
side-panel page (e.g. Ask AI). Honoring that touches the shared
`SidePanelToggleButton`, and there's a related decision: search / Ask AI
opened from the command menu currently reset the nav stack rather than
push, so they don't get a "back to command menu" chevron. I left those
out here since they're a behavior change to a shared control with a
product call attached — happy to follow up once you confirm the intended
toggle behavior.
Closes twentyhq/core-team-issues#2500
Closes twentyhq/core-team-issues#2503
Closes twentyhq/core-team-issues#2505
Refs twentyhq/core-team-issues#2504
Refs twentyhq/core-team-issues#2478
|
||
|
|
adba66caea |
fix(twenty-front): new layout fast-follows — settings drawer, loading & command menu (#21389)
Second batch of new-layout fast-follows (master: twentyhq/core-team-issues#2478). All changes verified live against a running workspace. ## Settings drawer & header - **twentyhq/core-team-issues#2489** — sidebar icons render as plain 16px icons, no background tiles. - **twentyhq/core-team-issues#2488** — Advanced toggle spans the full drawer width; yellow dot removed. - **twentyhq/core-team-issues#2497** — page title stays centered in the settings header (breadcrumb stays left). - **twentyhq/core-team-issues#2490** — Exit Settings control aligned to the workspace switcher (24px, matching padding/gap). - **twentyhq/core-team-issues#2499** — 2px vertical gap restored between collapsible drawer section items. - **twentyhq/core-team-issues#2491** — settings drawer rhythm now matches the main app (28px items, 2px gaps, 28px section headers). - **twentyhq/core-team-issues#2492** — Home/Chat tab switch no longer flickers: both tab subtrees stay mounted (a shared `NavigationDrawerTabbedContent` toggles visibility instead of remounting + flashing the chat skeleton). ## Loading states - **twentyhq/core-team-issues#2486** — metadata loading shows an empty body (no dense skeleton rows). - **twentyhq/core-team-issues#2487** — settings table keeps its layout while loading, with the shimmer localized to the first row's first cell. ## Command menu & navigation - **twentyhq/core-team-issues#2501** — navigation section header height matches the nav item rhythm (28px). - **twentyhq/core-team-issues#2502 (part 1)** — the page side-panel toggle stays as the dots glyph while the command menu is open, instead of morphing into a second close control. ## New-field flow - **twentyhq/core-team-issues#2494** — the new-field stepper moved from a breadcrumb dropdown into a centered secondary wizard bar (back chevron + Save on the configure step); breadcrumb stays clean and the object label is the centered title. ## Descoped (substantive bugs already fixed) - **twentyhq/core-team-issues#2500** — command-menu highlight right gutter: the menu-item base measures full-width, so it's likely a scrollbar gutter on the list, not the shared component. Left for a focused follow-up. - **twentyhq/core-team-issues#2502 part 2** — moving the command-menu close from left to right is cosmetic (the duplicate-control bug is fixed by part 1) and would touch the shared `SidePanelTopBar` used by search/AI panels. ## Verification typecheck (tsgo) + oxlint + oxfmt green for all changed files; each change DOM-measured / screenshotted in the running app. |
||
|
|
02aa086866 |
fix(twenty-front): new layout fast-follows (#21360)
Fast-follows for the new layout / flat redesign (master: twentyhq/core-team-issues#2478). ## Changes - **Main navbar 48px** (twentyhq/core-team-issues#2479) — `SIDE_PANEL_TOP_BAR_HEIGHT` 40 → 48, so `PageCardHeader` matches the Figma target. The side panel top bar shares this constant and stays aligned. - **Content panel 12px radius** (twentyhq/core-team-issues#2480) — `PageCardLayout` card gets a full border + 12px radius and an 8px inset (`spacing[2]`) so it floats on the shell instead of square full-bleed. - **Square three-dots button** (twentyhq/core-team-issues#2481) — added a `square` option to `AnimatedButton`; the page-header side-panel toggle now renders a 24×24 square icon button instead of a 32×24 pill. - **Table checkbox sizing** (twentyhq/core-team-issues#2482) — restored `box-sizing: content-box` on the checkbox box. Its border is declared outside the label size, so the global `border-box` reset (#21349) was shrinking it (14px → 12px). Same fix pattern as #21349. - **Tertiary navbar background** (twentyhq/core-team-issues#2483) — left navbar / app shell use `background/tertiary` instead of the noisy surface (`DefaultLayout`, `UserOrMetadataLoader`). - **Skeleton loading** (twentyhq/core-team-issues#2484) — metadata + content loading now match the new layout: tertiary shell, 12px rounded content panel, 48px navbar, sparse bars, empty body (removed the dense full-width rows). Left-panel skeleton bars use `quaternary` so they stay visible on the tertiary shell. ## Verification - typecheck (tsgo) + oxlint + oxfmt pass for all changed files. - Verified live against a running workspace: measured navbar = 48px, three-dots = 24×24, checkbox box-sizing = content-box (14px), card radius = 12px, shell background = tertiary (no noisy image). Content-loading skeleton matches the target. |
||
|
|
bfefcd3755 |
feat(twenty-front): generalize the page primary/secondary bars (flat redesign) (#21308)
Replaces #21279 and #21282 with one clean PR from `main`. Generalizes the settings primary-bar / secondary-bar card chrome to the record index, record show and standalone pages via a shared `PageCardLayout` + `PageCardHeader` (the side panel sits as a sibling of the content card), and applies the new flat design direction: square corners on the card, side panel and loading skeletons. Iterating toward the new design (Figma node 102282-221623); the confirmed direction and the explicit "remove rounded corners" change are in, remaining designer specifics to follow. |
||
|
|
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`. |
||
|
|
c18f8d6cf7 |
Always show Favorites section and add favorites via the side panel (#21087)
## What & why The left-sidebar **Favorites** section was hidden whenever the user had no favorites, so it was effectively undiscoverable — and personal favorites could only be created via the record-level "Add to favorites" action or drag-drop. This PR: - **Always shows the Favorites section**, with an empty-state **"Add a favorite"** call-to-action. - Adds a **"+" on the Favorites header that opens the same "New menu item" side panel** the Workspace section already uses, so users can add personal **Objects, Views, Records, Links and Folders** directly from the sidebar. ## How Favorites and workspace navigation are the same `NavigationMenuItem` entity (a personal favorite simply has `userWorkspaceId` set). Rather than build a separate favorites-only flow, the shared add/edit side-panel subsystem is made **section-aware** (`NavigationMenuItemSection = 'workspace' | 'favorite'`): - a new `navigationMenuItemEditSectionState` atom records which section the panel is operating on; - a new `useNavigationMenuItemEditController` forks persistence — the **workspace** section stages changes in the draft (saved on layout-customization exit), while the **favorite** section creates/updates/deletes personal items **immediately** with `userWorkspaceId = current member`. This mirrors the existing `useHandleNavigationMenuItemDragAndDrop` fork. The existing add/edit hooks, pickers and title editors were rerouted through the controller and a section-aware items hook, so they work for both sections with no behavior change to the workspace flow. **Backend: no changes** — `canUserCreateNavigationMenuItem` already authorizes personal navigation menu items of every type for any authenticated user. ## Decisions & tradeoffs - **Folder button → unified "+":** the folder-only header button is replaced by the single "+" (Folder is one of the panel's options), matching the Workspace section. This removed the inline folder-create code path. - **Click-to-add only in v1:** dragging items from the panel directly into Favorites is deferred — those drag handles are disabled in the favorite section (the drag path is hardwired to workspace layout mode), with a defense-in-depth no-op in the drop handler. - **Persist-on-commit:** favorite title/URL edits hit the network once on blur/enter, never per keystroke. - **Personal color edits** change only the favorite's own color, never the shared object metadata (that remains a workspace-customization behavior). - The change touches ~37 files because it generalizes the shared subsystem rather than duplicating it; net diff is slightly negative (+605 / −636). ## Testing - `npx nx typecheck twenty-front` — passes - `npx nx lint twenty-front` (oxlint + oxfmt) — passes - `navigation-menu-item` unit tests — pass (incl. an updated `computeInsertIndexAndPosition` test covering personal items) - Manual end-to-end walkthrough still recommended before merge. |
||
|
|
51202d5a32 |
fix(front): scroll long content in rich text editor (#20319)
## Summary Fixes scroll behavior in the rich text editor. Long content was unbounded — the popup grew off-screen, the expand-to-side-panel button became unreachable, and the side panel itself didn't scroll either. Closes #20309 ## Changes - `RichTextFieldInput`: cap the popup at `min(60vh, 500px)` and wrap the editor in a scrollable region. Collapse button stays at the top via `align-items: flex-start`. - `RecordInlineCellEditMode`: add `shift()` middleware to keep the popup inside the viewport after `flip()` triggers (previously the popup could extend above the viewport top with the collapse button out of reach). - `SidePanelContainer` / `SidePanelRouter`: add `min-height: 0` to the flex-column chain so the existing `overflow-y: auto` on the content area can actually clip and scroll long children. The previous attempt in #20310 added the same `min-height: 0` plus a nested overflow wrapper inside the rich-text page; the nested wrapper turned out to be the reason scrolling didn't work. ## Test plan - [x] Open a record with a long `RICH_TEXT` field - [x] Click the field — popup opens bounded; long content scrolls inside - [x] Click the expand button (top-right) — side panel opens - [x] Side panel: long content scrolls vertically - [x] Popup near the bottom of the viewport flips upward and stays fully inside the viewport (collapse button remains visible) - [x] `lint:diff-with-main`, `typecheck`, prettier — green https://github.com/user-attachments/assets/827be881-aadd-49ed-9ddc-7566c00cf4be --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
323e66433e |
lint: migrate prettier to oxfmt (#20783)
Most changes are `implements` being unwrapped this is not a oxfmt regression Prettier in 3.7 (we're on 3.1) changed this behaviour prettier blog [post](https://prettier.io/blog/2025/11/27/3.7.0#change-18094) This unifies our linting tooling --------- Co-authored-by: github-actions <github-actions@twenty.com> Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
140dceebd1 |
fix(front): use theme-aware color for side panel title (#20645)
## Summary
- Added `color: ${themeCssVariables.font.color.primary}` to
`StyledPageInfoTitleContainer` in `SidePanelPageInfoLayout.tsx`
- The "Update records" title had no explicit color, so it didn't adapt
to dark mode and was nearly invisible against the dark background
- Now correctly uses the theme-aware primary font color
## Changes
-
`packages/twenty-front/src/modules/side-panel/components/SidePanelPageInfoLayout.tsx`
Fixes #20627
|
||
|
|
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" /> |
||
|
|
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> |
||
|
|
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
|
||
|
|
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" /> |
||
|
|
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 |
||
|
|
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 |
||
|
|
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" /> |
||
|
|
6829fc315a |
Implement full tab widget frontend (#19568)
https://github.com/user-attachments/assets/488e0145-ff49-4205-8fd3-0eb4f614c054 |
||
|
|
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>
|
||
|
|
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
|
||
|
|
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> |
||
|
|
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. |
||
|
|
34972f6167 |
Record table widget - Follow up (#19479)
closes https://github.com/twentyhq/core-team-issues/issues/2360 |
||
|
|
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 |
||
|
|
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> |
||
|
|
9f95c4763c |
[COMMAND MENU ITEMS] Remove deprecated code (#19199)
This PR is the first one of a cleanup after upgrading command menu items to V2. |
||
|
|
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> |
||
|
|
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> |
||
|
|
5f0d6553f6 |
Add object type filter dropdown to side panel record searches (#18912)
## Summary - Adds an object type filter dropdown to the side panel, allowing users to scope record search results to a specific object type (e.g. People, Companies, Opportunities) - The filter appears as a funnel icon next to the search input in both the global search (SearchRecords page) and the "Pick a record" sidebar flow - Replaces the AI sparkles button on the search records page with the filter icon - Uses colored icons matching the navigation menu style (NavigationMenuItemStyleIcon + getStandardObjectIconColor) ## Test plan - [ ] Open the side panel, type something to enter SearchRecords mode, verify the filter icon appears - [ ] Click the filter icon, verify the dropdown opens with "Object" header, search input, "All Objects" and individual object types with colored icons - [ ] Select an object type, verify only records of that type appear in search results - [ ] Select "All Objects", verify all record types appear again - [ ] Verify the filter icon turns blue when a filter is active - [ ] In layout customization mode, add a new sidebar item > Record, verify the filter dropdown also works there - [ ] Close and reopen the side panel, verify the filter resets to "All Objects" - [ ] Verify the AI sparkles button no longer appears on the command menu root page - [ ] Verify the AI edit icon still appears on Ask AI pages Made with [Cursor](https://cursor.com) |
||
|
|
4a099ed097 |
Use side panel sub-pages for add-to-folder flow (#18872)
https://github.com/user-attachments/assets/a31b8523-2d0a-4a02-bcd9-5548c906b7cc --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
96f3ff0e90 |
Add record table widget to dashboards (#18747)
## Demo https://github.com/user-attachments/assets/584de452-544a-41f8-ae9f-4be9e9d0cd9f ## Problem - Dashboards only supported chart widgets — tabular record data had no inline widget type - `RecordTable` was tightly coupled to the record index: HTML IDs, CSS variables, and hover portals were global strings with no per-instance scoping, so multiple tables on the same page would collide - `updateRecordTableCSSVariable`, `RECORD_TABLE_HTML_ID`, and cell portal IDs were hardcoded — placing two tables caused hover portals and CSS column widths to bleed across instances - Grid drag-select captured record UUIDs as cell IDs, producing `NaN` layout coordinates and a full-page freeze on second widget creation ## Fix - `RECORD_TABLE` is now a valid widget type across the full stack — server DTOs, DB enum migration, universal config mapping, GraphQL codegen, shared types (`RecordTableConfigurationDto`, `WidgetType`, `addRecordTableWidgetType` migration) - A record table widget can be placed on a dashboard and boots from a View ID with no record index dependency — `StandaloneRecordTableProvider` + `StandaloneRecordTableViewLoadEffect` (wraps existing `RecordTableWithWrappers` unchanged) - Selecting a data source auto-creates a dedicated View with up to 6 initial fields; switching source or deleting the widget cleans up the View — `useCreateViewForRecordTableWidget` + `useDeleteViewForRecordTableWidget` - The settings panel exposes source, field visibility/reorder, filter conditions, sort rules, and editable widget title — `SidePanelPageLayoutRecordTableSettings` + sub-pages, matching chart widget pattern - Filters, sorts, and aggregate operations update the table in real time but only persist to the View on explicit dashboard save — `useSaveRecordTableWidgetsViewDataOnDashboardSave` (diff + flush on save) - Headers are always non-interactive (no dropdown, no cursor pointer); columns are resizable only in edit mode; cells are non-editable in both modes — `isRecordTableColumnHeadersReadOnlyComponentState`, `isRecordTableColumnResizableComponentState`, `isRecordTableCellsNonEditableComponentState` (Jotai component states) - Hover portals and CSS column widths no longer bleed between multiple table widgets — `getRecordTableHtmlId(tableId)`, `getRecordTableCellId(tableId, …)`, `updateRecordTableCSSVariable(tableId, …)` scope all DOM IDs and CSS variables per instance - Clicking inside a widget's content area no longer opens the settings panel — `WidgetCardContent` stops click propagation when editable, limiting settings-open to the card header and chrome - Second widget creation no longer freezes the page — `PageLayoutGridLayout` drag-select filters by `cell-` prefix to exclude record UUIDs from grid cell detection ## Follow-up fixes **Widget save flow** - Saving a dashboard silently dropped record table widget changes (column visibility, order, filters, sorts, aggregates) because widget data save was bundled inside the layout save and only ran when layout structure changed - Widget data now persists independently via `useSavePageLayoutWidgetsData`, called in all save paths (dashboard save, record page save, layout customization save); saves are also skipped when nothing has changed **Drag-and-drop / checkbox columns in widget** - Record table widgets showed the drag handle column and checkbox selection column even though row reordering and multi-select are meaningless in a read-only widget - Two new component states (`isRecordTableDragColumnHiddenComponentState`, `isRecordTableCheckboxColumnHiddenComponentState`) hide each column independently; widget tables now display only data columns **Sticky column layout** - Sticky positioning of the first three columns used `:nth-of-type` CSS selectors — when drag or checkbox columns were hidden, the selector targeted the wrong column and the first data column didn't stick - Sticky CSS now targets semantic class names (`RECORD_TABLE_COLUMN_DRAG_AND_DROP_WIDTH_CLASS_NAME`, etc.) so sticky behavior is correct regardless of which columns are hidden **Save/Cancel buttons during edit mode** - Save and Cancel command-menu buttons were unpinned during dashboard edit mode because the pin logic excluded all items while `isPageInEditMode` was true - Items whose availability expression contains `isPageInEditMode` are now exempted from the unpin rule; Save/Cancel stay pinned during editing **Title input auto-focus** - Selecting "Record Table" as widget type auto-focused the title input, interrupting the configuration flow - `focusTitleInput` is now `false` when navigating to record table settings **Morph relation field error** - A field with missing `morphRelations` metadata crashed the page with a "refresh" error from `mapObjectMetadataToGraphQLQuery` - Now returns an empty array and silently omits the field from the query instead of crashing **`updateRecordMutation` prop removal** - `RecordTableWithWrappers` required callers to pass an `updateRecordMutation` callback, duplicating `useUpdateOneRecord` at every usage site - The mutation is now owned inside `RecordTableContextProvider` via `RecordTableUpdateContext`; the prop is gone **Standalone → Widget module rename** - `record-table-standalone` module renamed to `record-table-widget` — `StandaloneRecordTable` → `RecordTableWidget`, `StandaloneRecordTableViewLoadEffect` → `RecordTableWidgetViewLoadEffect`, etc. **RecordTableRow cell extraction** - Row rendering logic (`RecordTableCellDragAndDrop`, `RecordTableCellCheckbox`, `RecordTableFieldsCells`, hotkey/arrow-key effects) was duplicated between `RecordTableRow` and `RecordTableRowVirtualizedFullData` - Extracted `RecordTableRowCells` (shared cell content) and `RecordTableStaticTr` (non-draggable `<tr>` wrapper); when drag column is hidden, rows render inside a static `<tr>` instead of the draggable wrapper **View load effect metadata tracking** - `RecordTableWidgetViewLoadEffect` now tracks `objectMetadataItem.updatedAt` alongside `viewId` to re-load states when metadata changes (e.g. field additions), preventing stale column data **Data source dropdown deduplication** - Extracted `filterReadableActiveObjectMetadataItems` util, shared by both chart and record table data source dropdowns — removes duplicated permission-filtering logic **RECORD_TABLE view identifier mapping (server)** - Added `RECORD_TABLE` case to `fromPageLayoutWidgetConfigurationToUniversalConfiguration` and `fromUniversalConfigurationToFlatPageLayoutWidgetConfiguration` so widget views are properly mapped during workspace import/export **GraphQL error handler typing (server)** - `formatError` parameter changed from `any` to `unknown`; `workspaceQueryRunnerGraphqlApiExceptionHandler` broadened from `QueryFailedErrorWithCode` to `Error | QueryFailedError` — removes unsafe type casts **Save hook signature** - `useSaveRecordTableWidgetsViewDataOnDashboardSave` no longer takes `pageLayoutId` in constructor; receives it as a callback parameter, eliminating the need for `useAtomComponentStateCallbackState` **Customize Dashboard hidden during edit mode** - The "Customize Dashboard" command was still visible while already editing — its `conditionalAvailabilityExpression` now includes `not isPageInEditMode` **Fields dropdown split** - `RecordTableFieldsDropdownContent` (300+ lines) split into `RecordTableFieldsDropdownVisibleFieldsContent` and `RecordTableFieldsDropdownHiddenFieldsContent` **Checkbox placeholder cleanup** - Removed unnecessary `StyledRecordTableTdContainer` wrapper from `RecordTableCellCheckboxPlaceholder` |
||
|
|
cec23e89fa |
Fix batch update optimistic and prevent accidental mass-update (#17213)
## Problem - ⚠️ Multi-edit could silently update ALL records of an object with no undo, no ctrl-z - After a batch update the table showed stale data — `useIncrementalUpdateManyRecords` had no explicit `findMany` refetch and `skipOptimisticEffect` was missing - Clicking inside the side panel deselected all kanban cards — `RecordBoardClickOutsideEffect` uses `refs:[]` and the side panel had no `data-click-outside-id` - Clicking a currency/select dropdown inside the panel also triggered deselection — `FloatingPortal` renders outside the side panel DOM, bypassing the click-outside-id exclusion - An empty selection silently matched every record — `computeContextStoreFilters` returned `undefined` filter when `selectedRecordIds` was `[]` ## Fix - Table refreshes correctly after batch update — `useRefetchFindManyRecords` explicitly refetches `FindMany<Object>` queries; `useIncrementalUpdateManyRecords` adds `skipOptimisticEffect: true` and calls it in `finally` - Clicking the side panel no longer deselects kanban cards — `SidePanelForDesktop` carries `data-click-outside-id`; `RecordBoardClickOutsideEffect` + `RecordTableBodyFocusClickOutsideEffect` exclude it - Clicking dropdowns inside the panel no longer deselects either — `ParentClickOutsideIdContext` propagates the side panel ID into `FloatingPortal` content via `DropdownInternalContainer` - Empty selection can no longer match all records — `computeContextStoreFilters` returns `{ id: { in: [] } }` instead of `undefined` - Apply is disabled with no selection; a confirmation modal shows the count + no-undo warning before executing — `UpdateMultipleRecordsContainer` ## Not included - Undo / snapshot restore — requires backend changes, out of scope ## Blast radius - `ParentClickOutsideIdContext` touches `DropdownInternalContainer` (207 `<Dropdown>` usages). `parentClickOutsideId` is `undefined` everywhere outside the side panel → attribute not rendered → zero behavioral change for existing consumers. --------- Co-authored-by: Samuel Arbibe <samuelarbibe@Samuels-MacBook-Pro.local> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> Co-authored-by: Félix Malfait <felix.malfait@gmail.com> |
||
|
|
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) |
||
|
|
fc9723949b |
Fix AI chat re-renders and refactored code (#18585)
This PR: - Breaks useAgentChatData into focused effect components (streaming, fetch, init, auto-scroll, diff sync) - Splits message list into non-last (stable) + last (streaming/error) to prevent full re-renders on each stream chunk - Adds scroll-to-bottom button and MutationObserver-based auto-scroll on thread switch - Lifts loading state from context to atoms - Adds areEqual to selector factories We could improve further but this sets up a robust architecture for further refactoring. ## Messages flow The flow of messages loading and streaming is now more solid. Everything goes out from `AgentChatAiSdkStreamEffect`, whether loaded from the DB or streaming directly, and every consumers is using only one atom `agentChatMessagesComponentFamilyState` ## Data sync effect with callbacks new hook See `packages/twenty-front/src/modules/apollo/hooks/useQueryWithCallbacks.ts` which allows to fix Apollo v4 migration leftovers and is an implementation of the pattern we talked about with @charlesBochet We could refine this pattern in another PR. # Before https://github.com/user-attachments/assets/84e7a96f-6790-405d-8a73-2dacbf783be5 # After https://github.com/user-attachments/assets/4c692e3a-2413-4513-abcc-44d0da311203 Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
fe4512f80c |
Add record page layout tabs editing (#18702)
- Fix duplication of tabs in dashboards that didn't open the duplicated tab in the side panel: replaced the logic that used Math.round with an algorithm that switches the positions of the elements. We will keep relying on integers, but it will work well in all cases. - Make dnd work with record page layouts, where there is a pinned tab - Make tab movements work with pinned tab, too - Allow user to set a tab as the pinned tab - Make backend changes to be able to save tabs with `layoutMode` https://github.com/user-attachments/assets/ce1130fa-71df-49ba-ba2f-6f971e15dd49 --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
9f7c29bce8 |
refactor(twenty-front): unify Favorites and Workspace navigation menu item code (#18697)
## Summary - Consolidate duplicated Favorites and Workspace navigation menu item frontend code into a single unified codebase within `navigation-menu-item/` - Move all DnD-related code from `navigation/` module into `navigation-menu-item/display/dnd/`, renaming `workspaceDndKit*` files to `navigationMenuItemDndKit*` - Unify duplicated components (DnD providers, DnD hooks, folder components, orphan items, section shell) using a `NavigationSections` enum to parameterize section-specific behavior - Rename residual workspace-prefixed symbols (`WorkspaceDndKitSortableItem`, `WorkspaceDndKitDroppableSlot`, `useWorkspaceSectionItems`, etc.) to `navigationMenuItem`-prefixed equivalents - Clean `navigation/` module to only contain app-level concerns (drawer layout, settings, routing) ### Key changes | Before (duplicated) | After (unified) | |---|---| | `FavoritesDndKitProvider` + `WorkspaceDndKitProvider` | `NavigationMenuItemDndKitProvider` with `section` prop | | `useFavoritesDndKit` + `useWorkspaceDndKit` | `useNavigationMenuItemDndKit(section)` | | `FavoritesFolderItem` + `WorkspaceNavigationMenuItemsFolder` | `NavigationMenuItemFolder` with `section` prop | | `FavoritesOrphanItems` | `NavigationMenuItemOrphanItems` with `section` prop | | Separate section shells | Shared `NavigationMenuItemSection` with thin wrappers | | `WorkspaceDndKitSortableItem` | `NavigationMenuItemSortableItem` | | `WorkspaceDndKitDroppableSlot` | `NavigationMenuItemDroppableSlot` | | `useWorkspaceSectionItems` | `useNavigationMenuItemSectionItems` | | `useWorkspaceFolderOpenState` | `useNavigationMenuItemFolderOpenState` | Net result: **~1650 lines deleted** across 51 files. ## Test plan - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx lint twenty-front` passes (0 errors) - [x] `npx nx test twenty-front` passes (763 suites, 4467 tests) - [ ] Smoke test: Favorites section renders, DnD reorder works, folder create/rename/delete works - [ ] Smoke test: Workspace section renders, edit mode works, DnD reorder works - [ ] Smoke test: Add-to-navigation from side panel works for both sections Made with [Cursor](https://cursor.com) |
||
|
|
f1dfcfd163 |
refactor(twenty-front): reorganize NavigationMenuItem module into common/display/edit subfolders (#18691)
## Summary - Reorganizes the `navigation-menu-item` frontend module from a flat structure into `common/`, `display/`, and `edit/` subfolders with type-specific subdirectories (`link/`, `folder/`, `object/`, `view/`, `record/`) - Every file is now in a leaf folder describing its type: `components/`, `hooks/`, `utils/`, `types/`, or `constants/` - Moves 14 NavigationMenuItem-related components out of `object-metadata/` and `side-panel/pages/` into the `navigation-menu-item` module where they belong - Creates type-specific display utility functions (e.g., `getLinkNavigationMenuItemLabel`, `getObjectNavigationMenuItemComputedLink`) to replace generic switch-based functions - Unifies the Favorites section drag-and-drop from `@hello-pangea/dnd` to `@dnd-kit/react`, matching the Workspace section's DnD library - Renames Favorites section components from `CurrentWorkspaceMember*` to `Favorites*` for clarity - Deletes unused `FavoritesDragDropProviderContent` and `NavbarDragProvider` ## Test plan - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx lint:diff-with-main twenty-front` passes (0 warnings, 0 errors) - [x] `npx nx test twenty-front` passes (763 suites, 4467 tests) - [ ] Verify favorites drag-and-drop still works in the UI (reorder items, move between folders) - [ ] Verify workspace edit mode drag-and-drop still works - [ ] Verify "add to navigation" drag from command menu/side panel still works |
||
|
|
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`. |