From 80fb91c033dfd367f17ba58e2095526faa016e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Sat, 8 Aug 2026 13:04:08 +0200 Subject: [PATCH] Add List View lab icon and make lab feature flag icons mandatory (#23930) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Context The List View toggle in Settings → Lab rendered without an icon: the front end kept a hardcoded `Partial>` map, so nothing caught a public feature flag added without an icon. ## What this PR does Moves the icon into the public feature flag metadata, next to `label` and `description`, as a **required** field — so a lab flag now registers in one place and can't be declared without an icon (server typecheck fails otherwise): - `twenty-server`: `FeatureFlagMetadata` gains required `icon: string`; each `PUBLIC_FEATURE_FLAGS` entry declares its icon (`IconList` for List View — the icon already used for the List view type). `PublicFeatureFlagMetadata` GraphQL entity exposes it. - `twenty-front`: `SettingsLabContent` renders `getIcon(flag.metadata.icon)` via `useIcons`, the same metadata-driven icon pattern used across the app; the hardcoded icon map is deleted. - `twenty-ui`: registers `IconCalendarWeek` in `AllIcons` — it was importable but not resolvable by name through `useIcons`, so it would have silently fallen back to the default icon. - Generated artifacts (`twenty-front/src/generated-metadata`, `twenty-client-sdk/src/metadata/generated`) updated for the new field. ## Verification - `nx run-many -t typecheck,lint -p twenty-front twenty-server twenty-shared twenty-client-sdk twenty-ui` passes (oxlint + oxfmt clean). - Negative case: removing `icon` from a `PUBLIC_FEATURE_FLAGS` entry fails `twenty-server:typecheck` with `TS2741: Property 'icon' is missing … but required in type 'FeatureFlagMetadata'`. --- .../src/metadata/generated/schema.graphql | 1 + .../src/metadata/generated/schema.ts | 2 ++ .../src/metadata/generated/types.ts | 3 +++ .../src/generated-metadata/graphql.ts | 1 + .../lab/components/SettingsLabContent.tsx | 20 +++++-------------- .../client-config/client-config.entity.ts | 3 +++ .../constants/public-feature-flag.const.ts | 5 +++++ .../src/icon/providers/internal/AllIcons.ts | 2 ++ 8 files changed, 22 insertions(+), 15 deletions(-) diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 4853cdf37e..94c6225a78 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -1968,6 +1968,7 @@ type ApiConfig { type PublicFeatureFlagMetadata { label: String! description: String! + icon: String! imagePath: String } diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index cd418a4803..cae780b6ff 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -1600,6 +1600,7 @@ export interface ApiConfig { export interface PublicFeatureFlagMetadata { label: Scalars['String'] description: Scalars['String'] + icon: Scalars['String'] imagePath?: Scalars['String'] __typename: 'PublicFeatureFlagMetadata' } @@ -4867,6 +4868,7 @@ export interface ApiConfigGenqlSelection{ export interface PublicFeatureFlagMetadataGenqlSelection{ label?: boolean | number description?: boolean | number + icon?: boolean | number imagePath?: boolean | number __typename?: boolean | number __scalar?: boolean | number diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index adcbfa268f..45d10617eb 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -3769,6 +3769,9 @@ export default { "description": [ 1 ], + "icon": [ + 1 + ], "imagePath": [ 1 ], diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 073c484220..a4fd9b8b1a 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -4502,6 +4502,7 @@ export type PublicFeatureFlag = { export type PublicFeatureFlagMetadata = { __typename?: 'PublicFeatureFlagMetadata'; description: Scalars['String']['output']; + icon: Scalars['String']['output']; imagePath?: Maybe; label: Scalars['String']['output']; }; diff --git a/packages/twenty-front/src/modules/settings/lab/components/SettingsLabContent.tsx b/packages/twenty-front/src/modules/settings/lab/components/SettingsLabContent.tsx index 709ed44531..517fc5367e 100644 --- a/packages/twenty-front/src/modules/settings/lab/components/SettingsLabContent.tsx +++ b/packages/twenty-front/src/modules/settings/lab/components/SettingsLabContent.tsx @@ -5,15 +5,10 @@ import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomState import { styled } from '@linaria/react'; import { isNonEmptyString } from '@sniptt/guards'; import { useState } from 'react'; -import { - IconCalendarWeek, - IconRelationManyToMany, - IconSparkles, - type IconComponent, -} from 'twenty-ui/icon'; +import { useIcons } from 'twenty-ui/icon'; import { Card } from 'twenty-ui/surfaces'; import { themeCssVariables } from 'twenty-ui/theme-constants'; -import { FeatureFlagKey } from '~/generated-metadata/graphql'; +import { type FeatureFlagKey } from '~/generated-metadata/graphql'; const StyledCardGrid = styled.div` display: grid; @@ -29,14 +24,9 @@ const StyledImage = styled.img` width: 100%; `; -const labFeatureFlagIcons: Partial> = { - [FeatureFlagKey.IS_CALENDAR_WEEK_VIEW_ENABLED]: IconCalendarWeek, - [FeatureFlagKey.IS_JUNCTION_RELATIONS_ENABLED]: IconRelationManyToMany, - [FeatureFlagKey.IS_SETTINGS_DISCOVERY_HERO_ENABLED]: IconSparkles, -}; - export const SettingsLabContent = () => { const currentWorkspace = useAtomStateValue(currentWorkspaceState); + const { getIcon } = useIcons(); const { labPublicFeatureFlags, handleLabPublicFeatureFlagUpdate } = useLabPublicFeatureFlags(); const [hasImageLoadingError, setHasImageLoadingError] = useState< @@ -75,7 +65,7 @@ export const SettingsLabContent = () => { /> )} { {labPublicFeatureFlagsWithoutImage.map((flag, index) => ( String) description: string; + @Field(() => String) + icon: string; + @Field(() => String, { nullable: true }) imagePath?: string; } diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts index f081eac35b..73e14f61a5 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/constants/public-feature-flag.const.ts @@ -3,6 +3,7 @@ import { FeatureFlagKey } from 'twenty-shared/types'; type FeatureFlagMetadata = { label: string; description: string; + icon: string; imagePath?: string; }; @@ -18,6 +19,7 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [ label: 'Calendar Day and Week Views', description: 'Display calendar records in daily or weekly layouts with optional end dates', + icon: 'IconCalendarWeek', }, }, { @@ -26,6 +28,7 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [ label: 'Junction Relations', description: 'Enable many-to-many relations through junction tables configuration', + icon: 'IconRelationManyToMany', }, }, { @@ -34,6 +37,7 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [ label: 'List View', description: 'Display records in a list layout with collapsible groups and inline fields', + icon: 'IconList', }, }, { @@ -42,6 +46,7 @@ export const PUBLIC_FEATURE_FLAGS: PublicFeatureFlag[] = [ label: 'Settings Discovery Hero', description: 'Show the per-page hero illustration + video walkthrough modal on settings pages', + icon: 'IconSparkles', }, }, ...(process.env.CLOUDFLARE_API_KEY diff --git a/packages/twenty-ui/src/icon/providers/internal/AllIcons.ts b/packages/twenty-ui/src/icon/providers/internal/AllIcons.ts index ae7791c37a..a4d486b9b8 100644 --- a/packages/twenty-ui/src/icon/providers/internal/AllIcons.ts +++ b/packages/twenty-ui/src/icon/providers/internal/AllIcons.ts @@ -965,6 +965,7 @@ import { IconCalendarStats, IconCalendarTime, IconCalendarUp, + IconCalendarWeek, IconCalendarX, IconCamera, IconCameraBolt, @@ -5149,6 +5150,7 @@ export const ALL_ICONS = { IconCalendarStats, IconCalendarTime, IconCalendarUp, + IconCalendarWeek, IconCalendarX, IconCamera, IconCameraBolt,