diff --git a/packages/app/e2e/helpers/settings.ts b/packages/app/e2e/helpers/settings.ts index 7b8c63bda..22c5f5881 100644 --- a/packages/app/e2e/helpers/settings.ts +++ b/packages/app/e2e/helpers/settings.ts @@ -371,6 +371,7 @@ export async function expectRetiredSidebarSectionsAbsent(page: Page): Promise { await openSettingsHost(page, serverId); await openHostSection(page, serverId, "host"); - await expectSettingsHeader(page, "Host"); + await expectSettingsHeader(page, "Overview"); await expectHostLabelDisplayed(page); await expectHostActionCards(page, serverId); }); diff --git a/packages/app/src/app/settings/[section].tsx b/packages/app/src/app/settings/[section].tsx index 19340ad70..8b036e2aa 100644 --- a/packages/app/src/app/settings/[section].tsx +++ b/packages/app/src/app/settings/[section].tsx @@ -1,7 +1,38 @@ -import { useLocalSearchParams } from "expo-router"; +import { Redirect, useLocalSearchParams } from "expo-router"; import { useMemo } from "react"; +import { useHostRuntimeBootstrapState } from "@/app/_layout"; +import { HostRouteBootstrapBoundary } from "@/components/host-route-bootstrap-boundary"; +import { useLocalDaemonServerIdState } from "@/hooks/use-is-local-daemon"; +import { useHosts } from "@/runtime/host-runtime"; import SettingsScreen from "@/screens/settings-screen"; -import { isSettingsSectionSlug, type SettingsSectionSlug } from "@/utils/host-routes"; +import { StartupSplashScreen } from "@/screens/startup-splash-screen"; +import { + buildSettingsHostSectionRoute, + buildSettingsRoute, + isSettingsSectionSlug, + type SettingsSectionSlug, +} from "@/utils/host-routes"; + +// COMPAT(settingsDaemonRedirect): added 2026-07-08, remove after 2027-01-08. +function SettingsDaemonRedirect() { + const hosts = useHosts(); + const localDaemon = useLocalDaemonServerIdState(); + const bootstrapState = useHostRuntimeBootstrapState(); + + if (localDaemon.status === "loading") { + return ; + } + + if ( + localDaemon.status === "resolved" && + localDaemon.serverId !== null && + hosts.some((host) => host.serverId === localDaemon.serverId) + ) { + return ; + } + + return ; +} export default function SettingsSectionRoute() { const params = useLocalSearchParams<{ section?: string; addHost?: string }>(); @@ -10,5 +41,14 @@ export default function SettingsSectionRoute() { const openAddHostIntent = typeof params.addHost === "string" ? params.addHost : null; const view = useMemo(() => ({ kind: "section" as const, section }), [section]); + // COMPAT(settingsDaemonRedirect): added 2026-07-08, remove after 2027-01-08. + if (rawSection === "daemon") { + return ( + + + + ); + } + return ; } diff --git a/packages/app/src/components/hosts/host-picker-constants.ts b/packages/app/src/components/hosts/host-picker-constants.ts index d28760019..c785e4f30 100644 --- a/packages/app/src/components/hosts/host-picker-constants.ts +++ b/packages/app/src/components/hosts/host-picker-constants.ts @@ -1,5 +1,6 @@ export const ADD_HOST_OPTION_ID = "__add_host__"; export const ALL_HOSTS_OPTION_ID = "__all_hosts__"; +export const ENABLE_BUILT_IN_DAEMON_OPTION_ID = "__enable_built_in_daemon__"; export function getHostPickerLabel( hosts: Array<{ label: string; serverId: string }>, diff --git a/packages/app/src/components/hosts/host-picker.tsx b/packages/app/src/components/hosts/host-picker.tsx index d00b88a01..60fafc5b7 100644 --- a/packages/app/src/components/hosts/host-picker.tsx +++ b/packages/app/src/components/hosts/host-picker.tsx @@ -11,10 +11,16 @@ import { orderHostsLocalFirst } from "@/types/host-connection"; import { ADD_HOST_OPTION_ID, ALL_HOSTS_OPTION_ID, + ENABLE_BUILT_IN_DAEMON_OPTION_ID, getHostPickerLabel, } from "./host-picker-constants"; -export { ADD_HOST_OPTION_ID, ALL_HOSTS_OPTION_ID, getHostPickerLabel }; +export { + ADD_HOST_OPTION_ID, + ALL_HOSTS_OPTION_ID, + ENABLE_BUILT_IN_DAEMON_OPTION_ID, + getHostPickerLabel, +}; const SEARCHABLE_THRESHOLD = 10; type RenderHostOption = NonNullable; @@ -115,6 +121,12 @@ export function HostPickerOption({ ); } +const SYSTEM_HOST_PICKER_OPTION_LABELS: Record<"add" | "all" | "enableBuiltInDaemon", string> = { + add: "Add host", + all: "All hosts", + enableBuiltInDaemon: "Enable built-in daemon", +}; + function SystemHostPickerOption({ active, selected, @@ -125,12 +137,12 @@ function SystemHostPickerOption({ active: boolean; selected?: boolean; onPress: () => void; - kind: "add" | "all"; + kind: "add" | "all" | "enableBuiltInDaemon"; testID?: string; }): ReactElement { const { theme } = useUnistyles(); const Icon = kind === "add" ? Plus : Server; - const label = kind === "add" ? "Add host" : "All hosts"; + const label = SYSTEM_HOST_PICKER_OPTION_LABELS[kind]; const leadingSlot = useMemo( () => , [Icon, theme.colors.foregroundMuted, theme.iconSize.sm], @@ -158,6 +170,8 @@ export interface HostPickerProps { includeAllHost?: boolean; includeAddHost?: boolean; onAddHost?: () => void; + includeEnableBuiltInDaemon?: boolean; + onEnableBuiltInDaemon?: () => void; showActiveConnection?: boolean; onOpenHostSettings?: (serverId: string) => void; searchable?: boolean; @@ -179,6 +193,8 @@ export function HostPicker({ includeAllHost, includeAddHost, onAddHost, + includeEnableBuiltInDaemon, + onEnableBuiltInDaemon, showActiveConnection, onOpenHostSettings, searchable, @@ -199,8 +215,13 @@ export function HostPicker({ const hostOptions = orderedHosts.map((host) => ({ id: host.serverId, label: host.label })); if (includeAllHost) hostOptions.unshift({ id: ALL_HOSTS_OPTION_ID, label: "All hosts" }); if (includeAddHost) hostOptions.push({ id: ADD_HOST_OPTION_ID, label: "Add host" }); + if (includeEnableBuiltInDaemon) + hostOptions.push({ + id: ENABLE_BUILT_IN_DAEMON_OPTION_ID, + label: "Enable built-in daemon", + }); return hostOptions; - }, [orderedHosts, includeAllHost, includeAddHost]); + }, [orderedHosts, includeAllHost, includeAddHost, includeEnableBuiltInDaemon]); const isSearchable = searchable === true && orderedHosts.length > SEARCHABLE_THRESHOLD; @@ -208,12 +229,14 @@ export function HostPicker({ (id: string) => { if (id === ADD_HOST_OPTION_ID) { onAddHost?.(); + } else if (id === ENABLE_BUILT_IN_DAEMON_OPTION_ID) { + onEnableBuiltInDaemon?.(); } else { onSelect(id); } onOpenChange(false); }, - [onAddHost, onOpenChange, onSelect], + [onAddHost, onEnableBuiltInDaemon, onOpenChange, onSelect], ); const handleOpenHostSettings = useCallback( @@ -246,6 +269,11 @@ export function HostPicker({ /> ); } + if (option.id === ENABLE_BUILT_IN_DAEMON_OPTION_ID) { + return ( + + ); + } return ( void; + enable: () => Promise; } export function useBuiltInDaemonManagement( @@ -42,12 +43,16 @@ export function useBuiltInDaemonManagement( const { t } = useTranslation(); const { daemonStatus, settings, updateSettings, setStatus, refreshStatus } = input; const reportError = useDesktopIpcErrorReporter(); - const { mutate: toggleDaemonManagement, isPending: isUpdating } = useMutation< - DaemonManagementToggleResult, - Error - >({ - mutationFn: async () => { - const wasManagingDaemon = settings.manageBuiltInDaemon; + const { + mutate: toggleDaemonManagement, + mutateAsync: toggleDaemonManagementAsync, + isPending: isUpdating, + } = useMutation({ + mutationFn: async ({ forceEnable }) => { + // forceEnable takes the enable branch regardless of the persisted + // setting — the recovery affordance must never reach the stop/confirm + // path even if manageBuiltInDaemon was left true. + const wasManagingDaemon = forceEnable ? false : settings.manageBuiltInDaemon; try { const result = await executeDaemonManagementToggle(wasManagingDaemon, daemonStatus, { confirm: () => @@ -109,8 +114,21 @@ export function useBuiltInDaemonManagement( return; } - toggleDaemonManagement(); + toggleDaemonManagement({ forceEnable: false }); }, [isUpdating, toggleDaemonManagement]); - return { isUpdating, toggle }; + const enable = useCallback(async () => { + if (isUpdating) { + return null; + } + + try { + return await toggleDaemonManagementAsync({ forceEnable: true }); + } catch { + // onError has already surfaced the failure; callers only act on success. + return null; + } + }, [isUpdating, toggleDaemonManagementAsync]); + + return { isUpdating, toggle, enable }; } diff --git a/packages/app/src/desktop/hooks/use-enable-built-in-daemon-option.ts b/packages/app/src/desktop/hooks/use-enable-built-in-daemon-option.ts new file mode 100644 index 000000000..4d2e2f103 --- /dev/null +++ b/packages/app/src/desktop/hooks/use-enable-built-in-daemon-option.ts @@ -0,0 +1,46 @@ +import { useCallback } from "react"; +import { router } from "expo-router"; +import { getIsElectron } from "@/constants/platform"; +import { useLocalDaemonServerIdState } from "@/hooks/use-is-local-daemon"; +import { useHosts } from "@/runtime/host-runtime"; +import { useDesktopSettings } from "@/desktop/settings/desktop-settings"; +import { useDaemonStatus } from "@/desktop/hooks/use-daemon-status"; +import { useBuiltInDaemonManagement } from "@/desktop/hooks/use-built-in-daemon-management"; +import { buildSettingsHostSectionRoute } from "@/utils/host-routes"; + +export interface EnableBuiltInDaemonOption { + visible: boolean; + onPress: () => void; +} + +export function useEnableBuiltInDaemonOption(): EnableBuiltInDaemonOption { + const isElectron = getIsElectron(); + const localDaemon = useLocalDaemonServerIdState(); + const hosts = useHosts(); + const { settings, updateSettings } = useDesktopSettings(); + const { data, setStatus, refetch } = useDaemonStatus(); + const { enable } = useBuiltInDaemonManagement({ + daemonStatus: data?.status ?? null, + settings: settings.daemon, + updateSettings: (updates) => updateSettings({ daemon: updates }), + setStatus, + refreshStatus: refetch, + }); + + const isLocalhostConfigured = + localDaemon.status === "resolved" && + localDaemon.serverId !== null && + hosts.some((host) => host.serverId === localDaemon.serverId); + const visible = isElectron && localDaemon.status === "resolved" && !isLocalhostConfigured; + + const onPress = useCallback(() => { + void (async () => { + const result = await enable(); + if (result?.kind === "enabled") { + router.push(buildSettingsHostSectionRoute(result.newStatus.serverId, "host")); + } + })(); + }, [enable]); + + return { visible, onPress }; +} diff --git a/packages/app/src/hooks/use-is-local-daemon.ts b/packages/app/src/hooks/use-is-local-daemon.ts index 4e47c71bf..5bc51200d 100644 --- a/packages/app/src/hooks/use-is-local-daemon.ts +++ b/packages/app/src/hooks/use-is-local-daemon.ts @@ -15,10 +15,10 @@ async function loadDesktopDaemonServerId(): Promise }; } -export function useLocalDaemonServerId(): string | null { +function useLocalDaemonServerIdQuery() { const isDesktopApp = shouldUseDesktopDaemon(); - const query = useQuery({ + return useQuery({ queryKey: DESKTOP_DAEMON_SERVER_ID_QUERY_KEY, queryFn: loadDesktopDaemonServerId, enabled: isDesktopApp, @@ -30,6 +30,11 @@ export function useLocalDaemonServerId(): string | null { refetchOnWindowFocus: false, retry: false, }); +} + +export function useLocalDaemonServerId(): string | null { + const isDesktopApp = shouldUseDesktopDaemon(); + const query = useLocalDaemonServerIdQuery(); if (!isDesktopApp) { return null; @@ -38,6 +43,27 @@ export function useLocalDaemonServerId(): string | null { return query.data?.serverId ?? null; } +export type LocalDaemonServerIdState = + | { status: "loading" } + | { status: "error" } + | { status: "resolved"; serverId: string | null }; + +export function useLocalDaemonServerIdState(): LocalDaemonServerIdState { + const isDesktopApp = shouldUseDesktopDaemon(); + const query = useLocalDaemonServerIdQuery(); + + if (!isDesktopApp) { + return { status: "resolved", serverId: null }; + } + if (query.isError) { + return { status: "error" }; + } + if (query.isSuccess) { + return { status: "resolved", serverId: query.data.serverId }; + } + return { status: "loading" }; +} + export function useIsLocalDaemon(serverId: string): boolean { const normalizedServerId = serverId.trim(); const localServerId = useLocalDaemonServerId(); diff --git a/packages/app/src/i18n/resources/ar.ts b/packages/app/src/i18n/resources/ar.ts index 88a6fe5eb..fda26dd98 100644 --- a/packages/app/src/i18n/resources/ar.ts +++ b/packages/app/src/i18n/resources/ar.ts @@ -1404,6 +1404,7 @@ export const ar: TranslationResources = { }, backToWorkspace: "خلف", addHost: "أضف مضيفًا", + enableBuiltInDaemon: "تفعيل البرنامج الخفي المدمج", projects: "المشاريع", projectList: { hostLoadFailed: "تعذر تحميل المشاريع من المضيف{{hostName}}:{{message}}", @@ -1412,7 +1413,6 @@ export const ar: TranslationResources = { groupInfo: "حول{{title}}", sections: { general: "عام", - daemon: "Daemon", appearance: "مظهر", shortcuts: "الاختصارات", integrations: "التكامل", @@ -1427,7 +1427,7 @@ export const ar: TranslationResources = { providers: "مقدمي الخدمات", usage: "الاستخدام", terminals: "Terminals", - host: "Host", + host: "نظرة عامة", }, general: { title: "عام", diff --git a/packages/app/src/i18n/resources/en.ts b/packages/app/src/i18n/resources/en.ts index a785a25a2..3448c930f 100644 --- a/packages/app/src/i18n/resources/en.ts +++ b/packages/app/src/i18n/resources/en.ts @@ -1412,6 +1412,7 @@ export const en = { }, backToWorkspace: "Back", addHost: "Add host", + enableBuiltInDaemon: "Enable built-in daemon", projects: "Projects", projectList: { hostLoadFailed: "Couldn't load projects from host {{hostName}}: {{message}}", @@ -1420,7 +1421,6 @@ export const en = { groupInfo: "About {{title}}", sections: { general: "General", - daemon: "Daemon", appearance: "Appearance", shortcuts: "Shortcuts", integrations: "Integrations", @@ -1435,7 +1435,7 @@ export const en = { providers: "Providers", usage: "Usage", terminals: "Terminals", - host: "Host", + host: "Overview", }, general: { title: "General", diff --git a/packages/app/src/i18n/resources/es.ts b/packages/app/src/i18n/resources/es.ts index 1463b5e23..5f800ef22 100644 --- a/packages/app/src/i18n/resources/es.ts +++ b/packages/app/src/i18n/resources/es.ts @@ -1443,6 +1443,7 @@ export const es: TranslationResources = { }, backToWorkspace: "Atrás", addHost: "Agregar anfitrión", + enableBuiltInDaemon: "Activar el demonio integrado", projects: "Proyectos", projectList: { hostLoadFailed: "No se pudieron cargar proyectos desde el host{{hostName}}:{{message}}", @@ -1451,7 +1452,6 @@ export const es: TranslationResources = { groupInfo: "Acerca de{{title}}", sections: { general: "General", - daemon: "Daemon", appearance: "Apariencia", shortcuts: "Atajos", integrations: "Integraciones", @@ -1466,7 +1466,7 @@ export const es: TranslationResources = { providers: "Proveedores", usage: "Uso", terminals: "Terminals", - host: "Host", + host: "Resumen", }, general: { title: "General", diff --git a/packages/app/src/i18n/resources/fr.ts b/packages/app/src/i18n/resources/fr.ts index b6ce77fc1..dd6b4ca1f 100644 --- a/packages/app/src/i18n/resources/fr.ts +++ b/packages/app/src/i18n/resources/fr.ts @@ -1446,6 +1446,7 @@ export const fr: TranslationResources = { }, backToWorkspace: "Dos", addHost: "Ajouter un hôte", + enableBuiltInDaemon: "Activer le démon intégré", projects: "Projets", projectList: { hostLoadFailed: "Impossible de charger les projets depuis l'hôte{{hostName}}:{{message}}", @@ -1454,7 +1455,6 @@ export const fr: TranslationResources = { groupInfo: "À propos de{{title}}", sections: { general: "Général", - daemon: "Daemon", appearance: "Apparence", shortcuts: "Raccourcis", integrations: "Intégrations", @@ -1469,7 +1469,7 @@ export const fr: TranslationResources = { providers: "Fournisseurs", usage: "Utilisation", terminals: "Terminals", - host: "Host", + host: "Aperçu", }, general: { title: "Général", diff --git a/packages/app/src/i18n/resources/ja.ts b/packages/app/src/i18n/resources/ja.ts index 940a69b29..ab65eb302 100644 --- a/packages/app/src/i18n/resources/ja.ts +++ b/packages/app/src/i18n/resources/ja.ts @@ -1421,6 +1421,7 @@ export const ja: TranslationResources = { }, backToWorkspace: "戻る", addHost: "ホストを追加", + enableBuiltInDaemon: "組み込みデーモンを有効にする", projects: "プロジェクト", projectList: { hostLoadFailed: "ホスト{{hostName}}からプロジェクトを読み込めませんでした: {{message}}", @@ -1429,7 +1430,6 @@ export const ja: TranslationResources = { groupInfo: "{{title}}について", sections: { general: "一般", - daemon: "デーモン", appearance: "外観", shortcuts: "ショートカット", integrations: "連携", @@ -1444,7 +1444,7 @@ export const ja: TranslationResources = { providers: "プロバイダー", usage: "使用状況", terminals: "ターミナル", - host: "ホスト", + host: "概要", }, general: { title: "一般", diff --git a/packages/app/src/i18n/resources/pt-BR.ts b/packages/app/src/i18n/resources/pt-BR.ts index 413538448..2ffd7118c 100644 --- a/packages/app/src/i18n/resources/pt-BR.ts +++ b/packages/app/src/i18n/resources/pt-BR.ts @@ -1429,6 +1429,7 @@ export const ptBR: TranslationResources = { }, backToWorkspace: "Voltar", addHost: "Adicionar host", + enableBuiltInDaemon: "Ativar o daemon integrado", projects: "Projetos", projectList: { hostLoadFailed: "Não foi possível carregar projetos do host {{hostName}}: {{message}}", @@ -1437,7 +1438,6 @@ export const ptBR: TranslationResources = { groupInfo: "Sobre {{title}}", sections: { general: "Geral", - daemon: "Daemon", appearance: "Aparência", shortcuts: "Atalhos", integrations: "Integrações", @@ -1452,7 +1452,7 @@ export const ptBR: TranslationResources = { providers: "Provedores", usage: "Uso", terminals: "Terminais", - host: "Host", + host: "Visão geral", }, general: { title: "Geral", diff --git a/packages/app/src/i18n/resources/ru.ts b/packages/app/src/i18n/resources/ru.ts index 8dce28187..62c15fa14 100644 --- a/packages/app/src/i18n/resources/ru.ts +++ b/packages/app/src/i18n/resources/ru.ts @@ -1435,6 +1435,7 @@ export const ru: TranslationResources = { }, backToWorkspace: "Назад", addHost: "Добавить хост", + enableBuiltInDaemon: "Включить встроенный демон", projects: "Проекты", projectList: { hostLoadFailed: "Не удалось загрузить проекты с хоста{{hostName}}:{{message}}.", @@ -1443,7 +1444,6 @@ export const ru: TranslationResources = { groupInfo: "О{{title}}", sections: { general: "Общий", - daemon: "Daemon", appearance: "Появление", shortcuts: "Ярлыки", integrations: "Интеграции", @@ -1458,7 +1458,7 @@ export const ru: TranslationResources = { providers: "Провайдеры", usage: "Использование", terminals: "Terminals", - host: "Host", + host: "Обзор", }, general: { title: "Общий", diff --git a/packages/app/src/i18n/resources/zh-CN.ts b/packages/app/src/i18n/resources/zh-CN.ts index ef6bee331..4eac0e9ca 100644 --- a/packages/app/src/i18n/resources/zh-CN.ts +++ b/packages/app/src/i18n/resources/zh-CN.ts @@ -1387,6 +1387,7 @@ export const zhCN: TranslationResources = { }, backToWorkspace: "返回", addHost: "添加主机", + enableBuiltInDaemon: "启用内置 daemon", projects: "项目", projectList: { hostLoadFailed: "无法从 Host {{hostName}} 加载 projects:{{message}}", @@ -1395,7 +1396,6 @@ export const zhCN: TranslationResources = { groupInfo: "关于 {{title}}", sections: { general: "通用", - daemon: "Daemon", appearance: "外观", shortcuts: "快捷键", integrations: "集成", @@ -1410,7 +1410,7 @@ export const zhCN: TranslationResources = { providers: "Providers", usage: "使用情况", terminals: "Terminals", - host: "Host", + host: "概览", }, general: { title: "通用", diff --git a/packages/app/src/screens/settings-screen.tsx b/packages/app/src/screens/settings-screen.tsx index cfbfaa07d..e004ca00e 100644 --- a/packages/app/src/screens/settings-screen.tsx +++ b/packages/app/src/screens/settings-screen.tsx @@ -72,7 +72,6 @@ import { SegmentedControl } from "@/components/ui/segmented-control"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem } from "@/components/ui/dropdown-menu"; import { DesktopPermissionsSection } from "@/desktop/components/desktop-permissions-section"; import { IntegrationsSection } from "@/desktop/components/integrations-section"; -import { LocalDaemonSection } from "@/desktop/components/desktop-updates-section"; import { isElectronRuntime } from "@/desktop/host"; import { useDesktopAppUpdater } from "@/desktop/updates/use-desktop-app-updater"; import { formatVersionWithPrefix } from "@/desktop/updates/desktop-updates"; @@ -100,6 +99,10 @@ import ProjectsScreen from "@/screens/projects-screen"; import ProjectSettingsScreen from "@/screens/project-settings-screen"; import { useIsCompactFormFactor } from "@/constants/layout"; import { useLocalDaemonServerId } from "@/hooks/use-is-local-daemon"; +import { + type EnableBuiltInDaemonOption, + useEnableBuiltInDaemonOption, +} from "@/desktop/hooks/use-enable-built-in-daemon-option"; import { useWebScrollbarStyle } from "@/hooks/use-web-scrollbar-style"; import { buildOpenProjectRoute, @@ -131,7 +134,6 @@ interface SidebarSectionItem { const SIDEBAR_SECTION_ITEMS: SidebarSectionItem[] = [ { id: "general", labelKey: "settings.sections.general", icon: Settings }, - { id: "daemon", labelKey: "settings.sections.daemon", icon: Server, desktopOnly: true }, { id: "appearance", labelKey: "settings.sections.appearance", icon: Palette }, { id: "shortcuts", labelKey: "settings.sections.shortcuts", icon: Keyboard, desktopOnly: true }, { @@ -157,13 +159,13 @@ interface HostSectionItem { } const HOST_SECTION_ITEMS: HostSectionItem[] = [ + { id: "host", labelKey: "settings.hostSections.host", icon: Server }, { id: "connections", labelKey: "settings.hostSections.connections", icon: Network }, { id: "agents", labelKey: "settings.hostSections.agents", icon: Bot }, { id: "workspaces", labelKey: "settings.hostSections.workspaces", icon: FolderGit2 }, { id: "providers", labelKey: "settings.hostSections.providers", icon: Boxes }, { id: "usage", labelKey: "settings.hostSections.usage", icon: Gauge }, { id: "terminals", labelKey: "settings.hostSections.terminals", icon: SquareTerminal }, - { id: "host", labelKey: "settings.hostSections.host", icon: Server }, ]; function renderHostSettingsContent( @@ -893,6 +895,7 @@ interface HostPickerProps { sortedHosts: HostProfile[]; onSelectHost: (serverId: string) => void; onAddHost: () => void; + enableBuiltInDaemonOption: EnableBuiltInDaemonOption; } /** @@ -902,7 +905,13 @@ interface HostPickerProps { * is using right now; an "Add host" row is always reachable from the list — * even with a single host. */ -function HostPicker({ activeServerId, sortedHosts, onSelectHost, onAddHost }: HostPickerProps) { +function HostPicker({ + activeServerId, + sortedHosts, + onSelectHost, + onAddHost, + enableBuiltInDaemonOption, +}: HostPickerProps) { const { t } = useTranslation(); const [isOpen, setIsOpen] = useState(false); const triggerRef = useRef(null); @@ -932,6 +941,8 @@ function HostPicker({ activeServerId, sortedHosts, onSelectHost, onAddHost }: Ho anchorRef={triggerRef} includeAddHost onAddHost={onAddHost} + includeEnableBuiltInDaemon={enableBuiltInDaemonOption.visible} + onEnableBuiltInDaemon={enableBuiltInDaemonOption.onPress} showActiveConnection searchable={false} title={t("settings.hostPicker.switchHost")} @@ -990,6 +1001,7 @@ function SettingsSidebar({ const localServerId = useLocalDaemonServerId(); const sortedHosts = useSortedHosts(hosts, localServerId); const hasHosts = sortedHosts.length > 0; + const enableBuiltInDaemonOption = useEnableBuiltInDaemonOption(); const isDesktopApp = isElectronRuntime(); const items = SIDEBAR_SECTION_ITEMS.filter((item) => !item.desktopOnly || isDesktopApp); const insets = useSafeAreaInsets(); @@ -1036,6 +1048,7 @@ function SettingsSidebar({ sortedHosts={sortedHosts} onSelectHost={onSelectHost} onAddHost={onAddHost} + enableBuiltInDaemonOption={enableBuiltInDaemonOption} /> {HOST_SECTION_ITEMS.map((item) => ( + {enableBuiltInDaemonOption.visible ? ( + + + + {t("settings.enableBuiltInDaemon")} + + + ) : null} )} @@ -1387,8 +1414,6 @@ export default function SettingsScreen({ view, openAddHostIntent = null }: Setti handleTerminalScrollbackLinesChange={handleTerminalScrollbackLinesChange} /> ); - case "daemon": - return ; case "appearance": return ; case "shortcuts": diff --git a/packages/app/src/utils/host-routes.test.ts b/packages/app/src/utils/host-routes.test.ts index 67fb9d856..fbc8115fc 100644 --- a/packages/app/src/utils/host-routes.test.ts +++ b/packages/app/src/utils/host-routes.test.ts @@ -15,6 +15,7 @@ import { decodeWorkspaceIdFromPathSegment, encodeFilePathForPathSegment, encodeWorkspaceIdForPathSegment, + isSettingsSectionSlug, normalizeHostSectionSlug, parseHostAgentRouteFromPathname, parseHostWorkspaceOpenIntentFromPathname, @@ -266,6 +267,12 @@ describe("host settings section slugs", () => { }); }); +describe("settings section slugs", () => { + it("no longer treats daemon as a valid app-level settings section", () => { + expect(isSettingsSectionSlug("daemon")).toBe(false); + }); +}); + describe("resolveKnownHostRoute", () => { it("renders when the route host is still saved", () => { expect( diff --git a/packages/app/src/utils/host-routes.ts b/packages/app/src/utils/host-routes.ts index 944cbe437..189340cbb 100644 --- a/packages/app/src/utils/host-routes.ts +++ b/packages/app/src/utils/host-routes.ts @@ -482,7 +482,6 @@ export function resolveKnownHostRoute(input: { export const SETTINGS_SECTION_SLUGS = [ "general", - "daemon", "appearance", "shortcuts", "integrations",