[Breaking change] fix: make pageLayout type field required (#22450)

fixes https://github.com/twentyhq/twenty/issues/22251


**Summary**
- Fixes #22251 — NavigationMenuItem with type PAGE_LAYOUT returns 404
"Off track" for custom standalone pages
- Makes type a required field in PageLayoutManifest instead of relying
on a fallback default to RECORD_PAGE
- Adds PageLayoutType enum to twenty-shared and exports it from the SDK
for app developers
- Adds build-time validation in definePageLayout to reject manifests
missing type
- Updates the CLI add command to prompt users to select a page layout
type interactively

**Root cause**
When definePageLayout was called without type, the manifest converter
defaulted to RECORD_PAGE. The frontend route guard at /page/:id then
rejected it (only STANDALONE_PAGE is allowed), producing a 404.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22450?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. -->
This commit is contained in:
Etienne
2026-07-02 15:40:04 +02:00
committed by GitHub
parent 7cf8b58f1b
commit 29e48e16ba
12 changed files with 87 additions and 16 deletions
@@ -5,11 +5,12 @@ describe('fromPageLayoutManifestToUniversalFlatPageLayout', () => {
const now = '2026-01-01T00:00:00.000Z';
const applicationUniversalIdentifier = 'app-uuid-1';
it('should convert a minimal page layout manifest', () => {
it('should convert a standalone page layout manifest', () => {
const result = fromPageLayoutManifestToUniversalFlatPageLayout({
pageLayoutManifest: {
universalIdentifier: 'pl-uuid-1',
name: 'My Page Layout',
type: PageLayoutType.STANDALONE_PAGE,
},
applicationUniversalIdentifier,
now,
@@ -20,7 +21,7 @@ describe('fromPageLayoutManifestToUniversalFlatPageLayout', () => {
applicationUniversalIdentifier,
);
expect(result.name).toBe('My Page Layout');
expect(result.type).toBe(PageLayoutType.RECORD_PAGE);
expect(result.type).toBe(PageLayoutType.STANDALONE_PAGE);
expect(result.objectMetadataUniversalIdentifier).toBeNull();
expect(
result.defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier,
@@ -28,10 +29,27 @@ describe('fromPageLayoutManifestToUniversalFlatPageLayout', () => {
expect(result.tabUniversalIdentifiers).toEqual([]);
});
it('should convert a fully specified page layout manifest', () => {
it('should convert a record page layout manifest', () => {
const result = fromPageLayoutManifestToUniversalFlatPageLayout({
pageLayoutManifest: {
universalIdentifier: 'pl-uuid-2',
name: 'Record Layout',
type: PageLayoutType.RECORD_PAGE,
objectUniversalIdentifier: 'obj-uuid-1',
},
applicationUniversalIdentifier,
now,
});
expect(result.name).toBe('Record Layout');
expect(result.type).toBe(PageLayoutType.RECORD_PAGE);
expect(result.objectMetadataUniversalIdentifier).toBe('obj-uuid-1');
});
it('should convert a fully specified page layout manifest', () => {
const result = fromPageLayoutManifestToUniversalFlatPageLayout({
pageLayoutManifest: {
universalIdentifier: 'pl-uuid-3',
name: 'Dashboard Layout',
type: PageLayoutType.DASHBOARD,
objectUniversalIdentifier: 'obj-uuid-1',
@@ -1,6 +1,6 @@
import { type PageLayoutManifest } from 'twenty-shared/application';
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { type PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
import { type UniversalFlatPageLayout } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-page-layout.type';
export const fromPageLayoutManifestToUniversalFlatPageLayout = ({
@@ -16,8 +16,7 @@ export const fromPageLayoutManifestToUniversalFlatPageLayout = ({
universalIdentifier: pageLayoutManifest.universalIdentifier,
applicationUniversalIdentifier,
name: pageLayoutManifest.name,
type:
(pageLayoutManifest.type as PageLayoutType) ?? PageLayoutType.RECORD_PAGE,
type: pageLayoutManifest.type as PageLayoutType,
objectMetadataUniversalIdentifier:
pageLayoutManifest.objectUniversalIdentifier ?? null,
defaultTabToFocusOnMobileAndSidePanelUniversalIdentifier: