Fix host switcher status syncing

This commit is contained in:
Mohamed Boudra
2026-04-10 18:48:36 +07:00
parent 05734e8b1b
commit faf85e038c
+62 -37
View File
@@ -5,8 +5,8 @@ import {
useState, useState,
useEffect, useEffect,
useRef, useRef,
useSyncExternalStore,
type Dispatch, type Dispatch,
type ReactElement,
type RefObject, type RefObject,
type SetStateAction, type SetStateAction,
} from "react"; } from "react";
@@ -44,8 +44,8 @@ import {
import { useSidebarAnimation } from "@/contexts/sidebar-animation-context"; import { useSidebarAnimation } from "@/contexts/sidebar-animation-context";
import { useWindowControlsPadding } from "@/utils/desktop-window"; import { useWindowControlsPadding } from "@/utils/desktop-window";
import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region"; import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region";
import { Combobox } from "@/components/ui/combobox"; import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
import { getHostRuntimeStore, useHosts } from "@/runtime/host-runtime"; import { useHostRuntimeSnapshot, useHosts } from "@/runtime/host-runtime";
import { formatConnectionStatus } from "@/utils/daemons"; import { formatConnectionStatus } from "@/utils/daemons";
import { import {
HEADER_INNER_HEIGHT, HEADER_INNER_HEIGHT,
@@ -69,18 +69,12 @@ interface LeftSidebarProps {
selectedAgentId?: string; selectedAgentId?: string;
} }
interface HostOption {
id: string;
label: string;
description: string;
}
interface SidebarSharedProps { interface SidebarSharedProps {
theme: SidebarTheme; theme: SidebarTheme;
activeServerId: string | null; activeServerId: string | null;
activeHostLabel: string; activeHostLabel: string;
activeHostStatusColor: string; activeHostStatusColor: string;
hostOptions: HostOption[]; hostOptions: ComboboxOption[];
hostTriggerRef: RefObject<View | null>; hostTriggerRef: RefObject<View | null>;
isHostPickerOpen: boolean; isHostPickerOpen: boolean;
setIsHostPickerOpen: Dispatch<SetStateAction<boolean>>; setIsHostPickerOpen: Dispatch<SetStateAction<boolean>>;
@@ -95,6 +89,12 @@ interface SidebarSharedProps {
handleHostSelect: (nextServerId: string) => void; handleHostSelect: (nextServerId: string) => void;
handleOpenProject: () => void; handleOpenProject: () => void;
handleSettings: () => void; handleSettings: () => void;
renderHostOption: (input: {
option: ComboboxOption;
selected: boolean;
active: boolean;
onPress: () => void;
}) => ReactElement;
} }
interface MobileSidebarProps extends SidebarSharedProps { interface MobileSidebarProps extends SidebarSharedProps {
@@ -124,28 +124,6 @@ export const LeftSidebar = memo(function LeftSidebar({
const closeToAgent = usePanelStore((state) => state.closeToAgent); const closeToAgent = usePanelStore((state) => state.closeToAgent);
const pathname = usePathname(); const pathname = usePathname();
const daemons = useHosts(); const daemons = useHosts();
const runtime = getHostRuntimeStore();
const runtimeConnectionStatusSignature = useSyncExternalStore(
(onStoreChange) => runtime.subscribeAll(onStoreChange),
() =>
daemons
.map(
(daemon) =>
`${daemon.serverId}:${
runtime.getSnapshot(daemon.serverId)?.connectionStatus ?? "connecting"
}`,
)
.join("|"),
() =>
daemons
.map(
(daemon) =>
`${daemon.serverId}:${
runtime.getSnapshot(daemon.serverId)?.connectionStatus ?? "connecting"
}`,
)
.join("|"),
);
const activeServerIdFromPath = useMemo(() => parseServerIdFromPathname(pathname), [pathname]); const activeServerIdFromPath = useMemo(() => parseServerIdFromPathname(pathname), [pathname]);
const activeDaemon = useMemo(() => { const activeDaemon = useMemo(() => {
if (daemons.length === 0) { if (daemons.length === 0) {
@@ -165,8 +143,9 @@ export const LeftSidebar = memo(function LeftSidebar({
const trimmed = activeDaemon.label?.trim(); const trimmed = activeDaemon.label?.trim();
return trimmed && trimmed.length > 0 ? trimmed : activeDaemon.serverId; return trimmed && trimmed.length > 0 ? trimmed : activeDaemon.serverId;
}, [activeDaemon]); }, [activeDaemon]);
const activeHostSnapshot = useHostRuntimeSnapshot(activeServerId ?? "");
const activeHostStatus = activeServerId const activeHostStatus = activeServerId
? (runtime.getSnapshot(activeServerId)?.connectionStatus ?? "connecting") ? (activeHostSnapshot?.connectionStatus ?? "connecting")
: "idle"; : "idle";
const activeHostStatusColor = const activeHostStatusColor =
activeHostStatus === "online" activeHostStatus === "online"
@@ -179,11 +158,25 @@ export const LeftSidebar = memo(function LeftSidebar({
daemons.map((daemon) => ({ daemons.map((daemon) => ({
id: daemon.serverId, id: daemon.serverId,
label: daemon.label?.trim() || daemon.serverId, label: daemon.label?.trim() || daemon.serverId,
description: formatConnectionStatus(
runtime.getSnapshot(daemon.serverId)?.connectionStatus ?? "connecting",
),
})), })),
[daemons, runtime, runtimeConnectionStatusSignature], [daemons],
);
const renderHostOption = useCallback(
({ option, selected, active, onPress }: {
option: ComboboxOption;
selected: boolean;
active: boolean;
onPress: () => void;
}) => (
<HostSwitchOption
serverId={option.id}
label={option.label}
selected={selected}
active={active}
onPress={onPress}
/>
),
[],
); );
const hostTriggerRef = useRef<View | null>(null); const hostTriggerRef = useRef<View | null>(null);
const [isHostPickerOpen, setIsHostPickerOpen] = useState(false); const [isHostPickerOpen, setIsHostPickerOpen] = useState(false);
@@ -276,6 +269,7 @@ export const LeftSidebar = memo(function LeftSidebar({
toggleProjectCollapsed, toggleProjectCollapsed,
handleRefresh, handleRefresh,
handleHostSelect, handleHostSelect,
renderHostOption,
}; };
if (isCompactLayout) { if (isCompactLayout) {
@@ -305,6 +299,33 @@ export const LeftSidebar = memo(function LeftSidebar({
); );
}); });
function HostSwitchOption({
serverId,
label,
selected,
active,
onPress,
}: {
serverId: string;
label: string;
selected: boolean;
active: boolean;
onPress: () => void;
}) {
const snapshot = useHostRuntimeSnapshot(serverId);
const connectionStatus = snapshot?.connectionStatus ?? "connecting";
return (
<ComboboxItem
label={label}
description={formatConnectionStatus(connectionStatus)}
selected={selected}
active={active}
onPress={onPress}
/>
);
}
function SessionsButton({ onPress }: { onPress: () => void }) { function SessionsButton({ onPress }: { onPress: () => void }) {
const { theme } = useUnistyles(); const { theme } = useUnistyles();
const pathname = usePathname(); const pathname = usePathname();
@@ -361,6 +382,7 @@ function MobileSidebar({
toggleProjectCollapsed, toggleProjectCollapsed,
handleRefresh, handleRefresh,
handleHostSelect, handleHostSelect,
renderHostOption,
handleOpenProject, handleOpenProject,
handleSettings, handleSettings,
insetsTop, insetsTop,
@@ -606,6 +628,7 @@ function MobileSidebar({
options={hostOptions} options={hostOptions}
value={activeServerId ?? ""} value={activeServerId ?? ""}
onSelect={handleHostSelect} onSelect={handleHostSelect}
renderOption={renderHostOption}
searchable={false} searchable={false}
title="Switch host" title="Switch host"
searchPlaceholder="Search hosts..." searchPlaceholder="Search hosts..."
@@ -639,6 +662,7 @@ function DesktopSidebar({
toggleProjectCollapsed, toggleProjectCollapsed,
handleRefresh, handleRefresh,
handleHostSelect, handleHostSelect,
renderHostOption,
handleOpenProject, handleOpenProject,
handleSettings, handleSettings,
insetsTop, insetsTop,
@@ -789,6 +813,7 @@ function DesktopSidebar({
options={hostOptions} options={hostOptions}
value={activeServerId ?? ""} value={activeServerId ?? ""}
onSelect={handleHostSelect} onSelect={handleHostSelect}
renderOption={renderHostOption}
searchable={false} searchable={false}
title="Switch host" title="Switch host"
searchPlaceholder="Search hosts..." searchPlaceholder="Search hosts..."