Add List View lab icon and make lab feature flag icons mandatory (#23930)

## Context

The List View toggle in Settings → Lab rendered without an icon: the
front end kept a hardcoded `Partial<Record<FeatureFlagKey,
IconComponent>>` 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'`.
This commit is contained in:
Félix Malfait
2026-08-08 13:04:08 +02:00
committed by GitHub
parent 02a187d065
commit 80fb91c033
8 changed files with 22 additions and 15 deletions
@@ -1968,6 +1968,7 @@ type ApiConfig {
type PublicFeatureFlagMetadata {
label: String!
description: String!
icon: String!
imagePath: String
}
@@ -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
@@ -3769,6 +3769,9 @@ export default {
"description": [
1
],
"icon": [
1
],
"imagePath": [
1
],
@@ -4502,6 +4502,7 @@ export type PublicFeatureFlag = {
export type PublicFeatureFlagMetadata = {
__typename?: 'PublicFeatureFlagMetadata';
description: Scalars['String']['output'];
icon: Scalars['String']['output'];
imagePath?: Maybe<Scalars['String']['output']>;
label: Scalars['String']['output'];
};
@@ -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<Record<FeatureFlagKey, IconComponent>> = {
[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 = () => {
/>
)}
<SettingsOptionCardContentToggle
Icon={labFeatureFlagIcons[flag.key]}
Icon={getIcon(flag.metadata.icon)}
title={flag.metadata.label}
description={flag.metadata.description}
checked={flag.value}
@@ -93,7 +83,7 @@ export const SettingsLabContent = () => {
{labPublicFeatureFlagsWithoutImage.map((flag, index) => (
<SettingsOptionCardContentToggle
key={flag.key}
Icon={labFeatureFlagIcons[flag.key]}
Icon={getIcon(flag.metadata.icon)}
title={flag.metadata.label}
description={flag.metadata.description}
checked={flag.value}
@@ -226,6 +226,9 @@ export class PublicFeatureFlagMetadata {
@Field(() => String)
description: string;
@Field(() => String)
icon: string;
@Field(() => String, { nullable: true })
imagePath?: string;
}
@@ -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
@@ -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,