Update files
This commit is contained in:
@@ -28,6 +28,10 @@ import {
|
||||
buildAgentNavigationKey,
|
||||
startNavigationTiming,
|
||||
} from "@/utils/navigation-timing";
|
||||
import {
|
||||
buildHostAgentDetailRoute,
|
||||
parseHostAgentRouteFromPathname,
|
||||
} from "@/utils/host-routes";
|
||||
|
||||
interface AgentListProps {
|
||||
agents: AggregatedAgent[];
|
||||
@@ -114,12 +118,12 @@ export function AgentList({
|
||||
params: { serverId, agentId },
|
||||
});
|
||||
|
||||
const shouldReplace = pathname.startsWith("/agent/");
|
||||
const shouldReplace = Boolean(parseHostAgentRouteFromPathname(pathname));
|
||||
const navigate = shouldReplace ? router.replace : router.push;
|
||||
|
||||
onAgentSelect?.();
|
||||
|
||||
navigate(`/agent/${serverId}/${agentId}` as any);
|
||||
navigate(buildHostAgentDetailRoute(serverId, agentId) as any);
|
||||
},
|
||||
[isActionSheetVisible, pathname, onAgentSelect]
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { View, Text, Pressable, ActivityIndicator } from "react-native";
|
||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||
import { X, ArrowUp, RefreshCcw, Check, Mic } from "lucide-react-native";
|
||||
import { X, ArrowUp, RefreshCcw, Check, Mic, Pencil } from "lucide-react-native";
|
||||
import { VolumeMeter } from "./volume-meter";
|
||||
import { FOOTER_HEIGHT } from "@/constants/layout";
|
||||
import type { DictationStatus } from "@/hooks/use-dictation";
|
||||
@@ -233,7 +233,7 @@ export function DictationOverlay({
|
||||
{ backgroundColor: "rgba(255, 255, 255, 0.25)" },
|
||||
]}
|
||||
>
|
||||
<Check
|
||||
<Pencil
|
||||
size={20}
|
||||
color={theme.colors.palette.white}
|
||||
strokeWidth={2.5}
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
type ActionStatus,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { GitHubIcon } from "@/components/icons/github-icon";
|
||||
import { buildHostAgentDraftRoute } from "@/utils/host-routes";
|
||||
|
||||
// =============================================================================
|
||||
// Git Actions Data Structure
|
||||
@@ -800,7 +801,7 @@ export function GitDiffPane({ serverId, agentId, cwd }: GitDiffPaneProps) {
|
||||
setActionError(null);
|
||||
void runArchiveWorktree({ serverId, cwd, worktreePath })
|
||||
.then(() => {
|
||||
router.replace("/agent" as any);
|
||||
router.replace(buildHostAgentDraftRoute(serverId) as any);
|
||||
})
|
||||
.catch((err) => {
|
||||
const message = err instanceof Error ? err.message : "Failed to archive worktree";
|
||||
|
||||
@@ -3,13 +3,11 @@ import {
|
||||
useDaemonRegistry,
|
||||
type HostProfile,
|
||||
} from "@/contexts/daemon-registry-context";
|
||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
||||
import {
|
||||
buildDaemonWebSocketUrl,
|
||||
buildRelayWebSocketUrl,
|
||||
} from "@/utils/daemon-endpoints";
|
||||
import { probeConnection } from "@/utils/test-daemon-connection";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useMemo } from "react";
|
||||
import type { ActiveConnection } from "@/contexts/daemon-connections-context";
|
||||
|
||||
type Candidate = {
|
||||
@@ -67,120 +65,17 @@ function buildCandidates(host: HostProfile): Candidate[] {
|
||||
}
|
||||
|
||||
function ManagedDaemonSession({ daemon }: { daemon: HostProfile }) {
|
||||
const { connectionStates } = useDaemonConnections();
|
||||
const { updateHost } = useDaemonRegistry();
|
||||
|
||||
const candidates = useMemo(() => buildCandidates(daemon), [daemon]);
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const active = candidates[activeIndex] ?? candidates[0] ?? null;
|
||||
const active =
|
||||
candidates.find((candidate) => candidate.connectionId === daemon.preferredConnectionId) ??
|
||||
candidates[0] ??
|
||||
null;
|
||||
const activeUrl = active?.url ?? null;
|
||||
|
||||
const lastAttemptedUrlRef = useRef<string | null>(null);
|
||||
const pendingPreferenceWriteRef = useRef(false);
|
||||
const upgradeProbeInFlightRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeUrl) {
|
||||
return;
|
||||
}
|
||||
// If the active URL fell out of the candidate set (e.g. endpoints updated), snap back.
|
||||
const idx = candidates.findIndex((c) => c.url === activeUrl);
|
||||
if (idx === -1) {
|
||||
setActiveIndex(0);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [candidates.map((c) => c.url).join("|")]);
|
||||
|
||||
if (!activeUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const connection = connectionStates.get(daemon.serverId);
|
||||
const status = connection?.status ?? "idle";
|
||||
const lastError = connection?.lastError ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
if (!connection) return;
|
||||
|
||||
if (status === "online") {
|
||||
if (!active) return;
|
||||
if (pendingPreferenceWriteRef.current) return;
|
||||
if (daemon.preferredConnectionId === active.connectionId) return;
|
||||
|
||||
pendingPreferenceWriteRef.current = true;
|
||||
void updateHost(daemon.serverId, {
|
||||
preferredConnectionId: active.connectionId,
|
||||
}).finally(() => {
|
||||
pendingPreferenceWriteRef.current = false;
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if ((status === "error" || (status === "offline" && lastError)) && candidates.length > 1) {
|
||||
if (lastAttemptedUrlRef.current === activeUrl) {
|
||||
return;
|
||||
}
|
||||
lastAttemptedUrlRef.current = activeUrl;
|
||||
if (activeIndex < candidates.length - 1) {
|
||||
setActiveIndex((idx) => Math.min(idx + 1, candidates.length - 1));
|
||||
}
|
||||
}
|
||||
}, [
|
||||
active,
|
||||
activeIndex,
|
||||
activeUrl,
|
||||
candidates.length,
|
||||
daemon.preferredConnectionId,
|
||||
daemon.serverId,
|
||||
lastError,
|
||||
status,
|
||||
updateHost,
|
||||
connection,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!connection) return;
|
||||
if (status !== "online") return;
|
||||
if (activeIndex === 0) return;
|
||||
|
||||
const best = candidates[0];
|
||||
if (!best) return;
|
||||
if (best.activeConnection.type !== "direct") return;
|
||||
|
||||
const intervalMs = 15_000;
|
||||
let cancelled = false;
|
||||
|
||||
const attemptUpgrade = async () => {
|
||||
if (cancelled) return;
|
||||
if (upgradeProbeInFlightRef.current) return;
|
||||
if (activeIndex === 0) return;
|
||||
|
||||
upgradeProbeInFlightRef.current = true;
|
||||
try {
|
||||
const { serverId } = await probeConnection(
|
||||
{ id: "probe", type: "direct", endpoint: best.activeConnection.endpoint },
|
||||
{ timeoutMs: 2000 },
|
||||
);
|
||||
if (cancelled) return;
|
||||
if (serverId !== daemon.serverId) return;
|
||||
|
||||
lastAttemptedUrlRef.current = null;
|
||||
setActiveIndex(0);
|
||||
} catch {
|
||||
// ignore - we'll retry periodically
|
||||
} finally {
|
||||
upgradeProbeInFlightRef.current = false;
|
||||
}
|
||||
};
|
||||
|
||||
void attemptUpgrade();
|
||||
const interval = setInterval(() => void attemptUpgrade(), intervalMs);
|
||||
return () => {
|
||||
cancelled = true;
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, [activeIndex, candidates, connection, daemon.serverId, status]);
|
||||
|
||||
return (
|
||||
<SessionProvider
|
||||
key={daemon.serverId}
|
||||
|
||||
@@ -31,6 +31,11 @@ import {
|
||||
buildAgentNavigationKey,
|
||||
startNavigationTiming,
|
||||
} from "@/utils/navigation-timing";
|
||||
import {
|
||||
buildHostAgentDetailRoute,
|
||||
parseHostAgentRouteFromPathname,
|
||||
} from "@/utils/host-routes";
|
||||
import { buildNewAgentRoute } from "@/utils/new-agent-routing";
|
||||
import {
|
||||
useSectionOrderStore,
|
||||
} from "@/stores/section-order-store";
|
||||
@@ -63,7 +68,7 @@ interface SectionHeaderProps {
|
||||
checkout: SidebarCheckoutLite | null;
|
||||
isCollapsed: boolean;
|
||||
onToggle: () => void;
|
||||
onCreateAgent: (workingDir: string) => void;
|
||||
onCreateAgent: (serverId: string, workingDir: string) => void;
|
||||
onDrag: () => void;
|
||||
isDragging: boolean;
|
||||
}
|
||||
@@ -109,11 +114,12 @@ function SectionHeader({
|
||||
const handleCreatePress = useCallback(
|
||||
(e: { stopPropagation: () => void }) => {
|
||||
e.stopPropagation();
|
||||
if (createAgentWorkingDir) {
|
||||
onCreateAgent(createAgentWorkingDir);
|
||||
const serverId = section.firstAgentServerId;
|
||||
if (createAgentWorkingDir && serverId) {
|
||||
onCreateAgent(serverId, createAgentWorkingDir);
|
||||
}
|
||||
},
|
||||
[onCreateAgent, createAgentWorkingDir]
|
||||
[onCreateAgent, createAgentWorkingDir, section.firstAgentServerId]
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -405,12 +411,12 @@ export function SidebarAgentList({
|
||||
params: { serverId, agentId },
|
||||
});
|
||||
|
||||
const shouldReplace = pathname.startsWith("/agent/");
|
||||
const shouldReplace = Boolean(parseHostAgentRouteFromPathname(pathname));
|
||||
const navigate = shouldReplace ? router.replace : router.push;
|
||||
|
||||
onAgentSelect?.();
|
||||
|
||||
navigate(`/agent/${serverId}/${agentId}` as any);
|
||||
navigate(buildHostAgentDetailRoute(serverId, agentId) as any);
|
||||
},
|
||||
[isActionSheetVisible, pathname, onAgentSelect]
|
||||
);
|
||||
@@ -432,9 +438,9 @@ export function SidebarAgentList({
|
||||
}, [actionAgent, actionClient]);
|
||||
|
||||
const handleCreateAgentInProject = useCallback(
|
||||
(workingDir: string) => {
|
||||
(serverId: string, workingDir: string) => {
|
||||
onAgentSelect?.();
|
||||
router.push(`/agent?workingDir=${encodeURIComponent(workingDir)}` as any);
|
||||
router.push(buildNewAgentRoute(serverId, workingDir) as any);
|
||||
},
|
||||
[onAgentSelect]
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useMemo, useState, useEffect } from "react";
|
||||
import { useCallback, useMemo, useState, useEffect, useRef } from "react";
|
||||
import { View, Pressable, Text, Platform } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import Animated, {
|
||||
@@ -10,7 +10,7 @@ import Animated, {
|
||||
import { Gesture, GestureDetector } from "react-native-gesture-handler";
|
||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||
import { Plus, Settings, Users } from "lucide-react-native";
|
||||
import { router } from "expo-router";
|
||||
import { router, usePathname } from "expo-router";
|
||||
import { usePanelStore } from "@/stores/panel-store";
|
||||
import { SidebarAgentList } from "./sidebar-agent-list";
|
||||
import { SidebarAgentListSkeleton } from "./sidebar-agent-list-skeleton";
|
||||
@@ -20,6 +20,17 @@ import { useTauriDragHandlers, useTrafficLightPadding } from "@/utils/tauri-wind
|
||||
import { useSidebarCollapsedSectionsStore } from "@/stores/sidebar-collapsed-sections-store";
|
||||
import { useKeyboardNavStore } from "@/stores/keyboard-nav-store";
|
||||
import { deriveSidebarShortcutAgentKeys } from "@/utils/sidebar-shortcuts";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
||||
import { formatConnectionStatus } from "@/utils/daemons";
|
||||
import {
|
||||
buildHostAgentDraftRoute,
|
||||
buildHostAgentsRoute,
|
||||
buildHostSettingsRoute,
|
||||
mapPathnameToServer,
|
||||
parseServerIdFromPathname,
|
||||
} from "@/utils/host-routes";
|
||||
|
||||
const DESKTOP_SIDEBAR_WIDTH = 320;
|
||||
|
||||
@@ -35,6 +46,42 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
||||
const mobileView = usePanelStore((state) => state.mobileView);
|
||||
const desktopAgentListOpen = usePanelStore((state) => state.desktop.agentListOpen);
|
||||
const closeToAgent = usePanelStore((state) => state.closeToAgent);
|
||||
const pathname = usePathname();
|
||||
const { daemons } = useDaemonRegistry();
|
||||
const { connectionStates } = useDaemonConnections();
|
||||
const activeServerIdFromPath = useMemo(
|
||||
() => parseServerIdFromPathname(pathname),
|
||||
[pathname]
|
||||
);
|
||||
const activeServerId = activeServerIdFromPath ?? daemons[0]?.serverId ?? null;
|
||||
const activeHostLabel = useMemo(() => {
|
||||
if (!activeServerId) return "No host";
|
||||
const daemon = daemons.find((entry) => entry.serverId === activeServerId);
|
||||
const trimmed = daemon?.label?.trim();
|
||||
return trimmed && trimmed.length > 0 ? trimmed : activeServerId;
|
||||
}, [activeServerId, daemons]);
|
||||
const activeHostStatus = activeServerId
|
||||
? (connectionStates.get(activeServerId)?.status ?? "idle")
|
||||
: "idle";
|
||||
const activeHostStatusColor =
|
||||
activeHostStatus === "online"
|
||||
? theme.colors.palette.green[400]
|
||||
: activeHostStatus === "connecting"
|
||||
? theme.colors.palette.amber[500]
|
||||
: theme.colors.palette.red[500];
|
||||
const hostOptions = useMemo(
|
||||
() =>
|
||||
daemons.map((daemon) => ({
|
||||
id: daemon.serverId,
|
||||
label: daemon.label?.trim() || daemon.serverId,
|
||||
description: formatConnectionStatus(
|
||||
connectionStates.get(daemon.serverId)?.status ?? "idle"
|
||||
),
|
||||
})),
|
||||
[connectionStates, daemons]
|
||||
);
|
||||
const hostTriggerRef = useRef<View>(null);
|
||||
const [isHostPickerOpen, setIsHostPickerOpen] = useState(false);
|
||||
|
||||
// Derive isOpen from the unified panel state
|
||||
const isOpen = isMobile ? mobileView === "agent-list" : desktopAgentListOpen;
|
||||
@@ -45,7 +92,7 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
||||
isInitialLoad,
|
||||
isRevalidating,
|
||||
refreshAll,
|
||||
} = useSidebarAgentsGrouped({ isOpen });
|
||||
} = useSidebarAgentsGrouped({ isOpen, serverId: activeServerId });
|
||||
const {
|
||||
translateX,
|
||||
backdropOpacity,
|
||||
@@ -88,8 +135,11 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
||||
}, [closeToAgent]);
|
||||
|
||||
const handleCreateAgentClean = useCallback(() => {
|
||||
router.push("/agent" as any);
|
||||
}, []);
|
||||
if (!activeServerId) {
|
||||
return;
|
||||
}
|
||||
router.push(buildHostAgentDraftRoute(activeServerId) as any);
|
||||
}, [activeServerId]);
|
||||
|
||||
// Mobile: close sidebar and navigate
|
||||
const handleCreateAgentCleanMobile = useCallback(() => {
|
||||
@@ -104,14 +154,20 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
||||
|
||||
// Mobile: close sidebar and navigate
|
||||
const handleSettingsMobile = useCallback(() => {
|
||||
if (!activeServerId) {
|
||||
return;
|
||||
}
|
||||
closeToAgent();
|
||||
router.push("/settings");
|
||||
}, [closeToAgent]);
|
||||
router.push(buildHostSettingsRoute(activeServerId) as any);
|
||||
}, [activeServerId, closeToAgent]);
|
||||
|
||||
// Desktop: just navigate, don't close
|
||||
const handleSettingsDesktop = useCallback(() => {
|
||||
router.push("/settings");
|
||||
}, []);
|
||||
if (!activeServerId) {
|
||||
return;
|
||||
}
|
||||
router.push(buildHostSettingsRoute(activeServerId) as any);
|
||||
}, [activeServerId]);
|
||||
|
||||
// Mobile: close sidebar when agent is selected
|
||||
// Snap immediately since navigation interrupts animations
|
||||
@@ -122,13 +178,35 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
||||
}, [closeToAgent, translateX, backdropOpacity, windowWidth]);
|
||||
|
||||
const handleViewMore = useCallback(() => {
|
||||
if (!activeServerId) {
|
||||
return;
|
||||
}
|
||||
if (isMobile) {
|
||||
translateX.value = -windowWidth;
|
||||
backdropOpacity.value = 0;
|
||||
closeToAgent();
|
||||
}
|
||||
router.push("/agents");
|
||||
}, [backdropOpacity, closeToAgent, isMobile, translateX, windowWidth]);
|
||||
router.push(buildHostAgentsRoute(activeServerId) as any);
|
||||
}, [
|
||||
activeServerId,
|
||||
backdropOpacity,
|
||||
closeToAgent,
|
||||
isMobile,
|
||||
translateX,
|
||||
windowWidth,
|
||||
]);
|
||||
|
||||
const handleHostSelect = useCallback(
|
||||
(nextServerId: string) => {
|
||||
if (!nextServerId) {
|
||||
return;
|
||||
}
|
||||
const nextPath = mapPathnameToServer(pathname, nextServerId);
|
||||
setIsHostPickerOpen(false);
|
||||
router.push(nextPath as any);
|
||||
},
|
||||
[pathname]
|
||||
);
|
||||
|
||||
// Close gesture (swipe left to close when sidebar is open)
|
||||
// Only activates on leftward swipe, fails on rightward or vertical movement
|
||||
@@ -206,18 +284,46 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
||||
<View style={styles.sidebarContent} pointerEvents="auto">
|
||||
{/* Header */}
|
||||
<View style={styles.sidebarHeader}>
|
||||
<Pressable
|
||||
style={styles.newAgentButton}
|
||||
testID="sidebar-new-agent"
|
||||
onPress={handleCreateAgentCleanMobile}
|
||||
>
|
||||
{({ hovered }) => (
|
||||
<>
|
||||
<Plus size={18} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
|
||||
<Text style={[styles.newAgentButtonText, hovered && styles.newAgentButtonTextHovered]}>New agent</Text>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
<View style={styles.sidebarHeaderRow}>
|
||||
<Pressable
|
||||
style={styles.newAgentButton}
|
||||
testID="sidebar-new-agent"
|
||||
onPress={handleCreateAgentCleanMobile}
|
||||
>
|
||||
{({ hovered }) => (
|
||||
<>
|
||||
<Plus size={18} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
|
||||
<Text style={[styles.newAgentButtonText, hovered && styles.newAgentButtonTextHovered]}>New agent</Text>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
<Pressable
|
||||
ref={hostTriggerRef}
|
||||
style={styles.hostTrigger}
|
||||
onPress={() => setIsHostPickerOpen(true)}
|
||||
disabled={hostOptions.length === 0}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.hostStatusDot,
|
||||
{ backgroundColor: activeHostStatusColor },
|
||||
]}
|
||||
/>
|
||||
<Text style={styles.hostTriggerText} numberOfLines={1}>
|
||||
{activeHostLabel}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<Combobox
|
||||
options={hostOptions}
|
||||
value={activeServerId ?? ""}
|
||||
onSelect={handleHostSelect}
|
||||
title="Switch host"
|
||||
searchPlaceholder="Search hosts..."
|
||||
open={isHostPickerOpen}
|
||||
onOpenChange={setIsHostPickerOpen}
|
||||
anchorRef={hostTriggerRef}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Middle: scrollable agent list */}
|
||||
@@ -286,18 +392,46 @@ export function SlidingSidebar({ selectedAgentId }: SlidingSidebarProps) {
|
||||
style={[styles.sidebarHeader, { paddingTop: trafficLightPadding.top || styles.sidebarHeader.paddingTop }]}
|
||||
{...dragHandlers}
|
||||
>
|
||||
<Pressable
|
||||
style={styles.newAgentButton}
|
||||
testID="sidebar-new-agent"
|
||||
onPress={handleCreateAgentCleanDesktop}
|
||||
>
|
||||
{({ hovered }) => (
|
||||
<>
|
||||
<Plus size={18} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
|
||||
<Text style={[styles.newAgentButtonText, hovered && styles.newAgentButtonTextHovered]}>New agent</Text>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
<View style={styles.sidebarHeaderRow}>
|
||||
<Pressable
|
||||
style={styles.newAgentButton}
|
||||
testID="sidebar-new-agent"
|
||||
onPress={handleCreateAgentCleanDesktop}
|
||||
>
|
||||
{({ hovered }) => (
|
||||
<>
|
||||
<Plus size={18} color={hovered ? theme.colors.foreground : theme.colors.foregroundMuted} />
|
||||
<Text style={[styles.newAgentButtonText, hovered && styles.newAgentButtonTextHovered]}>New agent</Text>
|
||||
</>
|
||||
)}
|
||||
</Pressable>
|
||||
<Pressable
|
||||
ref={hostTriggerRef}
|
||||
style={styles.hostTrigger}
|
||||
onPress={() => setIsHostPickerOpen(true)}
|
||||
disabled={hostOptions.length === 0}
|
||||
>
|
||||
<View
|
||||
style={[
|
||||
styles.hostStatusDot,
|
||||
{ backgroundColor: activeHostStatusColor },
|
||||
]}
|
||||
/>
|
||||
<Text style={styles.hostTriggerText} numberOfLines={1}>
|
||||
{activeHostLabel}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
<Combobox
|
||||
options={hostOptions}
|
||||
value={activeServerId ?? ""}
|
||||
onSelect={handleHostSelect}
|
||||
title="Switch host"
|
||||
searchPlaceholder="Search hosts..."
|
||||
open={isHostPickerOpen}
|
||||
onOpenChange={setIsHostPickerOpen}
|
||||
anchorRef={hostTriggerRef}
|
||||
/>
|
||||
</View>
|
||||
|
||||
{/* Middle: scrollable agent list */}
|
||||
@@ -383,12 +517,19 @@ const styles = StyleSheet.create((theme) => ({
|
||||
borderBottomColor: theme.colors.border,
|
||||
userSelect: "none",
|
||||
},
|
||||
sidebarHeaderRow: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
newAgentButton: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
paddingVertical: theme.spacing[1],
|
||||
paddingHorizontal: theme.spacing[1],
|
||||
flexShrink: 0,
|
||||
},
|
||||
newAgentButtonHovered: {},
|
||||
newAgentButtonText: {
|
||||
@@ -399,6 +540,25 @@ const styles = StyleSheet.create((theme) => ({
|
||||
newAgentButtonTextHovered: {
|
||||
color: theme.colors.foreground,
|
||||
},
|
||||
hostTrigger: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
gap: theme.spacing[2],
|
||||
minWidth: 0,
|
||||
maxWidth: "55%",
|
||||
paddingVertical: theme.spacing[1],
|
||||
paddingHorizontal: theme.spacing[1],
|
||||
},
|
||||
hostStatusDot: {
|
||||
width: 8,
|
||||
height: 8,
|
||||
borderRadius: theme.borderRadius.full,
|
||||
},
|
||||
hostTriggerText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
color: theme.colors.foregroundMuted,
|
||||
},
|
||||
sidebarFooter: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
|
||||
@@ -9,6 +9,7 @@ import { useSessionStore } from "@/stores/session-store";
|
||||
import { AddHostModal } from "./add-host-modal";
|
||||
import { PairLinkModal } from "./pair-link-modal";
|
||||
import { NameHostModal } from "./name-host-modal";
|
||||
import { buildHostAgentDraftRoute } from "@/utils/host-routes";
|
||||
|
||||
const styles = StyleSheet.create((theme) => ({
|
||||
container: {
|
||||
@@ -90,7 +91,7 @@ export function WelcomeScreen({ onHostAdded }: WelcomeScreenProps) {
|
||||
|
||||
const finishOnboarding = useCallback(
|
||||
(serverId: string) => {
|
||||
router.replace({ pathname: "/", params: { serverId } });
|
||||
router.replace(buildHostAgentDraftRoute(serverId) as any);
|
||||
},
|
||||
[router]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user