From 3a71619a482c5031969dea7f632eb5ac9128cf9d Mon Sep 17 00:00:00 2001 From: Steven Enamakel <31011319+senamakel@users.noreply.github.com> Date: Fri, 12 Jun 2026 13:32:39 -0700 Subject: [PATCH] feat(settings): always show Developer & Diagnostics (drop dev-mode gate) (#3639) --- app/src/components/settings/SettingsHome.tsx | 36 ++++++------- .../settings/__tests__/SettingsHome.test.tsx | 15 ++---- .../components/settings/panels/AboutPanel.tsx | 43 +--------------- .../panels/__tests__/AboutPanel.test.tsx | 51 ------------------- app/src/hooks/useDeveloperMode.ts | 18 +++---- 5 files changed, 28 insertions(+), 135 deletions(-) diff --git a/app/src/components/settings/SettingsHome.tsx b/app/src/components/settings/SettingsHome.tsx index 974192e9c..516c3615d 100644 --- a/app/src/components/settings/SettingsHome.tsx +++ b/app/src/components/settings/SettingsHome.tsx @@ -1,6 +1,5 @@ import { type ReactNode, useState } from 'react'; -import { useDeveloperMode } from '../../hooks/useDeveloperMode'; import { useT } from '../../lib/i18n/I18nContext'; import LanguageSelect from '../LanguageSelect'; import SettingsHeader from './components/SettingsHeader'; @@ -229,7 +228,6 @@ const GroupHeader = ({ label }: { label: string }) => const SettingsHome = () => { const { navigateToSettings } = useSettingsNavigation(); const { t } = useT(); - const developerMode = useDeveloperMode(); // Global settings search. While a query is active the normal menu is hidden // and the search bar renders its own ranked result list instead. @@ -375,26 +373,22 @@ const SettingsHome = () => { ], }; - // --- Developer & Diagnostics (gated) --- - // Hidden when developer mode is off. - // About is always accessible — that's where the toggle lives. - const developerGroup: SettingsGroup | null = developerMode - ? { - id: 'developer', - label: '', - items: [ - { - id: 'developer-options', - title: t('settings.developerDiagnostics'), - description: t('settings.developerDiagnosticsDesc'), - icon: DeveloperIcon, - onClick: () => navigateToSettings('developer-options'), - }, - ], - } - : null; + // --- Developer & Diagnostics (always visible) --- + const developerGroup: SettingsGroup = { + id: 'developer', + label: '', + items: [ + { + id: 'developer-options', + title: t('settings.developerDiagnostics'), + description: t('settings.developerDiagnosticsDesc'), + icon: DeveloperIcon, + onClick: () => navigateToSettings('developer-options'), + }, + ], + }; - const trailingGroups: SettingsGroup[] = [...(developerGroup ? [developerGroup] : []), aboutGroup]; + const trailingGroups: SettingsGroup[] = [developerGroup, aboutGroup]; return (
diff --git a/app/src/components/settings/__tests__/SettingsHome.test.tsx b/app/src/components/settings/__tests__/SettingsHome.test.tsx index 08657e94f..f3bd8a575 100644 --- a/app/src/components/settings/__tests__/SettingsHome.test.tsx +++ b/app/src/components/settings/__tests__/SettingsHome.test.tsx @@ -264,25 +264,18 @@ describe('SettingsHome', () => { expect(screen.queryByText('Billing & Usage')).not.toBeInTheDocument(); }); - it('navigates to developer-options when "Developer & Diagnostics" is clicked (developerMode=true)', async () => { + it('navigates to developer-options when "Developer & Diagnostics" is clicked', async () => { const user = userEvent.setup(); - renderSettingsHome({ developerMode: true }); + renderSettingsHome(); await user.click(screen.getByTestId('settings-nav-developer-options')); expect(mockNavigateToSettings).toHaveBeenCalledWith('developer-options'); }); }); - describe('developer mode gate', () => { - it('hides the developer-options entry when developerMode is off', () => { + describe('developer & diagnostics (always visible)', () => { + it('shows the developer-options entry regardless of the developerMode preference', () => { renderSettingsHome({ developerMode: false }); - expect(screen.queryByTestId('settings-nav-developer-options')).not.toBeInTheDocument(); - // The English resolved text should also be absent - expect(screen.queryByText('Developer & Diagnostics')).not.toBeInTheDocument(); - }); - - it('shows the developer-options entry when developerMode is on', () => { - renderSettingsHome({ developerMode: true }); expect(screen.getByTestId('settings-nav-developer-options')).toBeInTheDocument(); // useT() resolves to English even without I18nProvider expect(screen.getByText('Developer & Diagnostics')).toBeInTheDocument(); diff --git a/app/src/components/settings/panels/AboutPanel.tsx b/app/src/components/settings/panels/AboutPanel.tsx index dbda3598e..0443c9796 100644 --- a/app/src/components/settings/panels/AboutPanel.tsx +++ b/app/src/components/settings/panels/AboutPanel.tsx @@ -7,27 +7,21 @@ * here would race with that component's own state machine. */ import { invoke } from '@tauri-apps/api/core'; -import debug from 'debug'; import { useEffect, useState } from 'react'; import { useAppUpdate } from '../../../hooks/useAppUpdate'; -import { useDeveloperMode } from '../../../hooks/useDeveloperMode'; import { useT } from '../../../lib/i18n/I18nContext'; -import { useAppDispatch, useAppSelector } from '../../../store/hooks'; -import { selectDeveloperMode, setDeveloperMode } from '../../../store/themeSlice'; +import { useAppSelector } from '../../../store/hooks'; import { APP_VERSION, LATEST_APP_DOWNLOAD_URL } from '../../../utils/config'; import { isTauriEnvironment } from '../../../utils/configPersistence'; import { openUrl } from '../../../utils/openUrl'; import Button from '../../ui/Button'; import SettingsHeader from '../components/SettingsHeader'; -import { SettingsRow, SettingsSection, SettingsSwitch } from '../controls'; +import { SettingsRow, SettingsSection } from '../controls'; import { useSettingsNavigation } from '../hooks/useSettingsNavigation'; -const log = debug('settings:developer-mode'); - const AboutPanel = () => { const { t } = useT(); - const dispatch = useAppDispatch(); const { navigateBack, breadcrumbs } = useSettingsNavigation(); // The auto-cadence is already running via the global ; // disable it here so opening the panel doesn't double-trigger probes. @@ -35,12 +29,6 @@ const AboutPanel = () => { const [lastCheckedAt, setLastCheckedAt] = useState(null); const coreMode = useAppSelector(state => state.coreMode.mode); const [rpcUrl, setRpcUrl] = useState(null); - // Persisted developer mode preference (not the combined IS_DEV || developerMode). - // We read the raw preference here so the toggle reflects only the user's choice, - // not whether the build is a dev build. - const developerModePref = useAppSelector(selectDeveloperMode); - // Combined gate — true when IS_DEV or the pref is on. Used for the helper text. - const developerModeActive = useDeveloperMode(); // Local mode picks a dynamic port at app launch, so the authoritative // value lives in the Tauri shell (`core_rpc_url` command) rather than the @@ -161,33 +149,6 @@ const AboutPanel = () => {
- {/* Developer Mode toggle — always visible so users can enable it - without needing it to be on first (chicken-and-egg avoidance). */} -
- - { - log('toggled to %s', String(next)); - dispatch(setDeveloperMode(next)); - }} - aria-label={t('settings.developerMode.title')} - /> - } - /> - -
- {/* Releases */}
diff --git a/app/src/components/settings/panels/__tests__/AboutPanel.test.tsx b/app/src/components/settings/panels/__tests__/AboutPanel.test.tsx index c8dbb7752..682083965 100644 --- a/app/src/components/settings/panels/__tests__/AboutPanel.test.tsx +++ b/app/src/components/settings/panels/__tests__/AboutPanel.test.tsx @@ -151,54 +151,3 @@ describe('AboutPanel', () => { }); }); }); - -describe('AboutPanel — Developer Mode toggle', () => { - beforeEach(() => { - statusListeners.length = 0; - mockCheckAppUpdate.mockReset(); - hoisted.mockIsTauri.mockReturnValue(false); // Simplify — no Tauri IPC for these tests - }); - - // useT() falls back to English even without I18nProvider (resolveEn() lookup). - // The toggle's aria-label is set to t('settings.developerMode.title') which - // resolves to the English string "Developer mode". - const TOGGLE_ARIA_LABEL = /Developer mode/i; - - it('renders the Developer mode switch section', () => { - renderWithProviders(); - expect(screen.getByRole('switch', { name: TOGGLE_ARIA_LABEL })).toBeInTheDocument(); - expect(screen.getByTestId('developer-mode-section')).toBeInTheDocument(); - }); - - it('switch defaults to off (aria-checked=false)', () => { - renderWithProviders(); - const toggle = screen.getByRole('switch', { name: TOGGLE_ARIA_LABEL }); - expect(toggle).toHaveAttribute('aria-checked', 'false'); - }); - - it('turns on developer mode when toggle is clicked', () => { - const { store } = renderWithProviders(); - const toggle = screen.getByRole('switch', { name: TOGGLE_ARIA_LABEL }); - fireEvent.click(toggle); - expect(store.getState().theme.developerMode).toBe(true); - expect(toggle).toHaveAttribute('aria-checked', 'true'); - }); - - it('toggles developer mode off when clicked again', () => { - const { store } = renderWithProviders(, { - preloadedState: { - theme: { - mode: 'system', - tabBarLabels: 'hover', - fontSize: 'medium', - agentMessageViewMode: 'bubbles', - developerMode: true, - }, - }, - }); - const toggle = screen.getByRole('switch', { name: TOGGLE_ARIA_LABEL }); - expect(toggle).toHaveAttribute('aria-checked', 'true'); - fireEvent.click(toggle); - expect(store.getState().theme.developerMode).toBe(false); - }); -}); diff --git a/app/src/hooks/useDeveloperMode.ts b/app/src/hooks/useDeveloperMode.ts index 67a3931ac..655b311c4 100644 --- a/app/src/hooks/useDeveloperMode.ts +++ b/app/src/hooks/useDeveloperMode.ts @@ -1,18 +1,14 @@ /** * useDeveloperMode — runtime developer-mode gate. * - * Returns `true` when developer surfaces should be visible. The gate is open - * when EITHER the build is a Vite dev build (`IS_DEV`) OR the user has - * enabled Developer Mode in Settings › About. + * Developer surfaces (Settings › Developer & Diagnostics, dev-only settings + * search entries, the Intelligence "council" tab) are now always visible, so + * this hook unconditionally returns `true`. The previous opt-in toggle in + * Settings › About has been removed. * - * Gating is UI-only. The Rust `SecurityPolicy` / autonomy-tier enforcement - * in the core is authoritative and is never relaxed by this toggle. + * Gating was always UI-only. The Rust `SecurityPolicy` / autonomy-tier + * enforcement in the core is authoritative and is unaffected by this. */ -import { useAppSelector } from '../store/hooks'; -import { selectDeveloperMode } from '../store/themeSlice'; -import { IS_DEV } from '../utils/config'; - export function useDeveloperMode(): boolean { - const persistedMode = useAppSelector(selectDeveloperMode); - return IS_DEV || persistedMode; + return true; }