refactor(desktop): simplify managed runtime by removing state file and delegating to CLI daemon status

This commit is contained in:
Mohamed Boudra
2026-03-11 19:01:49 +07:00
parent 327b315610
commit 4c6d21af4a
16 changed files with 1010 additions and 1785 deletions
@@ -1,21 +1,28 @@
import { useCallback, useEffect, useState } from "react";
import { ActivityIndicator, Alert, Image, Text, View } from "react-native";
import * as Clipboard from "expo-clipboard";
import * as QRCode from "qrcode";
import { useFocusEffect } from "@react-navigation/native";
import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { settingsStyles } from "@/styles/settings";
import { ArrowUpRight, Play, Pause, RotateCw, Terminal, Copy, FileText, Smartphone } from "lucide-react-native";
import { AdaptiveModalSheet } from "@/components/adaptive-modal-sheet";
import { Button } from "@/components/ui/button";
import { useAppSettings } from "@/hooks/use-settings";
import { confirmDialog } from "@/utils/confirm-dialog";
import { openExternalUrl } from "@/utils/open-external-url";
import { useCallback, useEffect, useState } from 'react'
import { ActivityIndicator, Alert, Image, Text, View } from 'react-native'
import * as Clipboard from 'expo-clipboard'
import * as QRCode from 'qrcode'
import { useFocusEffect } from '@react-navigation/native'
import { StyleSheet, useUnistyles } from 'react-native-unistyles'
import { settingsStyles } from '@/styles/settings'
import {
formatVersionWithPrefix,
isVersionMismatch,
} from "@/desktop/updates/desktop-updates";
ArrowUpRight,
Play,
Pause,
RotateCw,
Terminal,
Copy,
FileText,
Smartphone,
} from 'lucide-react-native'
import { AdaptiveModalSheet } from '@/components/adaptive-modal-sheet'
import { Button } from '@/components/ui/button'
import { useAppSettings } from '@/hooks/use-settings'
import { confirmDialog } from '@/utils/confirm-dialog'
import { openExternalUrl } from '@/utils/open-external-url'
import { formatVersionWithPrefix, isVersionMismatch } from '@/desktop/updates/desktop-updates'
import {
getCliShimStatus,
getManagedDaemonLogs,
getManagedDaemonPairing,
getManagedDaemonStatus,
@@ -29,307 +36,304 @@ import {
type ManagedPairingOffer,
type ManagedDaemonStatus,
type CliManualInstructions,
} from "@/desktop/managed-runtime/managed-runtime";
type CliShimStatus,
} from '@/desktop/managed-runtime/managed-runtime'
export interface LocalDaemonSectionProps {
appVersion: string | null;
appVersion: string | null
}
export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
const { theme } = useUnistyles();
const showSection = shouldUseManagedDesktopDaemon();
const { settings, updateSettings } = useAppSettings();
const [managedStatus, setManagedStatus] = useState<ManagedDaemonStatus | null>(null);
const [statusError, setStatusError] = useState<string | null>(null);
const [isRestartingDaemon, setIsRestartingDaemon] = useState(false);
const [isUpdatingDaemonManagement, setIsUpdatingDaemonManagement] = useState(false);
const [isInstallingCli, setIsInstallingCli] = useState(false);
const [statusMessage, setStatusMessage] = useState<string | null>(null);
const [cliStatusMessage, setCliStatusMessage] = useState<string | null>(null);
const [managedLogs, setManagedLogs] = useState<ManagedDaemonLogs | null>(null);
const [isLogsModalOpen, setIsLogsModalOpen] = useState(false);
const [isPairingModalOpen, setIsPairingModalOpen] = useState(false);
const [isCliInstallModalOpen, setIsCliInstallModalOpen] = useState(false);
const [isLoadingPairing, setIsLoadingPairing] = useState(false);
const [pairingOffer, setPairingOffer] = useState<ManagedPairingOffer | null>(null);
const [cliInstallInstructions, setCliInstallInstructions] = useState<CliManualInstructions | null>(
null
);
const [pairingStatusMessage, setPairingStatusMessage] = useState<string | null>(null);
const { theme } = useUnistyles()
const showSection = shouldUseManagedDesktopDaemon()
const { settings, updateSettings } = useAppSettings()
const [managedStatus, setManagedStatus] = useState<ManagedDaemonStatus | null>(null)
const [statusError, setStatusError] = useState<string | null>(null)
const [isRestartingDaemon, setIsRestartingDaemon] = useState(false)
const [isUpdatingDaemonManagement, setIsUpdatingDaemonManagement] = useState(false)
const [isInstallingCli, setIsInstallingCli] = useState(false)
const [statusMessage, setStatusMessage] = useState<string | null>(null)
const [cliStatusMessage, setCliStatusMessage] = useState<string | null>(null)
const [managedLogs, setManagedLogs] = useState<ManagedDaemonLogs | null>(null)
const [cliShimStatus, setCliShimStatus] = useState<CliShimStatus | null>(null)
const [isLogsModalOpen, setIsLogsModalOpen] = useState(false)
const [isPairingModalOpen, setIsPairingModalOpen] = useState(false)
const [isCliInstallModalOpen, setIsCliInstallModalOpen] = useState(false)
const [isLoadingPairing, setIsLoadingPairing] = useState(false)
const [pairingOffer, setPairingOffer] = useState<ManagedPairingOffer | null>(null)
const [cliInstallInstructions, setCliInstallInstructions] =
useState<CliManualInstructions | null>(null)
const [pairingStatusMessage, setPairingStatusMessage] = useState<string | null>(null)
const loadManagedStatus = useCallback(() => {
if (!showSection) {
return Promise.resolve();
return Promise.resolve()
}
return Promise.all([getManagedDaemonStatus(), getManagedDaemonLogs()])
.then(([status, logs]) => {
setManagedStatus(status);
setManagedLogs(logs);
setStatusError(null);
return Promise.all([getManagedDaemonStatus(), getManagedDaemonLogs(), getCliShimStatus()])
.then(([status, logs, shimStatus]) => {
setManagedStatus(status)
setManagedLogs(logs)
setCliShimStatus(shimStatus)
setStatusError(null)
})
.catch((error) => {
const message = error instanceof Error ? error.message : String(error);
setStatusError(message);
});
}, [showSection]);
const message = error instanceof Error ? error.message : String(error)
setStatusError(message)
})
}, [showSection])
useFocusEffect(
useCallback(() => {
if (!showSection) {
return undefined;
return undefined
}
void loadManagedStatus();
return undefined;
void loadManagedStatus()
return undefined
}, [loadManagedStatus, showSection])
);
)
const localDaemonVersionText = formatVersionWithPrefix(managedStatus?.runtimeVersion ?? null);
const daemonVersionMismatch = isVersionMismatch(appVersion, managedStatus?.runtimeVersion ?? null);
const localDaemonVersionText = formatVersionWithPrefix(managedStatus?.runtimeVersion ?? null)
const daemonVersionMismatch = isVersionMismatch(appVersion, managedStatus?.runtimeVersion ?? null)
const daemonStatusStateText =
statusError ??
(managedStatus?.daemonRunning
? managedStatus?.daemonStatus ?? "running"
: "not running");
const daemonStatusDetailText = `PID ${managedStatus?.daemonPid ? managedStatus.daemonPid : "—"}`;
const isDaemonManagementPaused = !settings.manageBuiltInDaemon;
const daemonActionLabel = managedStatus?.daemonRunning ? "Restart daemon" : "Start daemon";
const daemonActionMessage = managedStatus?.daemonRunning
? "Restarts the built-in daemon."
: isDaemonManagementPaused
? "Starts the built-in daemon manually. Paseo will not auto-start it while paused."
: "Starts the built-in daemon.";
statusError ?? (managedStatus?.status === 'running' ? managedStatus.status : 'not running')
const daemonStatusDetailText = `PID ${managedStatus?.pid ? managedStatus.pid : '—'}`
const isDaemonManagementPaused = !settings.manageBuiltInDaemon
const daemonActionLabel = managedStatus?.status === 'running' ? 'Restart daemon' : 'Start daemon'
const daemonActionMessage =
managedStatus?.status === 'running'
? 'Restarts the built-in daemon.'
: 'Starts the built-in daemon.'
const handleUpdateLocalDaemon = useCallback(() => {
if (!showSection) {
return;
return
}
if (isRestartingDaemon) {
return;
return
}
void confirmDialog({
title: daemonActionLabel,
message: managedStatus?.daemonRunning
? "This will restart the built-in daemon. The app will reconnect automatically."
: "This will start the built-in daemon.",
message:
managedStatus?.status === 'running'
? 'This will restart the built-in daemon. The app will reconnect automatically.'
: 'This will start the built-in daemon.',
confirmLabel: daemonActionLabel,
cancelLabel: "Cancel",
cancelLabel: 'Cancel',
})
.then((confirmed) => {
if (!confirmed) {
return;
return
}
setIsRestartingDaemon(true);
setStatusMessage(null);
setIsRestartingDaemon(true)
setStatusMessage(null)
const action = managedStatus?.daemonRunning ? restartManagedDaemon : startManagedDaemon;
const action =
managedStatus?.status === 'running' ? restartManagedDaemon : startManagedDaemon
void action()
.then((status) => {
setManagedStatus(status);
setManagedStatus(status)
setStatusMessage(
managedStatus?.daemonRunning ? "Daemon restarted." : "Daemon started."
);
return loadManagedStatus();
managedStatus?.status === 'running' ? 'Daemon restarted.' : 'Daemon started.'
)
return loadManagedStatus()
})
.catch((error) => {
console.error("[Settings] Failed to change managed daemon state", error);
const message = error instanceof Error ? error.message : String(error);
setStatusMessage(`${daemonActionLabel} failed: ${message}`);
console.error('[Settings] Failed to change managed daemon state', error)
const message = error instanceof Error ? error.message : String(error)
setStatusMessage(`${daemonActionLabel} failed: ${message}`)
})
.finally(() => {
setIsRestartingDaemon(false);
});
setIsRestartingDaemon(false)
})
})
.catch((error) => {
console.error("[Settings] Failed to open managed daemon action confirmation", error);
Alert.alert("Error", "Unable to open the daemon confirmation dialog.");
});
}, [daemonActionLabel, isRestartingDaemon, loadManagedStatus, managedStatus?.daemonRunning, showSection]);
console.error('[Settings] Failed to open managed daemon action confirmation', error)
Alert.alert('Error', 'Unable to open the daemon confirmation dialog.')
})
}, [daemonActionLabel, isRestartingDaemon, loadManagedStatus, managedStatus?.status, showSection])
const handleToggleDaemonManagement = useCallback(() => {
if (isUpdatingDaemonManagement) {
return;
return
}
if (!settings.manageBuiltInDaemon) {
setIsUpdatingDaemonManagement(true);
setStatusMessage(null);
setIsUpdatingDaemonManagement(true)
setStatusMessage(null)
void updateSettings({ manageBuiltInDaemon: true })
.then(() => {
setStatusMessage("Paseo will resume managing the built-in daemon on startup.");
setStatusMessage('Built-in daemon management resumed.')
})
.catch((error) => {
console.error("[Settings] Failed to update built-in daemon management", error);
Alert.alert("Error", "Unable to update built-in daemon management.");
console.error('[Settings] Failed to update built-in daemon management', error)
Alert.alert('Error', 'Unable to update built-in daemon management.')
})
.finally(() => {
setIsUpdatingDaemonManagement(false);
});
return;
setIsUpdatingDaemonManagement(false)
})
return
}
void confirmDialog({
title: "Pause built-in daemon",
title: 'Pause built-in daemon',
message:
"This will stop the built-in daemon immediately and prevent Paseo from auto-starting it on launch. Running agents and terminals connected to the built-in daemon will be stopped.",
confirmLabel: "Pause and stop",
cancelLabel: "Cancel",
'This will stop the built-in daemon immediately. Running agents and terminals connected to the built-in daemon will be stopped.',
confirmLabel: 'Pause and stop',
cancelLabel: 'Cancel',
destructive: true,
})
.then((confirmed) => {
if (!confirmed) {
return;
return
}
setIsUpdatingDaemonManagement(true);
setStatusMessage(null);
setIsUpdatingDaemonManagement(true)
setStatusMessage(null)
const stopPromise = managedStatus?.daemonRunning
? stopManagedDaemon()
: Promise.resolve(managedStatus ?? null);
const stopPromise =
managedStatus?.status === 'running'
? stopManagedDaemon()
: Promise.resolve(managedStatus ?? null)
void stopPromise
.then(() => updateSettings({ manageBuiltInDaemon: false }))
.then(() => loadManagedStatus())
.then(() => {
setStatusMessage(
"Paseo paused the built-in daemon and will no longer auto-start it on launch."
);
setStatusMessage('Built-in daemon paused and stopped.')
})
.catch((error) => {
console.error("[Settings] Failed to pause built-in daemon management", error);
Alert.alert("Error", "Unable to pause built-in daemon management.");
console.error('[Settings] Failed to pause built-in daemon management', error)
Alert.alert('Error', 'Unable to pause built-in daemon management.')
})
.finally(() => {
setIsUpdatingDaemonManagement(false);
});
setIsUpdatingDaemonManagement(false)
})
})
.catch((error) => {
console.error("[Settings] Failed to open built-in daemon pause confirmation", error);
Alert.alert("Error", "Unable to open the daemon confirmation dialog.");
});
console.error('[Settings] Failed to open built-in daemon pause confirmation', error)
Alert.alert('Error', 'Unable to open the daemon confirmation dialog.')
})
}, [
isUpdatingDaemonManagement,
loadManagedStatus,
managedStatus,
settings.manageBuiltInDaemon,
updateSettings,
]);
])
const handleToggleCliShim = useCallback(() => {
if (!showSection || isInstallingCli) {
return;
return
}
setIsInstallingCli(true);
const isInstalling = !managedStatus?.cliShimPath;
setIsInstallingCli(true)
const isInstalling = !cliShimStatus?.path
setCliStatusMessage(
isInstalling
? "A permissions popup may appear while Paseo installs the CLI globally."
: null
);
const action = managedStatus?.cliShimPath ? uninstallManagedCliShim : installManagedCliShim;
isInstalling ? 'A permissions popup may appear while Paseo installs the CLI globally.' : null
)
const action = cliShimStatus?.path ? uninstallManagedCliShim : installManagedCliShim
void action()
.then((result) => {
setCliStatusMessage(result.message);
setCliStatusMessage(result.message)
if (result.manualInstructions) {
setCliInstallInstructions(result.manualInstructions);
setIsCliInstallModalOpen(true);
setCliInstallInstructions(result.manualInstructions)
setIsCliInstallModalOpen(true)
} else {
setCliInstallInstructions(null);
setIsCliInstallModalOpen(false);
setCliInstallInstructions(null)
setIsCliInstallModalOpen(false)
}
return loadManagedStatus();
return loadManagedStatus()
})
.catch((error) => {
const message = error instanceof Error ? error.message : String(error);
setCliStatusMessage(`CLI install failed: ${message}`);
const message = error instanceof Error ? error.message : String(error)
setCliStatusMessage(`CLI install failed: ${message}`)
})
.finally(() => {
setIsInstallingCli(false);
});
}, [isInstallingCli, loadManagedStatus, managedStatus?.cliShimPath, showSection]);
setIsInstallingCli(false)
})
}, [cliShimStatus?.path, isInstallingCli, loadManagedStatus, showSection])
const handleCopyCliInstallCommands = useCallback(() => {
if (!cliInstallInstructions?.commands) {
return;
return
}
void Clipboard.setStringAsync(cliInstallInstructions.commands)
.then(() => {
Alert.alert("Copied", "CLI install commands copied.");
Alert.alert('Copied', 'CLI install commands copied.')
})
.catch((error) => {
console.error("[Settings] Failed to copy CLI install commands", error);
Alert.alert("Error", "Unable to copy CLI install commands.");
});
}, [cliInstallInstructions?.commands]);
console.error('[Settings] Failed to copy CLI install commands', error)
Alert.alert('Error', 'Unable to copy CLI install commands.')
})
}, [cliInstallInstructions?.commands])
const handleCopyLogPath = useCallback(() => {
const logPath = managedLogs?.logPath ?? managedStatus?.logPath;
const logPath = managedLogs?.logPath
if (!logPath) {
return;
return
}
void Clipboard.setStringAsync(logPath)
.then(() => {
Alert.alert("Copied", "Log path copied.");
Alert.alert('Copied', 'Log path copied.')
})
.catch((error) => {
console.error("[Settings] Failed to copy log path", error);
Alert.alert("Error", "Unable to copy log path.");
});
}, [managedLogs?.logPath, managedStatus?.logPath]);
console.error('[Settings] Failed to copy log path', error)
Alert.alert('Error', 'Unable to copy log path.')
})
}, [managedLogs?.logPath])
const handleOpenLogs = useCallback(() => {
if (!managedLogs) {
return;
return
}
setIsLogsModalOpen(true);
}, [managedLogs]);
setIsLogsModalOpen(true)
}, [managedLogs])
const handleOpenPairingModal = useCallback(() => {
if (isLoadingPairing) {
return;
return
}
setIsPairingModalOpen(true);
setIsLoadingPairing(true);
setPairingStatusMessage(null);
setIsPairingModalOpen(true)
setIsLoadingPairing(true)
setPairingStatusMessage(null)
void getManagedDaemonPairing()
.then((pairing) => {
setPairingOffer(pairing);
setPairingOffer(pairing)
if (!pairing.relayEnabled || !pairing.url) {
setPairingStatusMessage("Relay pairing is not available.");
setPairingStatusMessage('Relay pairing is not available.')
}
})
.catch((error) => {
const message = error instanceof Error ? error.message : String(error);
setPairingOffer(null);
setPairingStatusMessage(`Unable to load pairing offer: ${message}`);
const message = error instanceof Error ? error.message : String(error)
setPairingOffer(null)
setPairingStatusMessage(`Unable to load pairing offer: ${message}`)
})
.finally(() => {
setIsLoadingPairing(false);
});
}, [isLoadingPairing]);
setIsLoadingPairing(false)
})
}, [isLoadingPairing])
const handleCopyPairingLink = useCallback(() => {
if (!pairingOffer?.url) {
return;
return
}
void Clipboard.setStringAsync(pairingOffer.url)
.then(() => {
Alert.alert("Copied", "Pairing link copied.");
Alert.alert('Copied', 'Pairing link copied.')
})
.catch((error) => {
console.error("[Settings] Failed to copy pairing link", error);
Alert.alert("Error", "Unable to copy pairing link.");
});
}, [pairingOffer?.url]);
console.error('[Settings] Failed to copy pairing link', error)
Alert.alert('Error', 'Unable to copy pairing link.')
})
}, [pairingOffer?.url])
if (!showSection) {
return null;
return null
}
return (
@@ -364,35 +368,37 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
<Text style={styles.rowTitle}>Daemon management</Text>
<Text style={styles.hintText}>
{isDaemonManagementPaused
? "Paused. Paseo will not auto-start the built-in daemon on app launch."
: "Enabled. Paseo will start the built-in daemon automatically when needed."}
? 'Paused. The built-in daemon stays stopped until you start it again.'
: 'Enabled. Paseo can manage the built-in daemon from the desktop app.'}
</Text>
</View>
<Button
variant="outline"
size="sm"
leftIcon={isDaemonManagementPaused
? <Play size={theme.iconSize.sm} color={theme.colors.foreground} />
: <Pause size={theme.iconSize.sm} color={theme.colors.foreground} />}
leftIcon={
isDaemonManagementPaused ? (
<Play size={theme.iconSize.sm} color={theme.colors.foreground} />
) : (
<Pause size={theme.iconSize.sm} color={theme.colors.foreground} />
)
}
onPress={handleToggleDaemonManagement}
disabled={isUpdatingDaemonManagement}
>
{isUpdatingDaemonManagement
? isDaemonManagementPaused
? "Resuming..."
: "Pausing..."
? 'Resuming...'
: 'Pausing...'
: isDaemonManagementPaused
? "Resume"
: "Pause"}
? 'Resume'
: 'Pause'}
</Button>
</View>
<View style={[styles.row, styles.rowBorder]}>
<View style={styles.rowContent}>
<Text style={styles.rowTitle}>{daemonActionLabel}</Text>
<Text style={styles.hintText}>{daemonActionMessage}</Text>
{statusMessage ? (
<Text style={styles.statusText}>{statusMessage}</Text>
) : null}
{statusMessage ? <Text style={styles.statusText}>{statusMessage}</Text> : null}
</View>
<Button
variant="outline"
@@ -402,18 +408,16 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
disabled={isRestartingDaemon}
>
{isRestartingDaemon
? managedStatus?.daemonRunning
? "Restarting..."
: "Starting..."
? managedStatus?.status === 'running'
? 'Restarting...'
: 'Starting...'
: daemonActionLabel}
</Button>
</View>
<View style={[styles.row, styles.rowBorder]}>
<View style={styles.rowContent}>
<Text style={styles.rowTitle}>Command line (CLI)</Text>
<Text style={styles.hintText}>
Adds the `paseo` command to your terminal.
</Text>
<Text style={styles.hintText}>Adds the `paseo` command to your terminal.</Text>
{cliStatusMessage ? <Text style={styles.statusText}>{cliStatusMessage}</Text> : null}
</View>
<Button
@@ -423,25 +427,22 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
onPress={handleToggleCliShim}
disabled={isInstallingCli}
>
{isInstallingCli
? "Working..."
: managedStatus?.cliShimPath
? "Uninstall CLI"
: "Install CLI"}
{isInstallingCli ? 'Working...' : cliShimStatus?.path ? 'Uninstall CLI' : 'Install CLI'}
</Button>
</View>
<View style={[styles.row, styles.rowBorder]}>
<View style={styles.rowContent}>
<Text style={styles.rowTitle}>Log file</Text>
<Text style={styles.hintText}>
{managedLogs?.logPath ??
managedStatus?.logPath ??
"Log path unavailable."}
</Text>
<Text style={styles.hintText}>{managedLogs?.logPath ?? 'Log path unavailable.'}</Text>
</View>
<View style={styles.actionGroup}>
{(managedLogs?.logPath ?? managedStatus?.logPath) ? (
<Button variant="outline" size="sm" leftIcon={<Copy size={theme.iconSize.sm} color={theme.colors.foreground} />} onPress={handleCopyLogPath}>
{managedLogs?.logPath ? (
<Button
variant="outline"
size="sm"
leftIcon={<Copy size={theme.iconSize.sm} color={theme.colors.foreground} />}
onPress={handleCopyLogPath}
>
Copy path
</Button>
) : null}
@@ -459,11 +460,14 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
<View style={[styles.row, styles.rowBorder]}>
<View style={styles.rowContent}>
<Text style={styles.rowTitle}>Pair device</Text>
<Text style={styles.hintText}>
Connect your phone to this computer.
</Text>
<Text style={styles.hintText}>Connect your phone to this computer.</Text>
</View>
<Button variant="outline" size="sm" leftIcon={<Smartphone size={theme.iconSize.sm} color={theme.colors.foreground} />} onPress={handleOpenPairingModal}>
<Button
variant="outline"
size="sm"
leftIcon={<Smartphone size={theme.iconSize.sm} color={theme.colors.foreground} />}
onPress={handleOpenPairingModal}
>
Pair device
</Button>
</View>
@@ -493,7 +497,7 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
<Text style={styles.hintText}>{cliInstallInstructions.detail}</Text>
) : null}
<Text style={styles.codeBlock} selectable>
{cliInstallInstructions?.commands ?? ""}
{cliInstallInstructions?.commands ?? ''}
</Text>
<View style={styles.modalActions}>
<Button variant="outline" size="sm" onPress={() => setIsCliInstallModalOpen(false)}>
@@ -525,71 +529,67 @@ export function LocalDaemonSection({ appVersion }: LocalDaemonSectionProps) {
onClose={() => setIsLogsModalOpen(false)}
title="Daemon logs"
testID="managed-daemon-logs-dialog"
snapPoints={["70%", "92%"]}
snapPoints={['70%', '92%']}
>
<View style={styles.modalBody}>
<Text style={styles.hintText}>
{managedLogs?.logPath ??
managedStatus?.logPath ??
"Log path unavailable."}
</Text>
<Text style={styles.hintText}>{managedLogs?.logPath ?? 'Log path unavailable.'}</Text>
<Text style={styles.logOutput} selectable>
{managedLogs?.contents.length ? managedLogs.contents : "(log file is empty)"}
{managedLogs?.contents.length ? managedLogs.contents : '(log file is empty)'}
</Text>
</View>
</AdaptiveModalSheet>
</View>
);
)
}
const ADVANCED_DAEMON_SETTINGS_URL = "https://paseo.sh/docs/configuration";
const ADVANCED_DAEMON_SETTINGS_URL = 'https://paseo.sh/docs/configuration'
function PairingOfferDialogContent(input: {
isLoading: boolean;
pairingOffer: ManagedPairingOffer | null;
statusMessage: string | null;
onCopyLink: () => void;
isLoading: boolean
pairingOffer: ManagedPairingOffer | null
statusMessage: string | null
onCopyLink: () => void
}) {
const { isLoading, pairingOffer, statusMessage, onCopyLink } = input;
const [qrDataUrl, setQrDataUrl] = useState<string | null>(null);
const [qrError, setQrError] = useState<string | null>(null);
const { isLoading, pairingOffer, statusMessage, onCopyLink } = input
const [qrDataUrl, setQrDataUrl] = useState<string | null>(null)
const [qrError, setQrError] = useState<string | null>(null)
useEffect(() => {
let cancelled = false;
let cancelled = false
if (!pairingOffer?.url) {
setQrDataUrl(null);
setQrError(null);
setQrDataUrl(null)
setQrError(null)
return () => {
cancelled = true;
};
cancelled = true
}
}
setQrError(null);
setQrDataUrl(null);
setQrError(null)
setQrDataUrl(null)
void QRCode.toDataURL(pairingOffer.url, {
errorCorrectionLevel: "M",
errorCorrectionLevel: 'M',
margin: 1,
width: 320,
})
.then((dataUrl) => {
if (cancelled) {
return;
return
}
setQrDataUrl(dataUrl);
setQrDataUrl(dataUrl)
})
.catch((error) => {
if (cancelled) {
return;
return
}
setQrError(error instanceof Error ? error.message : String(error));
});
setQrError(error instanceof Error ? error.message : String(error))
})
return () => {
cancelled = true;
};
}, [pairingOffer?.url]);
cancelled = true
}
}, [pairingOffer?.url])
if (isLoading) {
return (
@@ -597,7 +597,7 @@ function PairingOfferDialogContent(input: {
<ActivityIndicator size="small" />
<Text style={styles.hintText}>Loading pairing offer…</Text>
</View>
);
)
}
if (statusMessage) {
@@ -605,7 +605,7 @@ function PairingOfferDialogContent(input: {
<View style={styles.modalBody}>
<Text style={styles.hintText}>{statusMessage}</Text>
</View>
);
)
}
if (!pairingOffer?.url) {
@@ -613,7 +613,7 @@ function PairingOfferDialogContent(input: {
<View style={styles.modalBody}>
<Text style={styles.hintText}>Pairing offer unavailable.</Text>
</View>
);
)
}
return (
@@ -640,20 +640,20 @@ function PairingOfferDialogContent(input: {
</Button>
</View>
</View>
);
)
}
const styles = StyleSheet.create((theme) => ({
sectionHeader: {
alignItems: "center",
flexDirection: "row",
justifyContent: "space-between",
alignItems: 'center',
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: theme.spacing[3],
marginLeft: theme.spacing[1],
},
sectionLink: {
alignItems: "center",
flexDirection: "row",
alignItems: 'center',
flexDirection: 'row',
gap: theme.spacing[1],
},
sectionLinkText: {
@@ -661,9 +661,9 @@ const styles = StyleSheet.create((theme) => ({
fontSize: theme.fontSize.xs,
},
row: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: theme.spacing[4],
paddingHorizontal: theme.spacing[4],
},
@@ -676,13 +676,13 @@ const styles = StyleSheet.create((theme) => ({
marginRight: theme.spacing[3],
},
actionGroup: {
flexDirection: "row",
flexDirection: 'row',
gap: theme.spacing[2],
flexWrap: "wrap",
justifyContent: "flex-end",
flexWrap: 'wrap',
justifyContent: 'flex-end',
},
statusValueGroup: {
alignItems: "flex-end",
alignItems: 'flex-end',
gap: 2,
},
rowTitle: {
@@ -713,7 +713,7 @@ const styles = StyleSheet.create((theme) => ({
borderRadius: theme.borderRadius.lg,
borderWidth: 1,
borderColor: theme.colors.palette.amber[500],
backgroundColor: "rgba(245, 158, 11, 0.12)",
backgroundColor: 'rgba(245, 158, 11, 0.12)',
paddingVertical: theme.spacing[2],
paddingHorizontal: theme.spacing[3],
},
@@ -726,15 +726,15 @@ const styles = StyleSheet.create((theme) => ({
paddingBottom: theme.spacing[2],
},
pairingState: {
alignItems: "center",
justifyContent: "center",
alignItems: 'center',
justifyContent: 'center',
gap: theme.spacing[3],
paddingVertical: theme.spacing[6],
},
qrCard: {
alignItems: "center",
justifyContent: "center",
alignSelf: "center",
alignItems: 'center',
justifyContent: 'center',
alignSelf: 'center',
minHeight: 220,
minWidth: 220,
padding: theme.spacing[4],
@@ -759,13 +759,13 @@ const styles = StyleSheet.create((theme) => ({
logOutput: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.xs,
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
lineHeight: 18,
},
codeBlock: {
color: theme.colors.foregroundMuted,
fontSize: theme.fontSize.xs,
fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
lineHeight: 18,
borderWidth: 1,
borderColor: theme.colors.border,
@@ -774,8 +774,8 @@ const styles = StyleSheet.create((theme) => ({
padding: theme.spacing[3],
},
modalActions: {
flexDirection: "row",
justifyContent: "flex-end",
flexDirection: 'row',
justifyContent: 'flex-end',
gap: theme.spacing[2],
},
}));
}))