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;
}