From cf705659769b26ba64860b5dbc364dad519eaee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Tue, 9 Jun 2026 16:33:58 +0200 Subject: [PATCH] feat(twenty-server): allow shouldHideEmptyGroups in app view manifest (#21370) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context The view **Hide empty groups** setting (`shouldHideEmptyGroups`) can be toggled in the UI, is persisted on the `View` entity, exposed in the `CreateView`/`UpdateView` GraphQL inputs, and tracked by the flat-view sync machinery — but it could **not** be set from an app's view manifest. Root cause: the field postdates the manifest plumbing (added in #16385, Dec 2025). Two spots were never updated to thread it through: - `ViewManifest` didn't declare the field. - `fromViewManifestToUniversalFlatView` hardcoded `shouldHideEmptyGroups: false`. Ref: twentyhq/core-team-issues#414 ## Changes - Add optional `shouldHideEmptyGroups?: boolean` to `ViewManifest`. - Read it in the converter (`?? false`), mirroring the existing `isCompact` handling. - Cover it in the converter unit test (default + explicit value). No migration or schema change — the column already exists, and downstream sync (`FLAT_VIEW_EDITABLE_PROPERTIES` + the universal-flat compare type) already handles it. ## Test - `npx jest from-view-manifest-to-universal-flat-view` → 5 passed - `tsgo -p tsconfig.json` (twenty-server) → no new errors - oxlint + oxfmt clean --- .../from-view-manifest-to-universal-flat-view.util.spec.ts | 3 +++ .../from-view-manifest-to-universal-flat-view.util.ts | 2 +- packages/twenty-shared/src/application/viewManifestType.ts | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/__tests__/from-view-manifest-to-universal-flat-view.util.spec.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/__tests__/from-view-manifest-to-universal-flat-view.util.spec.ts index 9f9951bb0f..be2f9c8f19 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/__tests__/from-view-manifest-to-universal-flat-view.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/__tests__/from-view-manifest-to-universal-flat-view.util.spec.ts @@ -35,6 +35,7 @@ describe('fromViewManifestToUniversalFlatView', () => { expect(result.icon).toBe('IconList'); expect(result.position).toBe(0); expect(result.isCompact).toBe(false); + expect(result.shouldHideEmptyGroups).toBe(false); expect(result.isCustom).toBe(true); expect(result.visibility).toBe(ViewVisibility.WORKSPACE); expect(result.openRecordIn).toBe(ViewOpenRecordIn.SIDE_PANEL); @@ -53,6 +54,7 @@ describe('fromViewManifestToUniversalFlatView', () => { icon: 'IconLayoutKanban', position: 3, isCompact: true, + shouldHideEmptyGroups: true, visibility: ViewVisibility.UNLISTED, openRecordIn: ViewOpenRecordIn.RECORD_PAGE, }, @@ -64,6 +66,7 @@ describe('fromViewManifestToUniversalFlatView', () => { expect(result.icon).toBe('IconLayoutKanban'); expect(result.position).toBe(3); expect(result.isCompact).toBe(true); + expect(result.shouldHideEmptyGroups).toBe(true); expect(result.visibility).toBe(ViewVisibility.UNLISTED); expect(result.openRecordIn).toBe(ViewOpenRecordIn.RECORD_PAGE); }); diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts index 0c50b797ce..e503ccc828 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-view-manifest-to-universal-flat-view.util.ts @@ -38,7 +38,7 @@ export const fromViewManifestToUniversalFlatView = ({ viewManifest.calendarFieldMetadataUniversalIdentifier ?? null, mainGroupByFieldMetadataUniversalIdentifier: viewManifest.mainGroupByFieldMetadataUniversalIdentifier ?? null, - shouldHideEmptyGroups: false, + shouldHideEmptyGroups: viewManifest.shouldHideEmptyGroups ?? false, anyFieldFilterValue: null, createdByUserWorkspaceId: null, viewFieldUniversalIdentifiers: [], diff --git a/packages/twenty-shared/src/application/viewManifestType.ts b/packages/twenty-shared/src/application/viewManifestType.ts index 103c07dab3..0e3db1cd65 100644 --- a/packages/twenty-shared/src/application/viewManifestType.ts +++ b/packages/twenty-shared/src/application/viewManifestType.ts @@ -74,6 +74,7 @@ export type ViewManifest = SyncableEntityOptions & { visibility?: ViewVisibility; openRecordIn?: ViewOpenRecordIn; mainGroupByFieldMetadataUniversalIdentifier?: string; + shouldHideEmptyGroups?: boolean; kanbanAggregateOperation?: AggregateOperations; kanbanAggregateOperationFieldMetadataUniversalIdentifier?: string; calendarLayout?: ViewCalendarLayout;