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() {
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 activeDaemonSession = useSessionForServer(activeDaemonId ?? null);
const activeDaemonConnection = activeDaemonId ? connectionStates.get(activeDaemonId) : null;
@@ -511,13 +511,6 @@ export default function SettingsScreen() {
[removeDaemon]
);
const handleSetDefaultDaemon = useCallback(
(profile: DaemonProfile) => {
void setDefaultDaemon(profile.id);
},
[setDefaultDaemon]
);
const updateDaemonTestState = useCallback((daemonId: string, state: { status: "idle" | "testing" | "success" | "error"; message?: string }) => {
setDaemonTestStates((prev) => {
const next = new Map(prev);
@@ -617,7 +610,6 @@ export default function SettingsScreen() {
label: deriveDaemonLabel(trimmedUrl),
wsUrl: trimmedUrl,
autoConnect: true,
isDefault: true,
});
}
@@ -813,7 +805,6 @@ export default function SettingsScreen() {
connectionStatus={connectionStatus}
lastError={lastConnectionError}
testState={testState}
onSetDefault={handleSetDefaultDaemon}
onTestConnection={handleTestDaemonConnection}
onEdit={handleOpenDaemonForm}
onRemove={handleRemoveDaemon}
@@ -991,7 +982,6 @@ interface DaemonCardProps {
connectionStatus: ConnectionStatus;
lastError: string | null;
testState?: DaemonTestState;
onSetDefault: (daemon: DaemonProfile) => void;
onTestConnection: (daemon: DaemonProfile) => void;
onEdit: (daemon: DaemonProfile) => void;
onRemove: (daemon: DaemonProfile) => void;
@@ -1006,7 +996,6 @@ function DaemonCard({
connectionStatus,
lastError,
testState,
onSetDefault,
onTestConnection,
onEdit,
onRemove,
@@ -1172,9 +1161,6 @@ function DaemonCard({
</Text>
) : null}
<View style={styles.daemonActionsRow}>
<Pressable style={styles.daemonActionButton} onPress={() => onSetDefault(daemon)}>
<Text style={styles.daemonActionText}>{daemon.isDefault ? "Default" : "Make Default"}</Text>
</Pressable>
<Pressable
style={[
styles.daemonActionButton,
@@ -231,8 +231,10 @@ export function DaemonConnectionsProvider({ children }: { children: ReactNode })
return;
}
const fallback = daemons.find((daemon) => daemon.isDefault) ?? daemons[0];
setActiveDaemonId(fallback.id, { source: "auto_fallback" });
const fallback = daemons[0];
if (fallback) {
setActiveDaemonId(fallback.id, { source: "auto_fallback" });
}
}, [daemons, activeDaemonId, activeDaemonPreference.isPending, persistActiveDaemonId, setActiveDaemonId]);
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 AsyncStorage from "@react-native-async-storage/async-storage";
import { useQuery, useQueryClient } from "@tanstack/react-query";
@@ -14,7 +14,6 @@ export type DaemonProfile = {
wsUrl: string;
restUrl?: string | null;
autoConnect: boolean;
isDefault: boolean;
createdAt: string;
updatedAt: string;
metadata?: Record<string, unknown> | null;
@@ -25,7 +24,6 @@ type CreateDaemonInput = {
wsUrl: string;
restUrl?: string | null;
autoConnect?: boolean;
isDefault?: boolean;
};
type UpdateDaemonInput = Partial<Omit<DaemonProfile, "id" | "createdAt">>;
@@ -34,11 +32,9 @@ interface DaemonRegistryContextValue {
daemons: DaemonProfile[];
isLoading: boolean;
error: unknown | null;
defaultDaemon: DaemonProfile | null;
addDaemon: (input: CreateDaemonInput) => Promise<DaemonProfile>;
updateDaemon: (id: string, updates: UpdateDaemonInput) => Promise<void>;
removeDaemon: (id: string) => Promise<void>;
setDefaultDaemon: (id: string) => Promise<void>;
}
const DaemonRegistryContext = createContext<DaemonRegistryContextValue | null>(null);
@@ -81,13 +77,12 @@ export function DaemonRegistryProvider({ children }: { children: ReactNode }) {
wsUrl: input.wsUrl,
restUrl: input.restUrl ?? null,
autoConnect: input.autoConnect ?? true,
isDefault: input.isDefault ?? existing.length === 0,
createdAt: timestamp,
updatedAt: timestamp,
metadata: null,
};
const next = normalizeDefaults([...existing, profile]);
const next = [...existing, profile];
await persist(next);
return profile;
}, [persist, readDaemons]);
@@ -107,36 +102,17 @@ export function DaemonRegistryProvider({ children }: { children: ReactNode }) {
const removeDaemon = useCallback(async (id: string) => {
const remaining = readDaemons().filter((daemon) => daemon.id !== id);
const normalized = normalizeDefaults(remaining);
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,
}));
const next = remaining.length > 0 ? remaining : [createProfile("Local Host", FALLBACK_DAEMON_URL)];
await persist(next);
}, [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 = {
daemons,
isLoading: isPending,
error: error ?? null,
defaultDaemon,
addDaemon,
updateDaemon,
removeDaemon,
setDefaultDaemon,
};
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();
return {
id: generateDaemonId(),
@@ -168,28 +144,12 @@ function createProfile(label: string, wsUrl: string, isDefault: boolean): Daemon
wsUrl,
restUrl: null,
autoConnect: true,
isDefault,
createdAt: timestamp,
updatedAt: timestamp,
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[]> {
try {
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 legacyUrl = typeof legacyParsed.serverUrl === "string" ? legacyParsed.serverUrl : null;
if (legacyUrl) {
const migrated = [createProfile("Primary Host", legacyUrl, true)];
const migrated = [createProfile("Primary Host", legacyUrl)];
await AsyncStorage.setItem(REGISTRY_STORAGE_KEY, JSON.stringify(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));
return fallback;
} catch (error) {