Manage the built-in daemon from one place in settings (#1938)

* feat(settings): consolidate daemon management into the host page

The app-level Daemon section and the localhost host page rendered the
same daemon card in two places. The host page is now the single home:
the Host sidebar group leads with Overview, and a desktop-only "Enable
built-in daemon" option in the host switcher recovers a removed
localhost connection. Old settings/daemon deep links redirect
(COMPAT-tagged).

* fix(settings): hide daemon recovery option when the server id probe fails

Review feedback: !query.isLoading treated an errored IPC probe as
resolved, so the recovery affordance could show without knowing the
local server id. Replace the resolved-boolean hook with a single
discriminated state hook (loading | error | resolved) — errors hide the
affordance and fall back to the settings root in the redirect. Also
collapses the two query subscriptions per caller into one.

* fix(settings): keep daemon recovery reachable when no hosts remain

Review feedback: removing localhost as the only host takes the settings
sidebar down its no-host branch, which never mounts the host picker —
so the "Enable built-in daemon" option was unreachable in the most
common recovery case. The sidebar now owns the single hook call, passes
it to the picker, and renders the same action as a sidebar row next to
"Add host" when no hosts exist.

* test(e2e): update settings host header to Overview

Test drift: the host section's sidebar/header label was intentionally
renamed to "Overview"; the spec still asserted the old "Host" title.
Also assert the retired app-level Daemon row stays absent.
This commit is contained in:
Mohamed Boudra
2026-07-08 10:20:30 +02:00
committed by GitHub
parent 06c3161008
commit 4fb70668f2
19 changed files with 232 additions and 41 deletions
@@ -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 }>,
@@ -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<ComboboxProps["renderOption"]>;
@@ -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 size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />,
[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 (
<SystemHostPickerOption kind="enableBuiltInDaemon" active={active} onPress={onPress} />
);
}
return (
<HostPickerOption
serverId={option.id}