Remove primary host concept
This commit is contained in:
@@ -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) {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user