From 99b055d2f980b5ad8612d09a0c223a98e5d38cb0 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Wed, 26 Nov 2025 19:09:19 +0100 Subject: [PATCH] Remove global settings test form --- packages/app/src/app/settings.tsx | 285 +----------------------------- plan.md | 7 +- 2 files changed, 7 insertions(+), 285 deletions(-) diff --git a/packages/app/src/app/settings.tsx b/packages/app/src/app/settings.tsx index d66c6a514..9ae4dea1b 100644 --- a/packages/app/src/app/settings.tsx +++ b/packages/app/src/app/settings.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useRef, useCallback, useMemo } from "react"; +import { useState, useEffect, useRef, useCallback } from "react"; import type { MutableRefObject } from "react"; import { View, @@ -59,31 +59,6 @@ const styles = StyleSheet.create((theme) => ({ fontWeight: theme.fontWeight.semibold, marginBottom: theme.spacing[4], }, - hostSelectorRow: { - flexDirection: "row", - flexWrap: "wrap", - gap: theme.spacing[2], - marginBottom: theme.spacing[3], - }, - hostSelectorChip: { - paddingVertical: theme.spacing[1], - paddingHorizontal: theme.spacing[3], - borderRadius: theme.borderRadius.full, - backgroundColor: theme.colors.card, - borderWidth: theme.borderWidth[1], - borderColor: theme.colors.border, - }, - hostSelectorChipSelected: { - backgroundColor: theme.colors.foreground, - borderColor: theme.colors.foreground, - }, - hostSelectorChipText: { - color: theme.colors.mutedForeground, - fontSize: theme.fontSize.xs, - }, - hostSelectorChipTextSelected: { - color: theme.colors.background, - }, label: { color: theme.colors.mutedForeground, fontSize: theme.fontSize.sm, @@ -96,51 +71,6 @@ const styles = StyleSheet.create((theme) => ({ borderRadius: theme.borderRadius.lg, marginBottom: theme.spacing[2], }, - inputDisabled: { - opacity: theme.opacity[50], - }, - helperText: { - color: theme.colors.mutedForeground, - fontSize: theme.fontSize.xs, - marginBottom: theme.spacing[3], - }, - testButton: { - padding: theme.spacing[3], - borderRadius: theme.borderRadius.lg, - marginBottom: theme.spacing[3], - flexDirection: "row", - alignItems: "center", - justifyContent: "center", - backgroundColor: theme.colors.palette.blue[600], - }, - testButtonDisabled: { - backgroundColor: theme.colors.muted, - }, - testButtonText: { - color: theme.colors.palette.white, - fontWeight: theme.fontWeight.semibold, - marginLeft: theme.spacing[2], - }, - testResultSuccess: { - padding: theme.spacing[3], - borderRadius: theme.borderRadius.lg, - backgroundColor: theme.colors.palette.green[900], - borderWidth: theme.borderWidth[1], - borderColor: theme.colors.palette.green[600], - }, - testResultError: { - padding: theme.spacing[3], - borderRadius: theme.borderRadius.lg, - backgroundColor: theme.colors.palette.red[900], - borderWidth: theme.borderWidth[1], - borderColor: theme.colors.palette.red[600], - }, - testResultTextSuccess: { - color: theme.colors.palette.green[200], - }, - testResultTextError: { - color: theme.colors.palette.red[200], - }, settingCard: { backgroundColor: theme.colors.card, borderRadius: theme.borderRadius.lg, @@ -350,53 +280,16 @@ export default function SettingsScreen() { const { settings, isLoading: settingsLoading, updateSettings, resetSettings } = useAppSettings(); const { daemons, isLoading: daemonLoading, addDaemon, updateDaemon, removeDaemon } = useDaemonRegistry(); const { connectionStates, updateConnectionStatus } = useDaemonConnections(); - const [selectedDaemonId, setSelectedDaemonId] = useState(null); - const selectedDaemon = useMemo(() => { - if (!selectedDaemonId) { - return null; - } - return daemons.find((daemon) => daemon.id === selectedDaemonId) ?? null; - }, [daemons, selectedDaemonId]); - const selectedDaemonSession = useSessionForServer(selectedDaemonId); - const selectedDaemonConnection = selectedDaemonId ? connectionStates.get(selectedDaemonId) : null; - const selectedDaemonStatusLabel = selectedDaemonConnection ? formatConnectionStatus(selectedDaemonConnection.status) : null; - - const [serverUrl, setServerUrl] = useState(selectedDaemon?.wsUrl ?? ""); const [useSpeaker, setUseSpeaker] = useState(settings.useSpeaker); const [keepScreenOn, setKeepScreenOn] = useState(settings.keepScreenOn); const [theme, setTheme] = useState<"dark" | "light" | "auto">(settings.theme); const [hasChanges, setHasChanges] = useState(false); - const [isTesting, setIsTesting] = useState(false); - const [testResult, setTestResult] = useState<{ - success: boolean; - message: string; - } | null>(null); const [isDaemonFormVisible, setIsDaemonFormVisible] = useState(false); const [daemonForm, setDaemonForm] = useState<{ id: string | null; label: string; wsUrl: string }>({ id: null, label: "", wsUrl: "" }); const [isSavingDaemon, setIsSavingDaemon] = useState(false); const [daemonTestStates, setDaemonTestStates] = useState>(() => new Map()); const isLoading = settingsLoading || daemonLoading; - const baselineServerUrl = selectedDaemon?.wsUrl ?? ""; const isMountedRef = useRef(true); - const isServerConfigLocked = Boolean(selectedDaemon && !selectedDaemonSession); - const serverDescriptionText = selectedDaemon - ? `${selectedDaemon.label}${selectedDaemonStatusLabel ? ` - ${selectedDaemonStatusLabel}` : ""}${ - isServerConfigLocked ? " - Session unavailable" : "" - }` - : "Add a host to configure its server URL."; - const serverHelperText = isServerConfigLocked - ? `Connect to ${selectedDaemon?.label ?? "this host"} to edit its server URL.` - : "Must be a valid WebSocket URL (ws:// or wss://)"; - - useEffect(() => { - if (daemons.length === 0) { - setSelectedDaemonId(null); - return; - } - if (!selectedDaemonId || !daemons.some((daemon) => daemon.id === selectedDaemonId)) { - setSelectedDaemonId(daemons[0].id); - } - }, [daemons, selectedDaemonId]); useEffect(() => { return () => { @@ -510,10 +403,8 @@ export default function SettingsScreen() { }; if (daemonForm.id) { await updateDaemon(daemonForm.id, payload); - setSelectedDaemonId(daemonForm.id); } else { - const created = await addDaemon(payload); - setSelectedDaemonId(created.id); + await addDaemon(payload); } handleCloseDaemonForm(); } catch (error) { @@ -586,19 +477,14 @@ export default function SettingsScreen() { setTheme(settings.theme); }, [settings]); - useEffect(() => { - setServerUrl(selectedDaemon?.wsUrl ?? ""); - }, [selectedDaemon?.wsUrl]); - // Track changes useEffect(() => { const changed = - serverUrl !== baselineServerUrl || useSpeaker !== settings.useSpeaker || keepScreenOn !== settings.keepScreenOn || theme !== settings.theme; setHasChanges(changed); - }, [serverUrl, baselineServerUrl, useSpeaker, keepScreenOn, theme, settings]); + }, [useSpeaker, keepScreenOn, theme, settings]); function validateServerUrl(url: string): boolean { try { @@ -609,49 +495,14 @@ export default function SettingsScreen() { } } - function deriveDaemonLabel(url: string): string { - try { - const parsed = new URL(url); - return parsed.hostname || "Host"; - } catch { - return "Host"; - } - } - async function handleSave() { - // Validate server URL - if (!validateServerUrl(serverUrl)) { - Alert.alert( - "Invalid URL", - "Server URL must be a valid WebSocket URL (ws:// or wss://)", - [{ text: "OK" }] - ); - return; - } - try { - const trimmedUrl = serverUrl.trim(); - await updateSettings({ useSpeaker, keepScreenOn, theme, }); - if (selectedDaemon) { - await updateDaemon(selectedDaemon.id, { - wsUrl: trimmedUrl, - label: selectedDaemon.label || deriveDaemonLabel(trimmedUrl), - }); - setSelectedDaemonId(selectedDaemon.id); - } else { - const created = await addDaemon({ - label: deriveDaemonLabel(trimmedUrl), - wsUrl: trimmedUrl, - }); - setSelectedDaemonId(created.id); - } - Alert.alert( "Settings Saved", "Your settings have been saved successfully.", @@ -700,48 +551,6 @@ export default function SettingsScreen() { const restartConfirmationMessage = "This will immediately stop the Voice Dev backend process. The app will disconnect until it restarts."; - - async function handleTestConnection() { - if (isServerConfigLocked) { - Alert.alert( - "Session unavailable", - `${selectedDaemon?.label ?? "This host"} is not connected. Connect to it before testing the URL.` - ); - return; - } - - if (!validateServerUrl(serverUrl)) { - Alert.alert( - "Invalid URL", - "Server URL must be a valid WebSocket URL (ws:// or wss://)", - [{ text: "OK" }] - ); - return; - } - - setIsTesting(true); - setTestResult(null); - - try { - await testServerConnection(serverUrl); - setTestResult({ - success: true, - message: "Connection successful", - }); - } catch (error) { - const message = - error instanceof Error - ? error.message - : "Connection failed - check URL and network"; - setTestResult({ - success: false, - message, - }); - } finally { - setIsTesting(false); - } - } - if (isLoading) { return ( @@ -756,94 +565,6 @@ export default function SettingsScreen() { - {/* Server Configuration */} - - Server Configuration - {daemons.length > 0 ? ( - - {daemons.map((daemon) => { - const isSelected = daemon.id === selectedDaemonId; - return ( - setSelectedDaemonId(daemon.id)} - > - - {daemon.label} - - - ); - })} - - ) : null} - {serverDescriptionText} - - WebSocket URL - { - setServerUrl(text); - setTestResult(null); - }} - autoCapitalize="none" - autoCorrect={false} - keyboardType="url" - editable={!isServerConfigLocked} - selectTextOnFocus={!isServerConfigLocked} - /> - - {serverHelperText} - - - {/* Test Connection Button */} - - {isTesting ? ( - <> - - Testing... - - ) : ( - Test Connection - )} - - - {/* Test Result */} - {testResult && ( - - - {testResult.message} - - - )} - - {/* Host Management */} Hosts diff --git a/plan.md b/plan.md index 18ce76079..d7636aaf2 100644 --- a/plan.md +++ b/plan.md @@ -39,7 +39,8 @@ The multi-daemon infrastructure is in place: session directory with daemon-scope - Create/import modal now requires explicit host selection (no first entry default) and `useAggregatedAgents` no longer fabricates a host id via `connectionStates.keys().next()`; verified with `npm run typecheck --workspace=@paseo/app`. ### 3. Simplify Settings Screen -- [ ] Remove the standalone "Test Connection" form/URL input at the top of settings. +- [x] Remove the standalone "Test Connection" form/URL input at the top of settings. + - Removed the global host selector/Test UI from `packages/app/src/app/settings.tsx` so configuration happens per-host within their cards/forms, and ran `npm run typecheck --workspace=@paseo/app`. - [ ] Keep the per-host "Test" button in each host row (already exists). ### 4. Transparent Connection Management @@ -52,9 +53,9 @@ The multi-daemon infrastructure is in place: session directory with daemon-scope - [ ] Only show errors when the user tries to interact with an agent whose host is stopped. - [ ] Update connection banners/indicators to show neutral "offline" state instead of error styling. - [ ] Make the Git Diff offline/unavailable state neutral and stop instructing users to "connect" manually. - - Context: `packages/app/src/app/git-diff.tsx:233-241` still renders the destructive error container and tells users "Connect this host or switch to another one to continue," violating the auto-connect guidance. + - Context (review): `packages/app/src/app/git-diff.tsx:229-242` still renders the destructive error container and tells users "Connect this host or switch to another one to continue," so the UI still contradicts the auto-connect guidance even after the wording change. - [ ] Update the File Explorer offline state to match the new philosophy (neutral messaging, no manual connect CTA). - - Context: `packages/app/src/app/file-explorer.tsx:690-698` presents the offline host as an error and asks users to "Connect this host and try again." + - Context (review): `packages/app/src/app/file-explorer.tsx:690-699` still uses the destructive error styling and the "Connect this host and try again" CTA instead of the neutral offline messaging the new philosophy calls for. - [ ] Audit the agent detail flows for the same issue (offline agent screen + delete sheet) and replace the "connect this host" requirement with passive/offline messaging. - Context: `packages/app/src/app/agent/[serverId]/[agentId].tsx:651-660` and `packages/app/src/components/agent-list.tsx:149` continue to show destructive states directing users to manually connect before managing agents.