feat(applications): restore the application custom settings tab (#23256)

## Summary

Restores the application **custom settings tab** feature that was
removed in #22156. This reverts that removal so applications can again
expose a custom settings tab via a front component.

## Changes

- Restore the `SettingsApplicationCustomTab` component and its tab
entry/rendering in `SettingsApplicationDetails`.
- `ApplicationManifestMigrationService` syncs
`settingsCustomTabFrontComponent` from application manifests again
(`syncDefaultRoleAndSettingsCustomTab`), resolving the front component
from `settingsCustomTabFrontComponentUniversalIdentifier`.
- Remove the deprecation annotations added by #22156:
- `ApplicationDTO.settingsCustomTabFrontComponentId` (drop GraphQL
`@deprecated`)
-
`ApplicationManifest.settingsCustomTabFrontComponentUniversalIdentifier`
- the `settingsCustomTabFrontComponentId` column comment on
`ApplicationEntity`
- Regenerate the corresponding GraphQL schema/types to drop the
`@deprecated` reason.

The DB column was never dropped, so no schema migration is required.


---
_Generated by [Claude
Code](https://claude.ai/code/session_01A6aoLa5kZjba9C3uwo6nay)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23256?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. -->

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
martmull
2026-07-27 08:52:04 +02:00
committed by GitHub
parent a94f2443b3
commit 4f9fd6f674
27 changed files with 307 additions and 47 deletions
@@ -592,7 +592,7 @@ type Application {
canBeUninstalled: Boolean!
autoUpgrade: Boolean!
defaultRoleId: String
settingsCustomTabFrontComponentId: UUID @deprecated(reason: "Custom settings tabs are no longer supported. This field is ignored.")
settingsCustomTabFrontComponentId: UUID
defaultLogicFunctionRole: Role
agents: [Agent!]!
frontComponents: [FrontComponent!]!
@@ -405,7 +405,6 @@ export interface Application {
canBeUninstalled: Scalars['Boolean']
autoUpgrade: Scalars['Boolean']
defaultRoleId?: Scalars['String']
/** @deprecated Custom settings tabs are no longer supported. This field is ignored. */
settingsCustomTabFrontComponentId?: Scalars['UUID']
defaultLogicFunctionRole?: Role
agents: Agent[]
@@ -3531,7 +3530,6 @@ export interface ApplicationGenqlSelection{
canBeUninstalled?: boolean | number
autoUpgrade?: boolean | number
defaultRoleId?: boolean | number
/** @deprecated Custom settings tabs are no longer supported. This field is ignored. */
settingsCustomTabFrontComponentId?: boolean | number
defaultLogicFunctionRole?: RoleGenqlSelection
agents?: AgentGenqlSelection
@@ -36,6 +36,7 @@ Notes:
- Pre-install, post-install, and uninstall functions are detected automatically during the manifest build — you do not need to reference them in `defineApplication()`.
- Passing `defaultRoleUniversalIdentifier` explicitly is still supported for backward compatibility, but is deprecated in favor of `defineApplicationRole()`.
- `serverVariables` are instance-scoped configuration and secrets (e.g. API keys). Unlike `applicationVariables`, they declare no value in the manifest — the workspace operator fills them in from the app's settings, and they are injected into logic functions only once set.
- To render a custom configuration UI inside the app's **Settings** tab (in place of the default variable configuration section), declare a front component with [`defineSettingsFrontComponent()`](/developers/extend/apps/layout/front-components#custom-settings-component) in its own file. Only one is allowed per app. System-managed sections (auto-upgrade, App URL, connections) always remain visible.
## Variable types
@@ -8,15 +8,17 @@ Front components are React components that render directly inside Twenty's UI. T
## Where front components can be used
Front components can render in two locations within Twenty:
Front components can render in three locations within Twenty:
- **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu.
- **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside [page layouts](/developers/extend/apps/layout/page-layouts). When configuring a dashboard or a record page layout, users can add a front component widget.
- **App settings** — Defined with [`defineSettingsFrontComponent()`](#custom-settings-component), the front component renders as a section inside the app's **Settings** tab, in place of the default variable configuration UI.
A front component on its own isn't reachable from the UI — you need to *surface* it. The two ways to do that are:
A front component on its own isn't reachable from the UI — you need to *surface* it. The three ways to do that are:
- **Pair it with a [command menu item](/developers/extend/apps/layout/command-menu-items)** — registers it in the command menu (Cmd+K) and, optionally, as a pinned quick-action.
- **Embed it as a widget in a [page layout](/developers/extend/apps/layout/page-layouts)** — places it on a record's detail page or dashboard.
- **Define it with [`defineSettingsFrontComponent()`](#custom-settings-component)** — renders it as a section inside the app's **Settings** tab, in place of the default variable configuration UI.
## Basic example
@@ -77,6 +79,34 @@ Click it to render the component inline.
Beyond commands, you can embed a front component directly into a record page by adding it as a widget in a **page layout**. See [Page Layouts](/developers/extend/apps/layout/page-layouts) for details.
## Custom settings component
To replace the auto-generated variable configuration UI in your app's **Settings** tab with your own component, define it with `defineSettingsFrontComponent` instead of `defineFrontComponent`. It takes the same [configuration fields](#configuration-fields) (except `isHeadless`, which is not accepted since a settings component always renders visible UI) and additionally marks the component as the app's settings UI.
The component renders as a section **inside** the Settings tab, not as a replacement for the whole tab. Twenty's system-managed sections — auto-upgrade, App URL, and connections — always render above it and cannot be overridden by the app.
```tsx src/front-components/app-settings.tsx
import { defineSettingsFrontComponent } from 'twenty-sdk/define';
const AppSettings = () => {
return (
<div style={{ padding: '20px' }}>
<h2>My app settings</h2>
{/* render your own configuration UI here */}
</div>
);
};
export default defineSettingsFrontComponent({
universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'app-settings',
description: "Custom UI for the app's Settings tab",
component: AppSettings,
});
```
Only one settings front component is allowed per app; declaring more than one fails the build. When present, the app's **Settings** tab renders this component in place of the default variable configuration UI.
## Headless vs non-headless
Front components come in two rendering modes controlled by the `isHeadless` option:
@@ -51,6 +51,7 @@ A Twenty app's **layout layer** is everything the user sees: where the app surfa
| **Record list** | A saved configuration for an object — visible columns, order, filters, groups | `defineView` |
| **Record detail page** | The tabs and widgets on a record page (your own object's, or a standard one) | `definePageLayout`, `definePageLayoutTab` |
| **Inside any of the above** | A custom React widget — buttons, forms, dashboards, integrations | `defineFrontComponent` |
| **App settings** | A custom configuration section inside the app's Settings tab, in place of the default variables UI | `defineSettingsFrontComponent` |
| **Command menu (Cmd+K)** | A pinned quick action or hidden command | `defineCommandMenuItem` |
Front components run inside an isolated Web Worker using Remote DOM — they render *natively* in the page (not inside an iframe), but cannot reach the host page or DOM directly. Communication with Twenty happens through a message-passing host API.
@@ -315,7 +315,6 @@ export type Application = {
objects: Array<Object>;
packageJsonChecksum?: Maybe<Scalars['String']['output']>;
packageJsonFileId?: Maybe<Scalars['UUID']['output']>;
/** @deprecated Custom settings tabs are no longer supported. This field is ignored. */
settingsCustomTabFrontComponentId?: Maybe<Scalars['UUID']['output']>;
universalIdentifier: Scalars['String']['output'];
version?: Maybe<Scalars['String']['output']>;
@@ -252,10 +252,14 @@ export const SettingsApplicationDetails = () => {
const hasHttpTriggeredFunctions =
applicationHasHttpTriggeredFunctions(application);
const canShowFunctionDomain = hasHttpTriggeredFunctions;
const hasSettingsFrontComponent = isDefined(
application?.settingsCustomTabFrontComponentId,
);
const hasNothingToConfigure =
!hasVariables &&
!hasConnectionProviders &&
!canShowFunctionDomain &&
!hasSettingsFrontComponent &&
!isUpgradableApplicationSourceType(sourceType);
return {
@@ -0,0 +1,38 @@
import { styled } from '@linaria/react';
import { Suspense, lazy } from 'react';
import { Section } from 'twenty-ui/layout';
import { FrontComponentSkeletonLoader } from '@/front-components/components/FrontComponentSkeletonLoader';
const FrontComponentRenderer = lazy(() =>
import('@/front-components/components/FrontComponentRenderer').then(
(module) => ({ default: module.FrontComponentRenderer }),
),
);
const StyledRendererContainer = styled.div`
display: flex;
min-height: 400px;
width: 100%;
`;
type SettingsApplicationCustomSettingsSectionProps = {
frontComponentId: string;
};
export const SettingsApplicationCustomSettingsSection = ({
frontComponentId,
}: SettingsApplicationCustomSettingsSectionProps) => {
return (
<Section>
<StyledRendererContainer>
<Suspense fallback={<FrontComponentSkeletonLoader />}>
<FrontComponentRenderer
frontComponentId={frontComponentId}
loadingFallback={<FrontComponentSkeletonLoader />}
/>
</Suspense>
</StyledRendererContainer>
</Section>
);
};
@@ -1,6 +1,8 @@
import { isDefined } from 'twenty-shared/utils';
import { type Application } from '~/generated-metadata/graphql';
import { useUpdateOneApplicationVariable } from '~/pages/settings/applications/hooks/useUpdateOneApplicationVariable';
import { SettingsApplicationConnectionsSection } from '~/pages/settings/applications/tabs/SettingsApplicationConnectionsSection';
import { SettingsApplicationCustomSettingsSection } from '~/pages/settings/applications/tabs/SettingsApplicationCustomSettingsSection';
import { SettingsApplicationDetailEnvironmentVariablesTable } from '~/pages/settings/applications/tabs/SettingsApplicationDetailEnvironmentVariablesTable';
import { SettingsApplicationFunctionDomainSection } from '~/pages/settings/applications/tabs/SettingsApplicationFunctionDomainSection';
import { SettingsApplicationGeneralSection } from '~/pages/settings/applications/tabs/SettingsApplicationGeneralSection';
@@ -19,6 +21,7 @@ export const SettingsApplicationDetailSettingsTab = ({
| 'autoUpgrade'
| 'applicationRegistration'
| 'logicFunctions'
| 'settingsCustomTabFrontComponentId'
>;
}) => {
const { updateOneApplicationVariable } = useUpdateOneApplicationVariable();
@@ -34,6 +37,9 @@ export const SettingsApplicationDetailSettingsTab = ({
application?.applicationRegistration?.sourceType,
);
const settingsFrontComponentId =
application?.settingsCustomTabFrontComponentId;
return (
<>
{isUpgradable && application?.id && (
@@ -50,18 +56,24 @@ export const SettingsApplicationDetailSettingsTab = ({
{application?.id && (
<SettingsApplicationConnectionsSection applicationId={application.id} />
)}
<SettingsApplicationDetailEnvironmentVariablesTable
envVariables={envVariables}
onUpdate={({ key, value }) =>
application?.id
? updateOneApplicationVariable({
key,
value,
applicationId: application.id,
})
: null
}
/>
{isDefined(settingsFrontComponentId) ? (
<SettingsApplicationCustomSettingsSection
frontComponentId={settingsFrontComponentId}
/>
) : (
<SettingsApplicationDetailEnvironmentVariablesTable
envVariables={envVariables}
onUpdate={({ key, value }) =>
application?.id
? updateOneApplicationVariable({
key,
value,
applicationId: application.id,
})
: null
}
/>
)}
</>
);
};
@@ -84,6 +84,7 @@ exports[`stub-twenty-sdk-define plugin > matches the recorded export partition 1
"definePostInstallLogicFunction",
"definePreInstallLogicFunction",
"defineRole",
"defineSettingsFrontComponent",
"defineSkill",
"defineUninstallLogicFunction",
"defineView",
@@ -227,6 +227,32 @@ describe('manifestValidate', () => {
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});
it('should not flag a front component referenced via settingsFrontComponent as a duplicate', () => {
const frontComponentId = '550e8400-e29b-41d4-a716-446655440050';
const frontComponent = {
universalIdentifier: frontComponentId,
name: 'app-settings',
componentName: 'AppSettings',
sourceComponentPath: 'src/front-components/app-settings.tsx',
builtComponentPath: 'dist/app-settings.mjs',
builtComponentChecksum: '00000000-0000-4000-8000-000000000000',
isHeadless: false,
} as unknown as Manifest['frontComponents'][number];
const result = manifestValidate({
...validManifest,
application: {
...validApplication,
settingsFrontComponent: { universalIdentifier: frontComponentId },
},
frontComponents: [frontComponent],
});
expect(result.isValid).toBe(true);
expect(result.errors).toHaveLength(0);
});
});
describe('relation field validation', () => {
@@ -122,6 +122,7 @@ export const buildManifest = async (
[];
const uninstallLogicFunctions: UninstallLogicFunctionApplicationManifest[] =
[];
const settingsFrontComponentUniversalIdentifiers: string[] = [];
const applicationRoleUniversalIdentifiers: string[] = [];
const applicationFilePaths: string[] = [];
const objectsFilePaths: string[] = [];
@@ -377,6 +378,14 @@ export const buildManifest = async (
frontComponents.push(config);
frontComponentsFilePaths.push(relativePath);
if (
targetFunctionName === TargetFunction.DefineSettingsFrontComponent
) {
settingsFrontComponentUniversalIdentifiers.push(
extract.config.universalIdentifier,
);
}
break;
}
case ManifestEntityKey.Views: {
@@ -559,6 +568,12 @@ export const buildManifest = async (
);
}
if (settingsFrontComponentUniversalIdentifiers.length > 1) {
errors.push(
'Only one settings front component is allowed per application',
);
}
if (applicationRoleUniversalIdentifiers.length > 1) {
errors.push('Only one defineApplicationRole is allowed per application');
}
@@ -611,6 +626,14 @@ export const buildManifest = async (
...(uninstallLogicFunctions.length >= 1
? { uninstallLogicFunction: uninstallLogicFunctions[0] }
: {}),
...(settingsFrontComponentUniversalIdentifiers.length >= 1
? {
settingsFrontComponent: {
universalIdentifier:
settingsFrontComponentUniversalIdentifiers[0],
},
}
: {}),
};
})()
: undefined;
@@ -16,6 +16,7 @@ export enum TargetFunction {
DefineAgent = 'defineAgent',
DefineConnectionProvider = 'defineConnectionProvider',
DefineFrontComponent = 'defineFrontComponent',
DefineSettingsFrontComponent = 'defineSettingsFrontComponent',
DefineView = 'defineView',
DefineViewField = 'defineViewField',
DefineNavigationMenuItem = 'defineNavigationMenuItem',
@@ -70,6 +71,8 @@ export const TARGET_FUNCTION_TO_ENTITY_KEY_MAPPING: Record<
[TargetFunction.DefineConnectionProvider]:
ManifestEntityKey.ConnectionProviders,
[TargetFunction.DefineFrontComponent]: ManifestEntityKey.FrontComponents,
[TargetFunction.DefineSettingsFrontComponent]:
ManifestEntityKey.FrontComponents,
[TargetFunction.DefineView]: ManifestEntityKey.Views,
[TargetFunction.DefineViewField]: ManifestEntityKey.ViewFields,
[TargetFunction.DefineNavigationMenuItem]:
@@ -74,7 +74,8 @@ const findUniversalIdentifiers = (obj: object): string[] => {
key === 'postInstallLogicFunction' ||
key === 'preInstallLogicFunction' ||
key === 'uninstallLogicFunction' ||
key === 'onConnectLogicFunction'
key === 'onConnectLogicFunction' ||
key === 'settingsFrontComponent'
) {
continue;
}
@@ -7,6 +7,7 @@ export type ApplicationConfig = Omit<
| 'requiredServerVersionRange'
| 'postInstallLogicFunction'
| 'preInstallLogicFunction'
| 'settingsFrontComponent'
| 'defaultRoleUniversalIdentifier'
| 'aboutDescription'
> & {
@@ -0,0 +1,62 @@
import { defineSettingsFrontComponent } from '@/sdk/define';
const MockComponent = () => null;
describe('defineSettingsFrontComponent', () => {
const validConfig = {
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'App Settings',
component: MockComponent,
};
it('should return successful validation result when valid', () => {
const result = defineSettingsFrontComponent(validConfig);
expect(result.success).toBe(true);
expect(result.config).toEqual(validConfig);
expect(result.errors).toEqual([]);
});
it('should return error when universalIdentifier is missing', () => {
const config = {
name: 'App Settings',
component: MockComponent,
};
const result = defineSettingsFrontComponent(config as any);
expect(result.success).toBe(false);
expect(result.errors).toContain(
'Settings front component must have a universalIdentifier',
);
});
it('should return error when component is missing', () => {
const config = {
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'App Settings',
};
const result = defineSettingsFrontComponent(config as any);
expect(result.success).toBe(false);
expect(result.errors).toEqual([
'Settings front component must have a component',
]);
});
it('should return error when component is not a function', () => {
const config = {
universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
name: 'App Settings',
component: 'not-a-function',
};
const result = defineSettingsFrontComponent(config as any);
expect(result.success).toBe(false);
expect(result.errors).toContain(
'Settings front component component must be a React component',
);
});
});
@@ -0,0 +1,24 @@
import { createValidationResult } from '@/sdk/define/common/utils/create-validation-result';
import type { DefineEntity } from '@/sdk/define/common/types/define-entity.type';
import { type SettingsFrontComponentConfig } from '@/sdk/define/front-component/settings-front-component-config';
export const defineSettingsFrontComponent: DefineEntity<
SettingsFrontComponentConfig
> = (config) => {
const errors = [];
if (!config.universalIdentifier) {
errors.push('Settings front component must have a universalIdentifier');
}
if (!config.component) {
errors.push('Settings front component must have a component');
} else if (typeof config.component !== 'function') {
errors.push('Settings front component component must be a React component');
}
return createValidationResult({
config,
errors,
});
};
@@ -0,0 +1,8 @@
import { type FrontComponentConfig } from '@/sdk/define/front-component/front-component-config';
// A settings front component always renders visible UI, so `isHeadless`
// is not configurable.
export type SettingsFrontComponentConfig = Omit<
FrontComponentConfig,
'isHeadless'
>;
@@ -70,10 +70,12 @@ export {
} from '@/sdk/define/conditional-availability/conditional-availability-variables';
export { defineFrontComponent } from '@/sdk/define/front-component/define-front-component';
export { defineSettingsFrontComponent } from '@/sdk/define/front-component/define-settings-front-component';
export type {
FrontComponentConfig,
FrontComponentType,
} from '@/sdk/define/front-component/front-component-config';
export type { SettingsFrontComponentConfig } from '@/sdk/define/front-component/settings-front-component-config';
export { defineIndex } from '@/sdk/define/indexes/define-index';
export type { IndexConfig } from '@/sdk/define/indexes/index-config';
@@ -249,7 +249,7 @@ export class ApplicationManifestMigrationService {
);
if (!dryRun) {
await this.syncDefaultRole({
await this.syncDefaultRoleAndSettingsFrontComponent({
manifest,
workspaceId,
ownerFlatApplication,
@@ -262,7 +262,7 @@ export class ApplicationManifestMigrationService {
};
}
private async syncDefaultRole({
private async syncDefaultRoleAndSettingsFrontComponent({
manifest,
workspaceId,
ownerFlatApplication,
@@ -271,10 +271,13 @@ export class ApplicationManifestMigrationService {
workspaceId: string;
ownerFlatApplication: FlatApplication;
}) {
const { flatRoleMaps: refreshedFlatRoleMaps } =
await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatRoleMaps',
]);
const {
flatRoleMaps: refreshedFlatRoleMaps,
flatFrontComponentMaps: refreshedFlatFrontComponentMaps,
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatRoleMaps',
'flatFrontComponentMaps',
]);
let defaultRoleId: string | null = null;
@@ -299,11 +302,31 @@ export class ApplicationManifestMigrationService {
}
}
if (isDefined(defaultRoleId)) {
await this.applicationService.update(ownerFlatApplication.id, {
workspaceId,
defaultRoleId,
let settingsCustomTabFrontComponentId: string | null = null;
const settingsFrontComponentUniversalIdentifier =
manifest.application.settingsFrontComponent?.universalIdentifier;
if (isDefined(settingsFrontComponentUniversalIdentifier)) {
const flatFrontComponent = findFlatEntityByUniversalIdentifier({
flatEntityMaps: refreshedFlatFrontComponentMaps,
universalIdentifier: settingsFrontComponentUniversalIdentifier,
});
if (!isDefined(flatFrontComponent)) {
throw new ApplicationException(
`Failed to resolve front component for settings front component universalIdentifier ${settingsFrontComponentUniversalIdentifier}`,
ApplicationExceptionCode.ENTITY_NOT_FOUND,
);
}
settingsCustomTabFrontComponentId = flatFrontComponent.id;
}
await this.applicationService.update(ownerFlatApplication.id, {
workspaceId,
settingsCustomTabFrontComponentId,
...(isDefined(defaultRoleId) ? { defaultRoleId } : {}),
});
}
}
@@ -5,10 +5,12 @@ import { type UniversalFlatFrontComponent } from 'src/engine/workspace-manager/w
export const fromFrontComponentManifestToUniversalFlatFrontComponent = ({
frontComponentManifest,
applicationUniversalIdentifier,
isSettingsFrontComponent,
now,
}: {
frontComponentManifest: FrontComponentManifest;
applicationUniversalIdentifier: string;
isSettingsFrontComponent: boolean;
now: string;
}): UniversalFlatFrontComponent => {
return {
@@ -20,7 +22,10 @@ export const fromFrontComponentManifestToUniversalFlatFrontComponent = ({
builtComponentPath: frontComponentManifest.builtComponentPath,
componentName: frontComponentManifest.componentName,
builtComponentChecksum: frontComponentManifest.builtComponentChecksum,
isHeadless: frontComponentManifest.isHeadless ?? false,
// A settings front component always renders visible UI.
isHeadless: isSettingsFrontComponent
? false
: (frontComponentManifest.isHeadless ?? false),
usesSdkClient: frontComponentManifest.usesSdkClient ?? false,
createdAt: now,
updatedAt: now,
@@ -215,12 +215,18 @@ export class ComputeApplicationManifestAllUniversalFlatEntityMapsService {
});
}
const settingsFrontComponentUniversalIdentifier =
manifest.application.settingsFrontComponent?.universalIdentifier;
for (const frontComponentManifest of manifest.frontComponents) {
addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({
universalFlatEntity:
fromFrontComponentManifestToUniversalFlatFrontComponent({
frontComponentManifest,
applicationUniversalIdentifier,
isSettingsFrontComponent:
frontComponentManifest.universalIdentifier ===
settingsFrontComponentUniversalIdentifier,
now,
}),
universalFlatEntityMapsToMutate:
@@ -113,11 +113,6 @@ export class ApplicationEntity extends WorkspaceRelatedEntity {
@Field(() => RoleDTO, { nullable: true })
defaultRole: RoleDTO | null;
/**
* @deprecated Custom settings tabs are no longer supported. The column is
* kept (not dropped) so existing installations upgrade cleanly, but the
* value is no longer read or synced from manifests.
*/
@Column({ nullable: true, type: 'uuid' })
settingsCustomTabFrontComponentId: string | null;
@@ -95,17 +95,9 @@ export class ApplicationDTO {
@Field({ nullable: true })
defaultRoleId?: string;
/**
* @deprecated Custom settings tabs are no longer supported. Kept for
* backward compatibility with existing installations; the value is ignored.
*/
@IsOptional()
@IsUUID()
@Field(() => UUIDScalarType, {
nullable: true,
deprecationReason:
'Custom settings tabs are no longer supported. This field is ignored.',
})
@Field(() => UUIDScalarType, { nullable: true })
settingsCustomTabFrontComponentId?: string;
@IsOptional()
@@ -1,5 +1,6 @@
import { type PostInstallLogicFunctionApplicationManifest } from '@/application/postInstallLogicFunctionApplicationType';
import { type PreInstallLogicFunctionApplicationManifest } from '@/application/preInstallLogicFunctionApplicationType';
import { type SettingsFrontComponentApplicationManifest } from '@/application/settingsFrontComponentApplicationType';
import { type UninstallLogicFunctionApplicationManifest } from '@/application/uninstallLogicFunctionApplicationType';
import { type ApplicationCategory } from './applicationCategoryType';
import { type ApplicationVariables } from './applicationVariablesType';
@@ -32,10 +33,10 @@ export type ApplicationManifest = SyncableEntityOptions & {
postInstallLogicFunction?: PostInstallLogicFunctionApplicationManifest;
preInstallLogicFunction?: PreInstallLogicFunctionApplicationManifest;
uninstallLogicFunction?: UninstallLogicFunctionApplicationManifest;
settingsFrontComponent?: SettingsFrontComponentApplicationManifest;
/**
* @deprecated Custom settings tabs are no longer supported. This property is
* kept for backward compatibility with older manifests but is now ignored.
* Use typed `applicationVariables` / `serverVariables` instead.
* @deprecated Use `defineSettingsFrontComponent()` (exposed on the manifest
* as `settingsFrontComponent`) instead. This property is ignored.
*/
settingsCustomTabFrontComponentUniversalIdentifier?: string;
packageJsonChecksum: string | null;
@@ -135,6 +135,7 @@ export type { RunAgentInput, RunAgentResult } from './runAgentType';
export type { ServerVariables } from './server-variables.type';
export type { ServerRouteDispatchResult } from './serverRouteDispatchResultType';
export type { ServerRouteTriggerSettings } from './serverRouteTriggerSettingsType';
export type { SettingsFrontComponentApplicationManifest } from './settingsFrontComponentApplicationType';
export type { SkillManifest } from './skillManifestType';
export type { StoredOAuthConnectionProviderConfig } from './storedOAuthConnectionProviderConfigType';
export type { SyncableEntityOptions } from './syncableEntityOptionsType';
@@ -0,0 +1,3 @@
export type SettingsFrontComponentApplicationManifest = {
universalIdentifier: string;
};