## Summary
- **Remove all "core" prefixes** from the views system — the
metadata-based storage migration is complete, so `CoreView`,
`coreViewsSelector`, `getCoreViews`, etc. are now just `View`,
`viewsSelector`, `getViews`
- **Eliminate the entire converter layer** (15 files, ~850 lines
deleted) — `convertCoreViewToView` and all sub-converters were either
no-ops or trivially adding `__typename` / mapping identical enum values.
Local enums now re-export from generated GraphQL types directly (single
source of truth)
- **Unify `View` and `ViewWithRelations`** into one type —
`ViewWithRelations` is now a type alias for `View`, selectors return
data directly without conversion
### Backend
- Rename `@ObjectType('CoreView')` → `@ObjectType('View')` (and all
sub-entities)
- Rename resolver methods: `getCoreViews` → `getViews`, `createCoreView`
→ `createView`, etc.
- Rename `FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION` →
`FIND_ALL_VIEWS_GRAPHQL_OPERATION`
### Frontend
- Delete 15 converter files (`convertGqlView*ToView*`,
`convertView*ToGql`, `convertViewWithRelationsToView`)
- Re-export `ViewType`, `ViewKey`, `ViewFilterGroupLogicalOperator` from
generated enums (no more duplicate enum definitions with different
casing)
- Replace `ViewOpenRecordInType` with `ViewOpenRecordIn` from generated
- Remove `__typename` from all local view sub-types
- Remove unused `variant` from `ViewFilter`, make `displayValue` and
`definition` optional
- Rename ~45 GraphQL query/mutation files and all selectors to drop
"core" prefix
- Delete unused `viewsWithRelationsSelector`
## Summary
Consolidates `objectMetadataItems` onto the metadata store as the
**single source of truth**, replacing the previous dual-store approach
(separate `objectMetadataItemsState` atom + untyped
`metadataStoreState`).
### Architecture: three-layer design
```
┌─────────────────────────────────────────────────────────┐
│ Store Layer (granular, typed) │
│ objectMetadataItems → FlatObjectMetadataItem[] │
│ fieldMetadataItems → FlatFieldMetadataItem[] │
│ indexMetadataItems → FlatIndexMetadataItem[] │
└────────────────┬────────────────────────────────────────┘
│ .current (never draft)
┌────────────────▼────────────────────────────────────────┐
│ Selectors (typed read-only) │
│ objectMetadataItemsSelector │
│ fieldMetadataItemsSelector │
│ indexMetadataItemsSelector │
│ metadataStoreStatusFamilySelector │
│ isSystemObjectByNameSingularFamilySelector (narrow) │
│ activeObjectNameSingularsSelector (narrow) │
└────────────────┬────────────────────────────────────────┘
│ joins objects + fields + indexes + permissions
┌────────────────▼────────────────────────────────────────┐
│ Joining Selector │
│ objectMetadataItemsWithFieldsSelector │
│ → produces full ObjectMetadataItem[] with │
│ readableFields / updatableFields from permissions │
│ → 12 existing selectors repointed here │
└─────────────────────────────────────────────────────────┘
```
### Key changes
- **Granular flat types** (`FlatObjectMetadataItem`,
`FlatFieldMetadataItem`, `FlatIndexMetadataItem`) — objects stored
without embedded fields/indexes, matching backend "Flat" naming
convention
- **Typed write API** — `updateDraft` is now generic via
`MetadataEntityTypeMap`, giving compile-time safety on what data shape
goes to each key
- **Write path refactored** — fetch → split into flat entities via
`splitObjectMetadataItemWithRelated` → write to metadata store directly.
No more dual-write through `objectMetadataItemsState`. Permissions
enrichment moved from write path into the joining selector.
- **SSE effects write directly** — `ObjectMetadataItemSSEEffect` and
`FieldMetadataSSEEffect` now patch the store from the SSE event payload
(create/update/delete) instead of triggering a full re-fetch
- **`objectMetadataItemsState` bridge** — converted from writable
`createAtomState` to read-only `createAtomSelector` that delegates to
the joining selector. All 100+ existing consumers continue to work
without code changes.
- **All selectors use Twenty state API** — `createAtomSelector` /
`createAtomFamilySelector` throughout, no raw `atom()`
- **Narrow selectors** for hot paths —
`isSystemObjectByNameSingularFamilySelector` and
`activeObjectNameSingularsSelector` read from flat objects only,
avoiding re-renders when fields/indexes/permissions change. Placed in
`object-metadata/states/` as higher-level business selectors.
- **Test helper** — `setTestObjectMetadataItemsInMetadataStore` for
tests that need to set up composite object metadata through the store
(clearly named as a testing utility)
### Naming conventions
- `ObjectMetadataItemWithRelated` — type for objects with embedded
fields/indexes (input to split utility)
- `FlatObjectMetadataItem` / `FlatFieldMetadataItem` /
`FlatIndexMetadataItem` — granular store types
- Selector names don't expose "Current" — that's an internal detail of
the metadata store API
### Future work
- Optimistic update API (`updateCurrentOptimistically` with rollback)
- Migrate remaining entities (views, pageLayouts, etc.) to the same
pattern
- Gradually remove `objectMetadataItemsState` bridge once all direct
imports are replaced
## Test plan
- [x] `npx nx typecheck twenty-front` passes
- [x] `npx nx lint:diff-with-main twenty-front` passes
- [ ] Verify app loads correctly with metadata from the store
- [ ] Verify SSE updates (object/field changes) propagate correctly
- [ ] Run existing test suites to confirm no regressions
## Summary
- Removes `RICH_TEXT` from the excluded/hidden field types in the
settings UI so users can create rich text fields on any object (not just
Note/Task)
- Creates a generic `RichTextFieldEditor` component that uses standard
`useUpdateOneRecord` for persistence, decoupled from the
Note/Task-specific `ActivityRichTextEditor`
- Updates the inline `RichTextFieldInput` and side panel to route to the
appropriate editor based on object type (activity editor for Note/Task,
generic editor for everything else)
## Details
### Tier 1 — Settings UI unlock
- Removed `RICH_TEXT` from `excludedFieldTypes` in
`SettingsObjectNewFieldSelect.tsx`
- Removed `RICH_TEXT` from `SettingsExcludedFieldType` type union
- Added `RICH_TEXT` to `previewableTypes` in
`SettingsDataModelFieldSettingsFormCard`
### Tier 2 — Generic inline editing
- New `RichTextFieldEditor` — a generic BlockNote editor that works for
any object using `useUpdateOneRecord` (no activity-specific coupling)
- `RichTextFieldInput` now branches: `ActivityRichTextEditor` for
Note/Task, `RichTextFieldEditor` for all other objects
- Generalized side panel state (`viewableRichTextComponentState`) from
`activityId`/`activityObjectNameSingular` to
`recordId`/`objectNameSingular`/`fieldName`
- `useOpenRichTextInSidePanel` now accepts an optional `fieldName`
parameter
### Tier 3 — Verification
- Search: only `markdown` subfield is indexed (correct behavior)
- Filters: `RichTextFilter` GraphQL input type already exists
- Import/export: `markdown` subfield is already marked `isImportable:
true`
## Summary
- Renames the `FieldMetadataType` enum key from `RICH_TEXT_V2` to
`RICH_TEXT` across the entire codebase, while keeping the underlying
string value as `'RICH_TEXT_V2'` to maintain PostgreSQL database
compatibility
- Renames all related types, guards, hooks, components, and files from
`*RichTextV2*` / `*rich-text-v2*` to `*RichText*` / `*rich-text*` (e.g.
`FormRichTextV2FieldInput` → `FormRichTextFieldInput`,
`isFieldRichTextV2` → `isFieldRichText`)
- Updates generated files (GraphQL schema, SDK types) to use the new key
while preserving the `RICH_TEXT_V2` string value for DB/API layer
- Updates i18n locale files, test snapshots, and integration tests to
reflect the rename
## Context
The legacy `RICH_TEXT` (V1) field type was deprecated and migrated to
`TEXT` in a previous PR (#18623). With V1 gone, the `RICH_TEXT_V2`
naming is no longer necessary — `RICH_TEXT` is now the canonical name.
The DB enum value stays `'RICH_TEXT_V2'` to avoid confusion with the
just-deprecated V1 type and to prevent a database migration.
## Test plan
- [x] `twenty-server` typecheck passes
- [x] `twenty-front` typecheck passes (only pre-existing Apollo client
errors remain)
- [x] `twenty-server` lint passes
- [x] `twenty-front` lint passes
- [x] `twenty-shared` build passes
- [ ] CI passes
Made with [Cursor](https://cursor.com)
## Summary
- Removes the deprecated `RICH_TEXT` (V1) field metadata type from the
codebase entirely
- Adds a 1.20 upgrade command that migrates existing `RICH_TEXT` fields
to `TEXT` in `core.fieldMetadata`
- Cleans up ~70 files across `twenty-shared`, `twenty-server`,
`twenty-front`, `twenty-sdk`, and `twenty-zapier`
## Context
`RICH_TEXT` was a legacy field type that stored rich text as a single
`text` column. It was already **read-only** — writes threw errors
directing users to `RICH_TEXT_V2` instead. `RICH_TEXT_V2` is the current
approach: a composite type with `blocknote` (editor JSON) and `markdown`
subfields. Keeping the deprecated type added maintenance burden without
any value.
Since the underlying database column type for `RICH_TEXT` was already
`text` (same as `TEXT`), the migration only needs to update the metadata
— no data migration or column changes required.
## Changes
### Upgrade command (new)
- `1-20-migrate-rich-text-to-text.command.ts` — runs `UPDATE
core."fieldMetadata" SET "type" = 'TEXT' WHERE "type" = 'RICH_TEXT'` per
workspace, with cache invalidation
### Enum & shared types
- Removed `RICH_TEXT` from `FieldMetadataType` enum
- Removed from `FieldMetadataDefaultValueMapping`,
`isFieldMetadataTextKind`
### Server (~30 files)
- Removed from type mapper (scalar, filter, order-by), data processors,
input transformer, filter operators, zod schemas, column type mapping,
searchable fields, RLS matching, OpenAPI schema, fake value generators
- Removed from field creation flow and field metadata type validator
- Updated dev seeder Pet `bio` field to `TEXT`
- Cleaned up mocks, snapshots, integration tests
### Frontend (~25 files)
- Deleted: `RichTextFieldDisplay`, `isFieldRichText`,
`isFieldRichTextValue`, `useRichTextFieldDisplay`
- Removed from `FieldDisplay`, `usePersistField`, `isFieldValueEmpty`,
`isRecordMatchingFilter`, `generateEmptyFieldValue`,
`isFieldCellSupported`, spreadsheet import, workflow fake values
- Removed from settings types, field type configs, and field creation
exclusion list
- Updated tests, mocks, and stories
### SDK & Zapier
- Removed from generated GraphQL schema and TypeScript types
- Removed from Zapier `computeInputFields`
## Summary
This PR upgrades Apollo Client from v3.10.0 to v4 and refactors error
handling patterns across the codebase to use a new centralized
`useSnackBarOnQueryError` hook.
## Key Changes
- **Dependency Update**: Upgraded `@apollo/client` from `^3.10.0` to
`^3.11.0` in root package.json
- **New Hook**: Added `useSnackBarOnQueryError` hook for centralized
Apollo query error handling with snack bar notifications
- **Error Handling Refactor**: Updated 100+ files to use the new error
handling pattern:
- Removed direct `ApolloError` imports where no longer needed
- Replaced manual error handling logic with `useSnackBarOnQueryError`
hook
- Simplified error handling in hooks and components across multiple
modules
- **GraphQL Codegen**: Updated codegen configuration files to work with
Apollo Client v3.11.0
- **Type Definitions**: Added TypeScript declaration file for
`apollo-upload-client` module
- **Test Updates**: Updated test files to reflect new error handling
patterns
## Notable Implementation Details
- The new `useSnackBarOnQueryError` hook provides a consistent way to
handle Apollo query errors with automatic snack bar notifications
- Changes span across multiple feature areas: auth, object records,
settings, workflows, billing, and more
- All changes maintain backward compatibility while improving code
maintainability and reducing duplication
- Jest configuration updated to work with the new Apollo Client version
https://claude.ai/code/session_019WGZ6Rd7sEHuBg9sTrXRqJ
---------
Co-authored-by: Claude <noreply@anthropic.com>
1. **Creating a new dashboard crashes with "Tab not found"** and widgets
can't be added after the crash is prevented.
**Root cause:** `initializePageLayout` wrapped both the persisted and
draft state updates behind an `isDeeplyEqual` guard. After navigation,
`resetPageLayoutEditMode` resets the draft atom to its default but
leaves the persisted atom untouched. On re-initialization,
`isDeeplyEqual` returns true (persisted unchanged), so the draft is
never repopulated. But edit mode is still activated.
**Fix**: Move the draft store.set outside the isDeeplyEqual guard so
it's always set on initialization. Also add a defensive check in
`PageLayoutRendererContent` to prevent the crash when activeTabId
doesn't match available tabs.
https://github.com/user-attachments/assets/bcd69866-63eb-4e5e-a1bb-655e71ba6dc5
2. **Permission role page design broken**
Before
<img width="573" height="1130" alt="role-page-broken"
src="https://github.com/user-attachments/assets/09f60fd2-ef08-4133-bb28-034b15579481"
/>
After
<img width="573" height="266" alt="Capture d’écran 2026-03-11 à 14 25
55"
src="https://github.com/user-attachments/assets/c34f9993-51e1-4108-a7e6-f434f558edfd"
/>
actions are being renamed to command menu item, they will be migrated to
server and will be served as headless front components
---------
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
## Summary
- Re-enable one lint rule that was temporarily disabled during the
ESLint-to-Oxlint migration:
- **`twenty/sort-css-properties-alphabetically`** in twenty-front — 578
violations auto-fixed across 390 files
- Document why **`typescript/consistent-type-imports`** cannot be
auto-fixed in twenty-server: NestJS relies on `emitDecoratorMetadata`
for DI, so converting constructor parameter imports to `import type`
erases them at compile time and breaks dependency injection at runtime
- Right-size CI runners, reducing 8-core usage from 18 jobs to 3:
| Change | Jobs | Rationale |
|--------|------|-----------|
| **Keep 8-core** | `ci-merge-queue/e2e-test`,
`ci-front/front-sb-build`, `ci-front/front-build` | Heavy builds needing
max CPU + memory (10GB NODE_OPTIONS, full Storybook webpack bundling) |
| **8-core → 4-core** | `ci-server` (build, lint-typecheck, validation,
test, integration-test), `ci-front/front-sb-test`,
`ci-zapier/server-setup`, `ci-sdk/sdk-e2e-test` | Already sharded into
10-12 parallel instances, I/O-bound (DB/Redis), or moderate single
builds |
| **8-core → 2-core** | `ci-emails/emails-test` | Trivially lightweight
(build + curl health check) |
| **Removed** | `ci-front/front-chromatic-deployment` | Dead code —
permanently disabled with `if: false` |
- Fix merge queue CI issues:
- **Concurrency**: Use `merge_group.base_ref` instead of unique merge
group ref so new queue entries cancel previous runs
- **Required status checks**: Add `merge_group` trigger to all 6
required CI workflows (front, server, shared, website, docker-compose,
sdk) with `changed-files-check` auto-skipped for merge_group events —
status check jobs auto-pass without re-running full CI
- **Build caching**: Add Nx build cache restore/save to E2E test job
with fallback to `main` branch cache for faster frontend and server
builds
## Test plan
- [ ] CI passes on this PR (verifies lint rule auto-fix works)
- [ ] Verify 4-core runner jobs complete within their 30-minute timeouts
- [ ] Verify merge queue status checks auto-pass (ci-front-status-check,
ci-server-status-check, etc.)
- [ ] Verify merge queue E2E concurrency cancels previous runs when a
new PR enters the queue