From ad803005c87a75869749c826e81e610eb331aa4c Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Mon, 29 Jun 2026 23:04:59 +0200 Subject: [PATCH] feat(app): show the connection in use on host switcher rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old marker only ever tagged the local host as "Local" based on its server id. Each row now shows the connection actually in use, inline after the host name — the TCP or relay endpoint (default :443/:80 stripped), or "Local" for socket/pipe transports. --- packages/app/e2e/helpers/settings.ts | 6 +- packages/app/e2e/settings-host-page.spec.ts | 2 +- .../app/src/components/hosts/host-picker.tsx | 77 +++++++++---------- packages/app/src/components/left-sidebar.tsx | 7 +- packages/app/src/screens/settings-screen.tsx | 9 +-- 5 files changed, 47 insertions(+), 54 deletions(-) diff --git a/packages/app/e2e/helpers/settings.ts b/packages/app/e2e/helpers/settings.ts index 40f128572..7b8c63bda 100644 --- a/packages/app/e2e/helpers/settings.ts +++ b/packages/app/e2e/helpers/settings.ts @@ -393,9 +393,9 @@ export async function expectLocalHostEntryFirst(page: Page, _serverId: string): await expect(sidebar).toBeVisible({ timeout: 15_000 }); // Single-host fixture: the picker is a non-interactive chip (no dropdown to - // open) that surfaces the local host by its label. The "Local" marker only - // appears on dropdown rows in the multi-host case, which this fixture does not - // exercise. + // open) that surfaces the local host by its label. The per-row connection + // endpoint only appears on dropdown rows in the multi-host case, which this + // fixture does not exercise. const picker = sidebar.getByTestId("settings-host-picker"); await expect(picker).toBeVisible(); await expect(picker.getByText(TEST_HOST_LABEL, { exact: true })).toBeVisible(); diff --git a/packages/app/e2e/settings-host-page.spec.ts b/packages/app/e2e/settings-host-page.spec.ts index a25786de1..e20480724 100644 --- a/packages/app/e2e/settings-host-page.spec.ts +++ b/packages/app/e2e/settings-host-page.spec.ts @@ -118,7 +118,7 @@ test.describe("Settings host page", () => { await expectHostActionCards(page, serverId); }); - test("sidebar pins the local daemon host first with a Local marker", async ({ page }) => { + test("sidebar pins the local daemon host first", async ({ page }) => { const serverId = getServerId(); // Simulate the Electron desktop bridge so `useIsLocalDaemon` resolves the diff --git a/packages/app/src/components/hosts/host-picker.tsx b/packages/app/src/components/hosts/host-picker.tsx index 86101d857..d39bd0b5b 100644 --- a/packages/app/src/components/hosts/host-picker.tsx +++ b/packages/app/src/components/hosts/host-picker.tsx @@ -1,11 +1,12 @@ import { useCallback, useMemo, type ReactElement, type ReactNode } from "react"; -import { Pressable, Text, View } from "react-native"; +import { Pressable, View } from "react-native"; import type { GestureResponderEvent } from "react-native"; import { Plus, Server, Settings } from "lucide-react-native"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { HostStatusDot } from "@/components/host-status-dot"; import { Combobox, ComboboxItem, type ComboboxProps } from "@/components/ui/combobox"; import { useLocalDaemonServerId } from "@/hooks/use-is-local-daemon"; +import { useHostRuntimeSnapshot, type ActiveConnection } from "@/runtime/host-runtime"; import { orderHostsLocalFirst } from "@/types/host-connection"; import { ADD_HOST_OPTION_ID, @@ -30,30 +31,48 @@ export function HostStatusDotSlot({ serverId }: { serverId: string }): ReactElem ); } +// Standard secure/plain web ports carry no information in the host display, so +// "relay.paseo.sh:443" reads as "relay.paseo.sh" while "127.0.0.1:6767" is kept. +function formatConnectionEndpoint(endpoint: string): string { + return endpoint.replace(/:(?:443|80)$/, ""); +} + +// Socket/pipe transports have no host:port — their endpoint is a filesystem +// path, so they read as "Local". TCP and relay show the address being used. +function formatActiveConnectionLabel(connection: ActiveConnection): string { + if (connection.type === "directSocket" || connection.type === "directPipe") { + return "Local"; + } + return formatConnectionEndpoint(connection.endpoint); +} + export interface HostPickerOptionProps { serverId: string; label: string; - isLocal: boolean; + showActiveConnection: boolean; selected?: boolean; active: boolean; onPress: () => void; onOpenHostSettings?: (serverId: string) => void; - localMarkerTestID?: string; testID?: string; } export function HostPickerOption({ serverId, label, - isLocal, + showActiveConnection, selected, active, onPress, onOpenHostSettings, - localMarkerTestID, testID, }: HostPickerOptionProps): ReactElement { const { theme } = useUnistyles(); + const activeConnection = useHostRuntimeSnapshot(serverId)?.activeConnection ?? null; + const connectionLabel = + showActiveConnection && activeConnection + ? formatActiveConnectionLabel(activeConnection) + : undefined; const leadingSlot = useMemo(() => , [serverId]); const handleSettingsPress = useCallback( (event: GestureResponderEvent) => { @@ -63,31 +82,20 @@ export function HostPickerOption({ [onOpenHostSettings, serverId], ); const trailingSlot = useMemo(() => { - if (!isLocal && !onOpenHostSettings) return undefined; + if (!onOpenHostSettings) return undefined; return ( - <> - {isLocal ? ( - - Local - - ) : null} - {onOpenHostSettings ? ( - - - - ) : null} - + + + ); }, [ handleSettingsPress, - isLocal, label, - localMarkerTestID, onOpenHostSettings, theme.colors.foregroundMuted, theme.iconSize.sm, @@ -96,6 +104,7 @@ export function HostPickerOption({ return ( void; - showLocalMarker?: boolean; + showActiveConnection?: boolean; onOpenHostSettings?: (serverId: string) => void; searchable?: boolean; title?: string; @@ -157,7 +166,6 @@ export interface HostPickerProps { desktopMinWidth?: number; addHostTestID?: string; hostOptionTestID?: (serverId: string) => string; - hostLocalMarkerTestID?: (serverId: string) => string; children: ReactNode; } @@ -171,7 +179,7 @@ export function HostPicker({ includeAllHost, includeAddHost, onAddHost, - showLocalMarker, + showActiveConnection, onOpenHostSettings, searchable, title, @@ -179,7 +187,6 @@ export function HostPicker({ desktopMinWidth, addHostTestID, hostOptionTestID, - hostLocalMarkerTestID, children, }: HostPickerProps): ReactElement { const localServerId = useLocalDaemonServerId(); @@ -243,23 +250,20 @@ export function HostPicker({ ); }, [ addHostTestID, - hostLocalMarkerTestID, hostOptionTestID, - localServerId, onOpenHostSettings, - showLocalMarker, + showActiveConnection, handleOpenHostSettings, ], ); @@ -292,9 +296,4 @@ const styles = StyleSheet.create((theme) => ({ alignItems: "center", justifyContent: "center", }, - localMarker: { - fontSize: theme.fontSize.xs, - color: theme.colors.foregroundMuted, - marginLeft: theme.spacing[1], - }, })); diff --git a/packages/app/src/components/left-sidebar.tsx b/packages/app/src/components/left-sidebar.tsx index 00b027236..6c6fd9c09 100644 --- a/packages/app/src/components/left-sidebar.tsx +++ b/packages/app/src/components/left-sidebar.tsx @@ -290,10 +290,6 @@ function sidebarHostOptionTestID(serverId: string): string { return `sidebar-host-row-${serverId}`; } -function sidebarHostLocalMarkerTestID(serverId: string): string { - return `sidebar-host-local-marker-${serverId}`; -} - function FooterIconButton({ buttonRef, onPress, @@ -365,13 +361,12 @@ function SidebarHostPicker({ anchorRef={triggerRef} includeAddHost onAddHost={onAddHost} - showLocalMarker + showActiveConnection onOpenHostSettings={onOpenHostSettings} searchable desktopMinWidth={240} addHostTestID="sidebar-host-add" hostOptionTestID={sidebarHostOptionTestID} - hostLocalMarkerTestID={sidebarHostLocalMarkerTestID} > . The local host is listed first and tagged "Local"; an "Add host" - * row is always reachable from the list — even with a single host. + * . The local host is listed first, each row shows the connection it + * is using right now; an "Add host" row is always reachable from the list — + * even with a single host. */ function HostPicker({ activeServerId, sortedHosts, onSelectHost, onAddHost }: HostPickerProps) { const { t } = useTranslation(); @@ -906,7 +907,6 @@ function HostPicker({ activeServerId, sortedHosts, onSelectHost, onAddHost }: Ho sortedHosts.find((host) => host.serverId === activeServerId) ?? sortedHosts[0] ?? null; const handleOpen = useCallback(() => setIsOpen(true), []); - const hostLocalMarkerTestID = useCallback(() => "settings-host-local-marker", []); const hostOptionTestID = useCallback( (serverId: string) => `settings-host-picker-item-${serverId}`, [], @@ -929,12 +929,11 @@ function HostPicker({ activeServerId, sortedHosts, onSelectHost, onAddHost }: Ho anchorRef={triggerRef} includeAddHost onAddHost={onAddHost} - showLocalMarker + showActiveConnection searchable={false} title={t("settings.hostPicker.switchHost")} desktopMinWidth={240} addHostTestID="settings-add-host" - hostLocalMarkerTestID={hostLocalMarkerTestID} hostOptionTestID={hostOptionTestID} >