+
+ Chat keys
+
+ Model-provider keys that power Chat on this deployment.
+
+
+
+ Managed keys
+
+
+
+ )
+}
diff --git a/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts b/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts
index ed53da7b24d..efc549b1923 100644
--- a/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts
+++ b/apps/sim/app/workspace/[workspaceId]/settings/navigation.test.ts
@@ -51,6 +51,7 @@ describe('unified settings navigation', () => {
{ id: 'sandboxes', label: 'Sandboxes', section: 'workspace' },
{ id: 'inbox', label: 'Sim Mailer', section: 'workspace' },
{ id: 'recently-deleted', label: 'Recently deleted', section: 'workspace' },
+ { id: 'self-host', label: 'Self hosting', section: 'platform' },
{ id: 'sso', label: 'Single sign-on', section: 'organization' },
{ id: 'sessions', label: 'Session policies', section: 'organization' },
{ id: 'data-retention', label: 'Data retention', section: 'organization' },
@@ -100,7 +101,7 @@ describe('unified settings navigation', () => {
'data-retention',
'data-drains',
])
- expect(idsForSection('platform')).toEqual(['admin', 'mothership'])
+ expect(idsForSection('platform')).toEqual(['admin', 'mothership', 'self-host'])
})
it('derives every unified item from exactly one registry entry', () => {
diff --git a/apps/sim/components/settings/navigation.test.ts b/apps/sim/components/settings/navigation.test.ts
index ca1b92fd241..0b05b742ddf 100644
--- a/apps/sim/components/settings/navigation.test.ts
+++ b/apps/sim/components/settings/navigation.test.ts
@@ -1,7 +1,9 @@
/**
* @vitest-environment node
*/
-import { resetEnvMock, setEnv } from '@sim/testing'
+import { createElement } from 'react'
+import { resetEnvFlagsMock, resetEnvMock, setEnv, setEnvFlags } from '@sim/testing'
+import { renderToStaticMarkup } from 'react-dom/server'
import { afterAll, beforeEach, describe, expect, it } from 'vitest'
import {
ACCOUNT_SETTINGS_ITEMS,
@@ -32,9 +34,13 @@ import {
*/
beforeEach(() => {
setEnv({ NEXT_PUBLIC_SANDBOX_ENABLED: 'true', NEXT_PUBLIC_E2B_ENABLED: undefined })
+ resetEnvFlagsMock()
})
-afterAll(resetEnvMock)
+afterAll(() => {
+ resetEnvMock()
+ resetEnvFlagsMock()
+})
describe('settings navigation boundaries', () => {
it('preserves the order of all four settings catalogs', () => {
@@ -58,6 +64,7 @@ describe('settings navigation boundaries', () => {
'sandboxes',
'inbox',
'recently-deleted',
+ 'self-host',
'sso',
'sessions',
'data-retention',
@@ -99,6 +106,7 @@ describe('settings navigation boundaries', () => {
'recently-deleted',
'forks',
'custom-blocks',
+ 'self-host',
])
})
@@ -127,6 +135,64 @@ describe('settings navigation boundaries', () => {
).not.toContain('sandboxes')
})
+ /**
+ * The Self-host section links out to the managed service that issues this
+ * deployment's Chat keys. On Sim Cloud that surface is reached from the
+ * account plane instead, so the section must not exist there at all — in the
+ * sidebar catalog or in the workspace-plane gate the route consults.
+ */
+ it('shows the Self-host section only on a self-hosted deployment', () => {
+ setEnvFlags({ isHosted: false })
+
+ expect(buildUnifiedSettingsNavigation().map(({ id }) => id)).toContain('self-host')
+ expect(
+ resolveWorkspaceNavigation({
+ permission: 'admin',
+ permissionConfig: {},
+ entitlements: {
+ byok: true,
+ inbox: true,
+ customBlocks: true,
+ forks: true,
+ sandboxes: true,
+ },
+ }).map(({ id }) => id)
+ ).toContain('self-host')
+ })
+
+ it('drops the Self-host section on hosted Sim', () => {
+ setEnvFlags({ isHosted: true })
+
+ expect(buildUnifiedSettingsNavigation().map(({ id }) => id)).not.toContain('self-host')
+ expect(
+ resolveWorkspaceNavigation({
+ permission: 'admin',
+ permissionConfig: {},
+ entitlements: {
+ byok: true,
+ inbox: true,
+ customBlocks: true,
+ forks: true,
+ sandboxes: true,
+ },
+ }).map(({ id }) => id)
+ ).not.toContain('self-host')
+ })
+
+ /**
+ * The mark must be a line icon that inherits `--text-icon` like every other
+ * nav glyph — an emoji would render in the platform's own colors and be the
+ * one colored item in a monochrome icon column.
+ */
+ it('marks the Self hosting section with a currentColor line icon', () => {
+ const selfHost = buildUnifiedSettingsNavigation().find(({ id }) => id === 'self-host')
+ const markup = renderToStaticMarkup(createElement(selfHost!.icon, {}))
+
+ expect(selfHost?.label).toBe('Self hosting')
+ expect(markup).toContain('