diff --git a/frontend/src/v2/__tests__/V2SettingsPage.test.tsx b/frontend/src/v2/__tests__/V2SettingsPage.test.tsx index 73e73a97f5..04be03b7e0 100644 --- a/frontend/src/v2/__tests__/V2SettingsPage.test.tsx +++ b/frontend/src/v2/__tests__/V2SettingsPage.test.tsx @@ -138,14 +138,13 @@ describe('V2SettingsPage', () => { expect(screen.getByText('Device controls')).toBeInTheDocument(); expect(screen.getByText('Plan controls')).toHaveAttribute('data-show-heading', 'false'); expect(screen.getByText('Device controls')).toHaveAttribute('data-show-heading', 'false'); - expect(screen.getByText('Connected app controls')).toBeInTheDocument(); + // Developer apps stay hidden unless REACT_APP_DEVELOPER_APPS=true (TASK-674). + expect(screen.queryByText('Connected app controls')).not.toBeInTheDocument(); expect(screen.getByRole('img', { name: 'lily avatar' })).toHaveClass('v2-settings__avatar'); expect(screen.getByRole('img', { name: 'lily avatar' })).toHaveAttribute('src', '/uploads/lily.png'); expect(screen.getByRole('img', { name: 'lily avatar' })).toHaveAttribute('data-kind', 'human'); - expect(screen.getByText('Connected app controls')).toHaveAttribute('data-variant', 'settings'); - expect(screen.getByText('Connected app controls')).toHaveAttribute('data-show-integrations', 'false'); expect(screen.getByRole('link', { name: /Connectors/ })).toHaveAttribute('href', '/v2/connectors'); - expect(screen.getByText('Developer apps').closest('details')).not.toHaveAttribute('open'); + expect(screen.queryByText('Developer apps')).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Regenerate' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Upload photo' })).toBeInTheDocument(); expect(screen.getByText('Regenerate for another, or upload a photo.')).toBeInTheDocument(); @@ -153,6 +152,24 @@ describe('V2SettingsPage', () => { expect(screen.getByRole('radio', { name: '中文' })).toBeInTheDocument(); }); + test('shows Developer apps only when REACT_APP_DEVELOPER_APPS is exactly true', () => { + (axios.get as jest.Mock).mockResolvedValue({ data: { hasToken: false } }); + const previous = process.env.REACT_APP_DEVELOPER_APPS; + try { + process.env.REACT_APP_DEVELOPER_APPS = 'yes'; + const off = renderSettings(); + expect(screen.queryByText('Developer apps')).not.toBeInTheDocument(); + off.unmount(); + + process.env.REACT_APP_DEVELOPER_APPS = 'true'; + renderSettings(); + expect(screen.getByText('Developer apps')).toBeInTheDocument(); + } finally { + if (previous === undefined) delete process.env.REACT_APP_DEVELOPER_APPS; + else process.env.REACT_APP_DEVELOPER_APPS = previous; + } + }); + test('uses a left navigation column to target Settings sections', () => { (axios.get as jest.Mock).mockResolvedValue({ data: { hasToken: false } }); renderSettings(); diff --git a/frontend/src/v2/components/V2SettingsPage.tsx b/frontend/src/v2/components/V2SettingsPage.tsx index 9e4c8f2c0d..327a8e8e8f 100644 --- a/frontend/src/v2/components/V2SettingsPage.tsx +++ b/frontend/src/v2/components/V2SettingsPage.tsx @@ -42,6 +42,10 @@ const SETTINGS_SECTIONS = [ { id: 'language', title: 'Language' }, ] as const; +// Custom "Developer apps" (AppsManagement) stays hidden until every path +// (create, edit, delete, use) is walked live. Default off (TASK-674, D215). +const developerAppsEnabled = () => process.env.REACT_APP_DEVELOPER_APPS === 'true'; + // Admin-only: the instance's user list and usage analytics. Their routes // survived the Settings consolidation (#1544) but the way in did not — the // pages were reachable only by typing the URL (2026-09-08). @@ -554,10 +558,12 @@ const V2SettingsPage: React.FC = () => { label="Connectors" detail="Manage connected services and channels" /> -
- Developer apps - -
+ {developerAppsEnabled() && ( +
+ Developer apps + +
+ )} {isAdmin && ( diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1153c173c8..87debaadc7 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -41,6 +41,8 @@ export default defineConfig({ // Error tracking is opt-in at build time. An empty value lets Rollup remove // the guarded dynamic import, keeping the SDK out of self-hosted bundles. 'process.env.REACT_APP_SENTRY_DSN': JSON.stringify(process.env.REACT_APP_SENTRY_DSN || ''), + // Developer apps flag (TASK-674). Unset in CI and deploy, so prod stays off. + 'process.env.REACT_APP_DEVELOPER_APPS': JSON.stringify(process.env.REACT_APP_DEVELOPER_APPS || ''), 'process.env': {}, }, });