Remove primary host concept

This commit is contained in:
Mohamed Boudra
2025-11-26 17:51:25 +01:00
parent 3524b0facb
commit e6d2998904
4 changed files with 13 additions and 64 deletions
+1 -15
View File
@@ -323,7 +323,7 @@ type DaemonTestState = {
export default function SettingsScreen() { export default function SettingsScreen() {
const { settings, isLoading: settingsLoading, updateSettings, resetSettings } = useAppSettings(); const { settings, isLoading: settingsLoading, updateSettings, resetSettings } = useAppSettings();
const { daemons, isLoading: daemonLoading, addDaemon, updateDaemon, removeDaemon, setDefaultDaemon } = useDaemonRegistry(); const { daemons, isLoading: daemonLoading, addDaemon, updateDaemon, removeDaemon } = useDaemonRegistry();
const { activeDaemon, activeDaemonId, setActiveDaemonId, connectionStates, updateConnectionStatus } = useDaemonConnections(); const { activeDaemon, activeDaemonId, setActiveDaemonId, connectionStates, updateConnectionStatus } = useDaemonConnections();
const activeDaemonSession = useSessionForServer(activeDaemonId ?? null); const activeDaemonSession = useSessionForServer(activeDaemonId ?? null);
const activeDaemonConnection = activeDaemonId ? connectionStates.get(activeDaemonId) : null; const activeDaemonConnection = activeDaemonId ? connectionStates.get(activeDaemonId) : null;
@@ -511,13 +511,6 @@ export default function SettingsScreen() {
[removeDaemon] [removeDaemon]
); );
const handleSetDefaultDaemon = useCallback(
(profile: DaemonProfile) => {
void setDefaultDaemon(profile.id);
},
[setDefaultDaemon]
);
const updateDaemonTestState = useCallback((daemonId: string, state: { status: "idle" | "testing" | "success" | "error"; message?: string }) => { const updateDaemonTestState = useCallback((daemonId: string, state: { status: "idle" | "testing" | "success" | "error"; message?: string }) => {
setDaemonTestStates((prev) => { setDaemonTestStates((prev) => {
const next = new Map(prev); const next = new Map(prev);
@@ -617,7 +610,6 @@ export default function SettingsScreen() {
label: deriveDaemonLabel(trimmedUrl), label: deriveDaemonLabel(trimmedUrl),
wsUrl: trimmedUrl, wsUrl: trimmedUrl,
autoConnect: true, autoConnect: true,
isDefault: true,
}); });
} }
@@ -813,7 +805,6 @@ export default function SettingsScreen() {
connectionStatus={connectionStatus} connectionStatus={connectionStatus}
lastError={lastConnectionError} lastError={lastConnectionError}
testState={testState} testState={testState}
onSetDefault={handleSetDefaultDaemon}
onTestConnection={handleTestDaemonConnection} onTestConnection={handleTestDaemonConnection}
onEdit={handleOpenDaemonForm} onEdit={handleOpenDaemonForm}
onRemove={handleRemoveDaemon} onRemove={handleRemoveDaemon}
@@ -991,7 +982,6 @@ interface DaemonCardProps {
connectionStatus: ConnectionStatus; connectionStatus: ConnectionStatus;
lastError: string | null; lastError: string | null;
testState?: DaemonTestState; testState?: DaemonTestState;
onSetDefault: (daemon: DaemonProfile) => void;
onTestConnection: (daemon: DaemonProfile) => void; onTestConnection: (daemon: DaemonProfile) => void;
onEdit: (daemon: DaemonProfile) => void; onEdit: (daemon: DaemonProfile) => void;
onRemove: (daemon: DaemonProfile) => void; onRemove: (daemon: DaemonProfile) => void;
@@ -1006,7 +996,6 @@ function DaemonCard({
connectionStatus, connectionStatus,
lastError, lastError,
testState, testState,
onSetDefault,
onTestConnection, onTestConnection,
onEdit, onEdit,
onRemove, onRemove,
@@ -1172,9 +1161,6 @@ function DaemonCard({
</Text> </Text>
) : null} ) : null}
<View style={styles.daemonActionsRow}> <View style={styles.daemonActionsRow}>
<Pressable style={styles.daemonActionButton} onPress={() => onSetDefault(daemon)}>
<Text style={styles.daemonActionText}>{daemon.isDefault ? "Default" : "Make Default"}</Text>
</Pressable>
<Pressable <Pressable
style={[ style={[
styles.daemonActionButton, styles.daemonActionButton,
@@ -231,8 +231,10 @@ export function DaemonConnectionsProvider({ children }: { children: ReactNode })
return; return;
} }
const fallback = daemons.find((daemon) => daemon.isDefault) ?? daemons[0]; const fallback = daemons[0];
setActiveDaemonId(fallback.id, { source: "auto_fallback" }); if (fallback) {
setActiveDaemonId(fallback.id, { source: "auto_fallback" });
}
}, [daemons, activeDaemonId, activeDaemonPreference.isPending, persistActiveDaemonId, setActiveDaemonId]); }, [daemons, activeDaemonId, activeDaemonPreference.isPending, persistActiveDaemonId, setActiveDaemonId]);
const activeDaemon = useMemo(() => { const activeDaemon = useMemo(() => {
@@ -1,4 +1,4 @@
import { createContext, useCallback, useContext, useMemo } from "react"; import { createContext, useCallback, useContext } from "react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useQuery, useQueryClient } from "@tanstack/react-query";
@@ -14,7 +14,6 @@ export type DaemonProfile = {
wsUrl: string; wsUrl: string;
restUrl?: string | null; restUrl?: string | null;
autoConnect: boolean; autoConnect: boolean;
isDefault: boolean;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
metadata?: Record<string, unknown> | null; metadata?: Record<string, unknown> | null;
@@ -25,7 +24,6 @@ type CreateDaemonInput = {
wsUrl: string; wsUrl: string;
restUrl?: string | null; restUrl?: string | null;
autoConnect?: boolean; autoConnect?: boolean;
isDefault?: boolean;
}; };
type UpdateDaemonInput = Partial<Omit<DaemonProfile, "id" | "createdAt">>; type UpdateDaemonInput = Partial<Omit<DaemonProfile, "id" | "createdAt">>;
@@ -34,11 +32,9 @@ interface DaemonRegistryContextValue {
daemons: DaemonProfile[]; daemons: DaemonProfile[];
isLoading: boolean; isLoading: boolean;
error: unknown | null; error: unknown | null;
defaultDaemon: DaemonProfile | null;
addDaemon: (input: CreateDaemonInput) => Promise<DaemonProfile>; addDaemon: (input: CreateDaemonInput) => Promise<DaemonProfile>;
updateDaemon: (id: string, updates: UpdateDaemonInput) => Promise<void>; updateDaemon: (id: string, updates: UpdateDaemonInput) => Promise<void>;
removeDaemon: (id: string) => Promise<void>; removeDaemon: (id: string) => Promise<void>;
setDefaultDaemon: (id: string) => Promise<void>;
} }
const DaemonRegistryContext = createContext<DaemonRegistryContextValue | null>(null); const DaemonRegistryContext = createContext<DaemonRegistryContextValue | null>(null);
@@ -81,13 +77,12 @@ export function DaemonRegistryProvider({ children }: { children: ReactNode }) {
wsUrl: input.wsUrl, wsUrl: input.wsUrl,
restUrl: input.restUrl ?? null, restUrl: input.restUrl ?? null,
autoConnect: input.autoConnect ?? true, autoConnect: input.autoConnect ?? true,
isDefault: input.isDefault ?? existing.length === 0,
createdAt: timestamp, createdAt: timestamp,
updatedAt: timestamp, updatedAt: timestamp,
metadata: null, metadata: null,
}; };
const next = normalizeDefaults([...existing, profile]); const next = [...existing, profile];
await persist(next); await persist(next);
return profile; return profile;
}, [persist, readDaemons]); }, [persist, readDaemons]);
@@ -107,36 +102,17 @@ export function DaemonRegistryProvider({ children }: { children: ReactNode }) {
const removeDaemon = useCallback(async (id: string) => { const removeDaemon = useCallback(async (id: string) => {
const remaining = readDaemons().filter((daemon) => daemon.id !== id); const remaining = readDaemons().filter((daemon) => daemon.id !== id);
const normalized = normalizeDefaults(remaining); const next = remaining.length > 0 ? remaining : [createProfile("Local Host", FALLBACK_DAEMON_URL)];
await persist(normalized.length > 0 ? normalized : [createProfile("Local Host", FALLBACK_DAEMON_URL, true)]);
}, [persist, readDaemons]);
const setDefaultDaemon = useCallback(async (id: string) => {
const next = readDaemons().map((daemon) => ({
...daemon,
isDefault: daemon.id === id,
updatedAt: daemon.id === id ? new Date().toISOString() : daemon.updatedAt,
}));
await persist(next); await persist(next);
}, [persist, readDaemons]); }, [persist, readDaemons]);
const defaultDaemon = useMemo(() => {
if (daemons.length === 0) {
return null;
}
const explicit = daemons.find((daemon) => daemon.isDefault);
return explicit ?? daemons[0];
}, [daemons]);
const value: DaemonRegistryContextValue = { const value: DaemonRegistryContextValue = {
daemons, daemons,
isLoading: isPending, isLoading: isPending,
error: error ?? null, error: error ?? null,
defaultDaemon,
addDaemon, addDaemon,
updateDaemon, updateDaemon,
removeDaemon, removeDaemon,
setDefaultDaemon,
}; };
return ( return (
@@ -160,7 +136,7 @@ function deriveLabelFromUrl(url: string): string {
} }
} }
function createProfile(label: string, wsUrl: string, isDefault: boolean): DaemonProfile { function createProfile(label: string, wsUrl: string): DaemonProfile {
const timestamp = new Date().toISOString(); const timestamp = new Date().toISOString();
return { return {
id: generateDaemonId(), id: generateDaemonId(),
@@ -168,28 +144,12 @@ function createProfile(label: string, wsUrl: string, isDefault: boolean): Daemon
wsUrl, wsUrl,
restUrl: null, restUrl: null,
autoConnect: true, autoConnect: true,
isDefault,
createdAt: timestamp, createdAt: timestamp,
updatedAt: timestamp, updatedAt: timestamp,
metadata: null, metadata: null,
}; };
} }
function normalizeDefaults(profiles: DaemonProfile[]): DaemonProfile[] {
if (profiles.length === 0) {
return profiles;
}
const hasDefault = profiles.some((daemon) => daemon.isDefault);
if (hasDefault) {
return profiles;
}
const [first, ...rest] = profiles;
return [
{ ...first, isDefault: true },
...rest,
];
}
async function loadDaemonRegistryFromStorage(): Promise<DaemonProfile[]> { async function loadDaemonRegistryFromStorage(): Promise<DaemonProfile[]> {
try { try {
const stored = await AsyncStorage.getItem(REGISTRY_STORAGE_KEY); const stored = await AsyncStorage.getItem(REGISTRY_STORAGE_KEY);
@@ -202,13 +162,13 @@ async function loadDaemonRegistryFromStorage(): Promise<DaemonProfile[]> {
const legacyParsed = JSON.parse(legacy) as Record<string, unknown>; const legacyParsed = JSON.parse(legacy) as Record<string, unknown>;
const legacyUrl = typeof legacyParsed.serverUrl === "string" ? legacyParsed.serverUrl : null; const legacyUrl = typeof legacyParsed.serverUrl === "string" ? legacyParsed.serverUrl : null;
if (legacyUrl) { if (legacyUrl) {
const migrated = [createProfile("Primary Host", legacyUrl, true)]; const migrated = [createProfile("Primary Host", legacyUrl)];
await AsyncStorage.setItem(REGISTRY_STORAGE_KEY, JSON.stringify(migrated)); await AsyncStorage.setItem(REGISTRY_STORAGE_KEY, JSON.stringify(migrated));
return migrated; return migrated;
} }
} }
const fallback = [createProfile("Local Host", FALLBACK_DAEMON_URL, true)]; const fallback = [createProfile("Local Host", FALLBACK_DAEMON_URL)];
await AsyncStorage.setItem(REGISTRY_STORAGE_KEY, JSON.stringify(fallback)); await AsyncStorage.setItem(REGISTRY_STORAGE_KEY, JSON.stringify(fallback));
return fallback; return fallback;
} catch (error) { } catch (error) {
+2 -1
View File
@@ -23,7 +23,8 @@ The multi-daemon infrastructure is in place: session directory with daemon-scope
### 2. Remove Active/Primary/Auto-Connect Concepts ### 2. Remove Active/Primary/Auto-Connect Concepts
- [x] Remove the concept of "active daemon" from the UI and simplify to just "hosts". - [x] Remove the concept of "active daemon" from the UI and simplify to just "hosts".
- Removed the Settings "Set Active" controls/labels, scrubbed the copy that mentioned an active host (including git diff fallback text), and left the per-host cards focused on status/default/test actions; no automated tests were run. - Removed the Settings "Set Active" controls/labels, scrubbed the copy that mentioned an active host (including git diff fallback text), and left the per-host cards focused on status/default/test actions; no automated tests were run.
- [ ] Remove the concept of "primary daemon"—no default host for actions. - [x] Remove the concept of "primary daemon"—no default host for actions.
- Removed the Default host toggle/state in the registry and settings UI, updated host docs, and kept connection fallback logic simple; tests not run (not requested).
- [ ] Remove the "auto-connect" toggle from host settings—hosts always auto-connect when added. - [ ] Remove the "auto-connect" toggle from host settings—hosts always auto-connect when added.
- [ ] Clean up any related state/UI that exposes these concepts to users. - [ ] Clean up any related state/UI that exposes these concepts to users.