fix(app): clean up host selection affordances

Single-host users should not see host-selection chrome, while multi-host surfaces keep shared combobox feedback and consistent hover-card metadata rows.
This commit is contained in:
Mohamed Boudra
2026-06-26 20:28:55 +07:00
parent 1ce00dca1f
commit 07680cdd69
6 changed files with 84 additions and 42 deletions
@@ -100,7 +100,6 @@ export function HostPickerOption({
trailingSlot={trailingSlot}
selected={selected}
active={active}
interactiveFeedback={false}
onPress={onPress}
testID={testID}
/>
@@ -134,7 +133,6 @@ function SystemHostPickerOption({
leadingSlot={leadingSlot}
selected={selected}
active={active}
interactiveFeedback={false}
onPress={onPress}
testID={testID}
/>
+4 -10
View File
@@ -230,7 +230,6 @@ export interface ComboboxItemProps {
disabled?: boolean;
/** When true, bumps hover/pressed colors up one surface level (for items on elevated backgrounds). */
elevated?: boolean;
interactiveFeedback?: boolean;
onPress: () => void;
testID?: string;
}
@@ -245,7 +244,6 @@ export function ComboboxItem({
active,
disabled,
elevated,
interactiveFeedback = true,
onPress,
testID,
}: ComboboxItemProps): ReactElement {
@@ -271,16 +269,12 @@ export function ComboboxItem({
const itemPressableStyle = useCallback(
({ pressed, hovered = false }: PressableStateCallbackType & { hovered?: boolean }) => [
styles.comboboxItem,
interactiveFeedback &&
hovered &&
(elevated ? styles.comboboxItemHoveredElevated : styles.comboboxItemHovered),
interactiveFeedback &&
pressed &&
(elevated ? styles.comboboxItemPressedElevated : styles.comboboxItemPressed),
interactiveFeedback && active && styles.comboboxItemActive,
hovered && (elevated ? styles.comboboxItemHoveredElevated : styles.comboboxItemHovered),
pressed && (elevated ? styles.comboboxItemPressedElevated : styles.comboboxItemPressed),
active && styles.comboboxItemActive,
disabled && styles.comboboxItemDisabled,
],
[elevated, active, disabled, interactiveFeedback],
[elevated, active, disabled],
);
const itemContentStyle = useMemo(
@@ -21,6 +21,7 @@ import {
ExternalLink,
Folder,
GitBranch,
Server,
} from "lucide-react-native";
import { GitHubIcon } from "@/components/icons/github-icon";
import type { Theme } from "@/styles/theme";
@@ -39,8 +40,7 @@ import { useHoverSafeZone } from "@/hooks/use-hover-safe-zone";
import { useIsCompactFormFactor } from "@/constants/layout";
import { FloatingSurface } from "@/components/ui/floating";
import { isWeb } from "@/constants/platform";
import { useHosts, useHostRuntimeSnapshot } from "@/runtime/host-runtime";
import { formatConnectionStatus } from "@/utils/daemons";
import { useHosts } from "@/runtime/host-runtime";
interface Rect {
x: number;
@@ -330,20 +330,16 @@ function WorkspaceHoverCardContent({
const ThemedGitBranch = withUnistyles(GitBranch);
const ThemedFolder = withUnistyles(Folder);
const ThemedServer = withUnistyles(Server);
type CardInfoIcon = React.ComponentType<React.ComponentProps<typeof ThemedGitBranch>>;
function HostRow({ serverId }: { serverId: string }): ReactElement | null {
const hosts = useHosts();
const host = hosts.find((h) => h.serverId === serverId);
const snapshot = useHostRuntimeSnapshot(serverId);
const label = host?.label?.trim() || serverId;
const status = formatConnectionStatus(snapshot?.connectionStatus ?? "idle");
return (
<View style={styles.hostRow}>
<Text style={styles.hostRowLabel}>{label}</Text>
<Text style={styles.hostRowStatus}>{status}</Text>
</View>
);
return <InfoRow icon={ThemedServer} value={label} testID="hover-card-workspace-host" />;
}
const ThemedExternalLink = withUnistyles(ExternalLink);
@@ -360,6 +356,25 @@ const successColorMapping = (theme: Theme) => ({ color: theme.colors.statusSucce
const warningColorMapping = (theme: Theme) => ({ color: theme.colors.statusWarning });
const dangerColorMapping = (theme: Theme) => ({ color: theme.colors.statusDanger });
function InfoRow({
icon: Icon,
value,
testID,
}: {
icon: CardInfoIcon;
value: string;
testID: string;
}) {
return (
<View style={styles.cardInfoRow}>
<Icon size={12} uniProps={foregroundMutedColorMapping} />
<Text style={styles.cardInfoText} numberOfLines={1} testID={testID}>
{value}
</Text>
</View>
);
}
function CopyableInfoRow({
icon: Icon,
value,
@@ -367,7 +382,7 @@ function CopyableInfoRow({
copyLabel,
testID,
}: {
icon: React.ComponentType<React.ComponentProps<typeof ThemedGitBranch>>;
icon: CardInfoIcon;
value: string;
copyValue: string;
copyLabel: string;
@@ -567,22 +582,6 @@ const styles = StyleSheet.create((theme) => ({
flex: 1,
minWidth: 0,
},
hostRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
gap: theme.spacing[2],
paddingHorizontal: theme.spacing[3],
paddingBottom: theme.spacing[2],
},
hostRowLabel: {
fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted,
},
hostRowStatus: {
fontSize: theme.fontSize.xs,
color: theme.colors.foregroundMuted,
},
cardMetaRow: {
flexDirection: "row",
alignItems: "center",
@@ -1196,6 +1196,7 @@ function useNewWorkspaceFormStack(input: NewWorkspaceFormStackInput): ReactEleme
const selectedHostLabel =
host.allHosts.find((h) => h.serverId === host.selectedServerId)?.label ?? "Host";
const showHostControl = host.allHosts.length > 1;
const isolationTriggerLabel = isolationLabel(t, isolation.effectiveIsolation);
const badgePressableStyle = useCallback(
@@ -1242,7 +1243,7 @@ function useNewWorkspaceFormStack(input: NewWorkspaceFormStackInput): ReactEleme
</View>
);
const hostControl = (
const hostControl = showHostControl ? (
<View>
<HostPicker
hosts={host.allHosts}
@@ -1271,7 +1272,7 @@ function useNewWorkspaceFormStack(input: NewWorkspaceFormStackInput): ReactEleme
</Pressable>
</HostPicker>
</View>
);
) : null;
const isolationControl = isolation.canCreateWorktree ? (
<View>
@@ -1334,7 +1335,7 @@ function useNewWorkspaceFormStack(input: NewWorkspaceFormStackInput): ReactEleme
return isCompact ? (
<View testID="new-workspace-ref-picker-row" style={styles.formStack}>
<FormRow>{projectControl}</FormRow>
<FormRow>{hostControl}</FormRow>
{hostControl ? <FormRow>{hostControl}</FormRow> : null}
{/* Keep fixed row height when git-only controls are hidden. */}
{isolationControl ? (
<FormRow>{isolationControl}</FormRow>