refactor(app): split the workspace header menu per form factor

Wide layouts make tabs from the tab strip's + menu, so the header menu
listing new agent / new terminal / new browser duplicated it. Compact has
no tab strip, so those actions have to live in the header there.

One menu with an isMobile prop could not express that: the prop only sized
the trigger, and the item list was shared. Two components now, with the
workspace actions (import, copy path, copy branch name, show setup) in one
component both render from the same callbacks so they cannot drift.
This commit is contained in:
Mohamed Boudra
2026-08-18 09:38:30 +02:00
parent ff6424fa4e
commit eb3591cee8
4 changed files with 365 additions and 322 deletions
+9
View File
@@ -26,6 +26,15 @@ when you have actually looked at it on a phone.
`ContextMenu` is the exception: it defaults to `compactMode="sheet"` and enables native long press.
Disable mobile triggering explicitly on draggable rows, where long press belongs to drag instead.
## When the items differ per form factor
A menu whose contents depend on what else is on screen gets one component per surface, not one
component with `isMobile` branches inside it. The workspace header menu is the example
(`packages/app/src/screens/workspace/workspace-header-menu.tsx`): compact has no tab strip, so it
carries new-tab actions, while wide leaves those to the strip's `+` menu and lists workspace
actions only. Items both surfaces share live in one component that each menu renders, from the same
callbacks, so the two can't drift.
## Selecting an item on iOS
An item that closes the menu runs its action after a fixed grace period on iOS, not immediately:
@@ -23,9 +23,15 @@ function ProviderProfileIcon({
const ThemedProviderProfileIcon = withUnistyles(ProviderProfileIcon);
export function TerminalProfileIcon({ iconKey }: { iconKey: string | undefined }): ReactElement {
export function TerminalProfileIcon({
iconKey,
size = 14,
}: {
iconKey: string | undefined;
size?: number;
}): ReactElement {
if (!iconKey) {
return <ThemedSquareTerminal size={14} uniProps={mutedColorMapping} />;
return <ThemedSquareTerminal size={size} uniProps={mutedColorMapping} />;
}
return <ThemedProviderProfileIcon iconKey={iconKey} size={14} uniProps={mutedColorMapping} />;
return <ThemedProviderProfileIcon iconKey={iconKey} size={size} uniProps={mutedColorMapping} />;
}
@@ -0,0 +1,312 @@
import React, { useCallback, useMemo } from "react";
import { View } from "react-native";
import { useRouter, type Href } from "expo-router";
import { useTranslation } from "react-i18next";
import {
Copy,
Ellipsis,
Globe,
Import as ImportIcon,
Settings,
SquarePen,
} from "lucide-react-native";
import { StyleSheet, withUnistyles } from "react-native-unistyles";
import type { TerminalProfile } from "@getpaseo/protocol/messages";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { buttonControlHeight } from "@/components/ui/control-geometry";
import { TerminalProfileIcon } from "@/components/terminal-profile-icon";
import { useDaemonConfig } from "@/hooks/use-daemon-config";
import {
getTerminalProfileIcon,
resolveTerminalProfiles,
} from "@getpaseo/protocol/terminal-profiles";
import { buildSettingsHostSectionRoute } from "@/utils/host-routes";
import type { Theme } from "@/styles/theme";
const ThemedEllipsis = withUnistyles(Ellipsis);
const ThemedCopy = withUnistyles(Copy);
const ThemedSquarePen = withUnistyles(SquarePen);
const ThemedGlobe = withUnistyles(Globe);
const ThemedImport = withUnistyles(ImportIcon);
const ThemedSettings = withUnistyles(Settings);
const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground });
const mutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
const MENU_NEW_AGENT_ICON = <ThemedSquarePen size={16} uniProps={mutedColorMapping} />;
const MENU_NEW_BROWSER_ICON = <ThemedGlobe size={16} uniProps={mutedColorMapping} />;
const MENU_NEW_TERMINAL_ICON = <TerminalProfileIcon iconKey={undefined} size={16} />;
const MENU_IMPORT_ICON = <ThemedImport size={16} uniProps={mutedColorMapping} />;
const MENU_COPY_ICON = <ThemedCopy size={16} uniProps={mutedColorMapping} />;
const MENU_SETTINGS_ICON = <ThemedSettings size={16} uniProps={mutedColorMapping} />;
/**
* The compact header buttons draw at 32pt, under the 44pt touch minimum, and sit flush against
* each other — so the slop can only grow vertically. Widening it would put two buttons' slop over
* the same pixels, which is a worse miss than a small target.
*/
const COMPACT_HEADER_BUTTON_HIT_SLOP = { top: 8, bottom: 8 } as const;
/**
* Actions that belong to the workspace itself rather than to a tab. Both header menus render them,
* from the same callbacks, so the two surfaces can't drift.
*/
export interface WorkspaceHeaderWorkspaceActions {
currentBranchName: string | null;
showWorkspaceSetup: boolean;
importAgentDisabled: boolean;
copyPathDisabled: boolean;
onOpenImportSheet: () => void;
onCopyWorkspacePath: () => void;
onCopyBranchName: () => void;
onOpenSetupTab: () => void;
}
function WorkspaceHeaderWorkspaceActionItems({
currentBranchName,
showWorkspaceSetup,
importAgentDisabled,
copyPathDisabled,
onOpenImportSheet,
onCopyWorkspacePath,
onCopyBranchName,
onOpenSetupTab,
}: WorkspaceHeaderWorkspaceActions) {
const { t } = useTranslation();
return (
<>
<DropdownMenuItem
testID="workspace-header-copy-path"
leading={MENU_COPY_ICON}
disabled={copyPathDisabled}
onSelect={onCopyWorkspacePath}
>
{t("workspace.header.actions.copyPath")}
</DropdownMenuItem>
{currentBranchName ? (
<DropdownMenuItem
testID="workspace-header-copy-branch-name"
leading={MENU_COPY_ICON}
onSelect={onCopyBranchName}
>
{t("workspace.header.actions.copyBranchName")}
</DropdownMenuItem>
) : null}
<DropdownMenuItem
testID="workspace-header-import-agent"
leading={MENU_IMPORT_ICON}
disabled={importAgentDisabled}
onSelect={onOpenImportSheet}
>
{t("workspace.header.actions.importSession")}
</DropdownMenuItem>
{showWorkspaceSetup ? (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
testID="workspace-header-show-setup"
leading={MENU_SETTINGS_ICON}
onSelect={onOpenSetupTab}
>
{t("workspace.header.actions.showSetup")}
</DropdownMenuItem>
</>
) : null}
</>
);
}
function WorkspaceHeaderMenuTriggerIcon({ hovered, open }: { hovered: boolean; open: boolean }) {
const colorMapping = hovered || open ? foregroundColorMapping : mutedColorMapping;
return <ThemedEllipsis size={16} uniProps={colorMapping} />;
}
function renderTriggerIcon({ hovered, open }: { hovered: boolean; open: boolean }) {
return <WorkspaceHeaderMenuTriggerIcon hovered={hovered} open={open} />;
}
/**
* Wide layouts make tabs from the tab strip's `+` menu, so this one carries workspace actions only.
*/
export function WorkspaceHeaderMenuDesktop(props: WorkspaceHeaderWorkspaceActions) {
const { t } = useTranslation();
return (
<DropdownMenu>
<DropdownMenuTrigger
testID="workspace-header-menu-trigger"
style={styles.headerActionButton}
accessibilityRole="button"
accessibilityLabel={t("workspace.header.actions.workspaceActions")}
>
{renderTriggerIcon}
</DropdownMenuTrigger>
<DropdownMenuContent align="start" width={220} testID="workspace-header-menu">
<WorkspaceHeaderWorkspaceActionItems {...props} />
</DropdownMenuContent>
</DropdownMenu>
);
}
interface HeaderMenuProfileItemProps {
profile: TerminalProfile;
disabled: boolean;
onCreateTerminalWithProfile: (profile: TerminalProfile) => void;
}
function HeaderMenuProfileItem({
profile,
disabled,
onCreateTerminalWithProfile,
}: HeaderMenuProfileItemProps) {
const handleSelect = useCallback(() => {
onCreateTerminalWithProfile(profile);
}, [onCreateTerminalWithProfile, profile]);
const leading = useMemo(
() => (
<View style={styles.headerMenuProfileIconWrapper}>
<TerminalProfileIcon iconKey={getTerminalProfileIcon(profile)} size={16} />
</View>
),
[profile],
);
return (
<DropdownMenuItem leading={leading} disabled={disabled} onSelect={handleSelect}>
{profile.name}
</DropdownMenuItem>
);
}
export interface WorkspaceHeaderMenuMobileProps extends WorkspaceHeaderWorkspaceActions {
normalizedServerId: string;
showCreateBrowserTab: boolean;
createTerminalDisabled: boolean;
onCreateDraftTab: () => void;
onCreateTerminal: () => void;
onCreateTerminalWithProfile: (profile: TerminalProfile) => void;
onCreateBrowser: () => void;
}
/**
* Compact layouts have no tab strip to launch from, so new tabs live here alongside the workspace
* actions.
*/
export function WorkspaceHeaderMenuMobile({
normalizedServerId,
showCreateBrowserTab,
createTerminalDisabled,
onCreateDraftTab,
onCreateTerminal,
onCreateTerminalWithProfile,
onCreateBrowser,
...workspaceActions
}: WorkspaceHeaderMenuMobileProps) {
const { t } = useTranslation();
const router = useRouter();
const { config } = useDaemonConfig(normalizedServerId);
const profiles = useMemo(
() => resolveTerminalProfiles(config?.terminalProfiles),
[config?.terminalProfiles],
);
const handleEditProfiles = useCallback(() => {
router.push(buildSettingsHostSectionRoute(normalizedServerId, "terminals") as Href);
}, [normalizedServerId, router]);
return (
<DropdownMenu compactMode="sheet">
<DropdownMenuTrigger
testID="workspace-header-menu-trigger"
style={styles.compactHeaderActionButton}
hitSlop={COMPACT_HEADER_BUTTON_HIT_SLOP}
accessibilityRole="button"
accessibilityLabel={t("workspace.header.actions.workspaceActions")}
>
{renderTriggerIcon}
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
width={220}
testID="workspace-header-menu"
sheetTitle={t("workspace.header.actions.workspaceActions")}
>
<DropdownMenuItem
testID="workspace-header-new-agent"
leading={MENU_NEW_AGENT_ICON}
onSelect={onCreateDraftTab}
>
{t("workspace.header.actions.newAgent")}
</DropdownMenuItem>
{showCreateBrowserTab ? (
<DropdownMenuItem
testID="workspace-header-new-browser"
leading={MENU_NEW_BROWSER_ICON}
onSelect={onCreateBrowser}
>
{t("workspace.header.actions.newBrowser")}
</DropdownMenuItem>
) : null}
<WorkspaceHeaderWorkspaceActionItems {...workspaceActions} />
<DropdownMenuSeparator />
<DropdownMenuLabel>{t("workspace.tabs.actions.terminalProfilesMenu")}</DropdownMenuLabel>
<DropdownMenuItem
testID="workspace-header-new-terminal"
leading={MENU_NEW_TERMINAL_ICON}
disabled={createTerminalDisabled}
onSelect={onCreateTerminal}
>
{t("workspace.header.actions.newTerminal")}
</DropdownMenuItem>
{profiles.map((profile) => (
<HeaderMenuProfileItem
key={profile.id}
profile={profile}
disabled={createTerminalDisabled}
onCreateTerminalWithProfile={onCreateTerminalWithProfile}
/>
))}
<DropdownMenuSeparator />
<DropdownMenuItem
testID="workspace-header-edit-terminal-profiles"
onSelect={handleEditProfiles}
>
{t("workspace.tabs.actions.editTerminalProfiles")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
const styles = StyleSheet.create((theme) => ({
headerActionButton: {
paddingVertical: theme.spacing[2],
paddingHorizontal: theme.spacing[2],
borderRadius: theme.borderRadius.lg,
},
compactHeaderActionButton: {
width: {
xs: theme.spacing[8],
md: buttonControlHeight.xs,
},
height: {
xs: theme.spacing[8],
md: buttonControlHeight.xs,
},
padding: 0,
borderRadius: theme.borderRadius.lg,
alignItems: "center",
justifyContent: "center",
},
headerMenuProfileIconWrapper: {
width: 16,
height: 16,
},
}));
@@ -19,17 +19,7 @@ import { useQueryClient } from "@tanstack/react-query";
import { useRouter, type Href } from "expo-router";
import * as Clipboard from "expo-clipboard";
import { useTranslation } from "react-i18next";
import {
ChevronDown,
Copy,
Ellipsis,
Globe,
Import as ImportIcon,
PanelRight,
Settings,
SquarePen,
SquareTerminal,
} from "lucide-react-native";
import { ChevronDown, PanelRight } from "lucide-react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { StyleSheet, withUnistyles } from "react-native-unistyles";
import type { Theme } from "@/styles/theme";
@@ -43,14 +33,6 @@ import { HostBadge } from "@/hosts/host-badge";
import { useHostBadges } from "@/hosts/use-host-badges";
import { Combobox, type ComboboxOption } from "@/components/ui/combobox";
import type { ShortcutKey } from "@/utils/format-shortcut";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
FloatingPanelPortalHost,
FloatingPanelPortalHostNameProvider,
@@ -175,19 +157,13 @@ import { findAdjacentPane } from "@/utils/split-navigation";
import { supportsDesktopPaneSplits, useIsCompactFormFactor } from "@/constants/layout";
import { getIsElectron, isNative, isWeb } from "@/constants/platform";
import type { SurfaceBackdrop } from "@/styles/surface-backdrop";
import {
buildHostRootRoute,
buildSettingsHostRoute,
buildSettingsHostSectionRoute,
} from "@/utils/host-routes";
import { buildHostRootRoute, buildSettingsHostRoute } from "@/utils/host-routes";
import { useWorkspaceTerminals } from "@/screens/workspace/terminals/use-workspace-terminals";
import type { TerminalProfile } from "@getpaseo/protocol/messages";
import { useDaemonConfig } from "@/hooks/use-daemon-config";
import {
getTerminalProfileIcon,
resolveTerminalProfiles,
} from "@getpaseo/protocol/terminal-profiles";
import { getProviderIcon } from "@/components/provider-icons";
WorkspaceHeaderMenuDesktop,
WorkspaceHeaderMenuMobile,
} from "@/screens/workspace/workspace-header-menu";
import {
createWorkspaceFileTabTarget,
normalizeWorkspaceFileLocation,
@@ -237,41 +213,15 @@ function buildWorkspaceFileLocation(
}
const ThemedLoadingSpinner = withUnistyles(LoadingSpinner);
const ThemedEllipsis = withUnistyles(Ellipsis);
const ThemedChevronDown = withUnistyles(ChevronDown);
const ThemedCopy = withUnistyles(Copy);
const ThemedSquarePen = withUnistyles(SquarePen);
const ThemedSquareTerminal = withUnistyles(SquareTerminal);
const ThemedGlobe = withUnistyles(Globe);
const ThemedImport = withUnistyles(ImportIcon);
const ThemedSettings = withUnistyles(Settings);
const ThemedPanelRight = withUnistyles(PanelRight);
interface DynamicProviderIconProps {
iconKey: string;
size: number;
color?: string;
}
function DynamicProviderIcon({ iconKey, size, color = "" }: DynamicProviderIconProps) {
const Icon = getProviderIcon(iconKey);
return <Icon size={size} color={color} />;
}
const ThemedDynamicProviderIcon = withUnistyles(DynamicProviderIcon);
const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground });
const mutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
const extraMutedColorMapping = (theme: Theme) => ({
color: theme.colors.foregroundExtraMuted,
});
const MENU_NEW_AGENT_ICON = <ThemedSquarePen size={16} uniProps={mutedColorMapping} />;
const MENU_NEW_TERMINAL_ICON = <ThemedSquareTerminal size={16} uniProps={mutedColorMapping} />;
const MENU_NEW_BROWSER_ICON = <ThemedGlobe size={16} uniProps={mutedColorMapping} />;
const MENU_IMPORT_ICON = <ThemedImport size={16} uniProps={mutedColorMapping} />;
const MENU_COPY_ICON = <ThemedCopy size={16} uniProps={mutedColorMapping} />;
const MENU_SETTINGS_ICON = <ThemedSettings size={16} uniProps={mutedColorMapping} />;
const GATED_WORKSPACE_HEADER_LEFT = <SidebarMenuToggle />;
interface WorkspaceScreenProps {
@@ -910,224 +860,6 @@ function useCloseTabs(): UseCloseTabsResult {
return { closingTabIds, closeTab };
}
interface WorkspaceHeaderMenuProps {
normalizedServerId: string;
currentBranchName: string | null;
showWorkspaceSetup: boolean;
showCreateBrowserTab: boolean;
isMobile: boolean;
createTerminalDisabled: boolean;
importAgentDisabled: boolean;
copyPathDisabled: boolean;
menuNewAgentIcon: ReactElement;
menuNewTerminalIcon: ReactElement;
menuNewBrowserIcon: ReactElement;
menuImportIcon: ReactElement;
menuCopyIcon: ReactElement;
menuSettingsIcon: ReactElement;
onCreateDraftTab: () => void;
onCreateTerminal: () => void;
onCreateTerminalWithProfile: (profile: TerminalProfile) => void;
onCreateBrowser: () => void;
onOpenImportSheet: () => void;
onCopyWorkspacePath: () => void;
onCopyBranchName: () => void;
onOpenSetupTab: () => void;
}
interface HeaderMenuProfileItemProps {
profile: { id: string; name: string; command: string; args?: string[]; icon?: string };
disabled: boolean;
onCreateTerminalWithProfile: (profile: TerminalProfile) => void;
}
function HeaderMenuProfileItem({
profile,
disabled,
onCreateTerminalWithProfile,
}: HeaderMenuProfileItemProps) {
const handleSelect = useCallback(() => {
onCreateTerminalWithProfile(profile);
}, [onCreateTerminalWithProfile, profile]);
const icon = getTerminalProfileIcon(profile);
const leading = useMemo(() => {
if (!icon) {
return (
<View style={styles.headerMenuProfileIconWrapper}>
<ThemedSquareTerminal size={16} uniProps={mutedColorMapping} />
</View>
);
}
return (
<View style={styles.headerMenuProfileIconWrapper}>
<ThemedDynamicProviderIcon iconKey={icon} size={16} uniProps={mutedColorMapping} />
</View>
);
}, [icon]);
return (
<DropdownMenuItem leading={leading} disabled={disabled} onSelect={handleSelect}>
{profile.name}
</DropdownMenuItem>
);
}
/**
* The compact header buttons draw at 32pt, under the 44pt touch minimum, and sit flush against
* each other — so the slop can only grow vertically. Widening it would put two buttons' slop over
* the same pixels, which is a worse miss than a small target.
*/
const COMPACT_HEADER_BUTTON_HIT_SLOP = { top: 8, bottom: 8 } as const;
function WorkspaceHeaderMenuTriggerIcon({ hovered, open }: { hovered: boolean; open: boolean }) {
const colorMapping = hovered || open ? foregroundColorMapping : mutedColorMapping;
return <ThemedEllipsis size={16} uniProps={colorMapping} />;
}
function WorkspaceHeaderMenu({
normalizedServerId,
currentBranchName,
showWorkspaceSetup,
showCreateBrowserTab,
isMobile,
createTerminalDisabled,
importAgentDisabled,
copyPathDisabled,
menuNewAgentIcon,
menuNewTerminalIcon,
menuNewBrowserIcon,
menuImportIcon,
menuCopyIcon,
menuSettingsIcon,
onCreateDraftTab,
onCreateTerminal,
onCreateTerminalWithProfile,
onCreateBrowser,
onOpenImportSheet,
onCopyWorkspacePath,
onCopyBranchName,
onOpenSetupTab,
}: WorkspaceHeaderMenuProps) {
const { t } = useTranslation();
const router = useRouter();
const { config } = useDaemonConfig(normalizedServerId);
const profiles = useMemo(
() => resolveTerminalProfiles(config?.terminalProfiles),
[config?.terminalProfiles],
);
const handleEditProfiles = useCallback(() => {
router.push(buildSettingsHostSectionRoute(normalizedServerId, "terminals") as Href);
}, [normalizedServerId, router]);
const renderTriggerIcon = useCallback(
({ hovered, open }: { hovered: boolean; open: boolean }) => (
<WorkspaceHeaderMenuTriggerIcon hovered={hovered} open={open} />
),
[],
);
return (
<DropdownMenu compactMode="sheet">
<DropdownMenuTrigger
testID="workspace-header-menu-trigger"
style={isMobile ? styles.compactHeaderActionButton : styles.headerActionButton}
hitSlop={isMobile ? COMPACT_HEADER_BUTTON_HIT_SLOP : undefined}
accessibilityRole="button"
accessibilityLabel={t("workspace.header.actions.workspaceActions")}
>
{renderTriggerIcon}
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
width={220}
testID="workspace-header-menu"
sheetTitle={t("workspace.header.actions.workspaceActions")}
>
<DropdownMenuItem
testID="workspace-header-new-agent"
leading={menuNewAgentIcon}
onSelect={onCreateDraftTab}
>
{t("workspace.header.actions.newAgent")}
</DropdownMenuItem>
{showCreateBrowserTab ? (
<DropdownMenuItem
testID="workspace-header-new-browser"
leading={menuNewBrowserIcon}
onSelect={onCreateBrowser}
>
{t("workspace.header.actions.newBrowser")}
</DropdownMenuItem>
) : null}
<DropdownMenuItem
testID="workspace-header-import-agent"
leading={menuImportIcon}
disabled={importAgentDisabled}
onSelect={onOpenImportSheet}
>
{t("workspace.header.actions.importSession")}
</DropdownMenuItem>
<DropdownMenuItem
testID="workspace-header-copy-path"
leading={menuCopyIcon}
disabled={copyPathDisabled}
onSelect={onCopyWorkspacePath}
>
{t("workspace.header.actions.copyPath")}
</DropdownMenuItem>
{currentBranchName ? (
<DropdownMenuItem
testID="workspace-header-copy-branch-name"
leading={menuCopyIcon}
onSelect={onCopyBranchName}
>
{t("workspace.header.actions.copyBranchName")}
</DropdownMenuItem>
) : null}
{showWorkspaceSetup ? (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
testID="workspace-header-show-setup"
leading={menuSettingsIcon}
onSelect={onOpenSetupTab}
>
{t("workspace.header.actions.showSetup")}
</DropdownMenuItem>
</>
) : null}
<DropdownMenuSeparator />
<DropdownMenuLabel>{t("workspace.tabs.actions.terminalProfilesMenu")}</DropdownMenuLabel>
<DropdownMenuItem
testID="workspace-header-new-terminal"
leading={menuNewTerminalIcon}
disabled={createTerminalDisabled}
onSelect={onCreateTerminal}
>
{t("workspace.header.actions.newTerminal")}
</DropdownMenuItem>
{profiles.map((profile) => (
<HeaderMenuProfileItem
key={profile.id}
profile={profile}
disabled={createTerminalDisabled}
onCreateTerminalWithProfile={onCreateTerminalWithProfile}
/>
))}
<DropdownMenuSeparator />
<DropdownMenuItem
testID="workspace-header-edit-terminal-profiles"
onSelect={handleEditProfiles}
>
{t("workspace.tabs.actions.editTerminalProfiles")}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
/**
* Which project the workspace belongs to, and which machine it runs on.
*
@@ -1185,12 +917,6 @@ interface WorkspaceHeaderTitleBarProps {
createTerminalDisabled: boolean;
importAgentDisabled: boolean;
copyPathDisabled: boolean;
menuNewAgentIcon: ReactElement;
menuNewTerminalIcon: ReactElement;
menuNewBrowserIcon: ReactElement;
menuImportIcon: ReactElement;
menuCopyIcon: ReactElement;
menuSettingsIcon: ReactElement;
onCreateDraftTab: () => void;
onCreateTerminal: () => void;
onCreateTerminalWithProfile: (profile: TerminalProfile) => void;
@@ -1220,12 +946,6 @@ function WorkspaceHeaderTitleBar({
createTerminalDisabled,
importAgentDisabled,
copyPathDisabled,
menuNewAgentIcon,
menuNewTerminalIcon,
menuNewBrowserIcon,
menuImportIcon,
menuCopyIcon,
menuSettingsIcon,
onCreateDraftTab,
onCreateTerminal,
onCreateTerminalWithProfile,
@@ -1255,21 +975,15 @@ function WorkspaceHeaderTitleBar({
</View>
)}
<View style={styles.compactHeaderMenuCluster}>
<WorkspaceHeaderMenu
{isMobile ? (
<WorkspaceHeaderMenuMobile
normalizedServerId={normalizedServerId}
currentBranchName={currentBranchName}
showWorkspaceSetup={showWorkspaceSetup}
showCreateBrowserTab={showCreateBrowserTab}
isMobile={isMobile}
createTerminalDisabled={createTerminalDisabled}
importAgentDisabled={importAgentDisabled}
copyPathDisabled={copyPathDisabled}
menuNewAgentIcon={menuNewAgentIcon}
menuNewTerminalIcon={menuNewTerminalIcon}
menuNewBrowserIcon={menuNewBrowserIcon}
menuImportIcon={menuImportIcon}
menuCopyIcon={menuCopyIcon}
menuSettingsIcon={menuSettingsIcon}
onCreateDraftTab={onCreateDraftTab}
onCreateTerminal={onCreateTerminal}
onCreateTerminalWithProfile={onCreateTerminalWithProfile}
@@ -1279,6 +993,18 @@ function WorkspaceHeaderTitleBar({
onCopyBranchName={onCopyBranchName}
onOpenSetupTab={onOpenSetupTab}
/>
) : (
<WorkspaceHeaderMenuDesktop
currentBranchName={currentBranchName}
showWorkspaceSetup={showWorkspaceSetup}
importAgentDisabled={importAgentDisabled}
copyPathDisabled={copyPathDisabled}
onOpenImportSheet={onOpenImportSheet}
onCopyWorkspacePath={onCopyWorkspacePath}
onCopyBranchName={onCopyBranchName}
onOpenSetupTab={onOpenSetupTab}
/>
)}
{isMobile && workspaceScripts.length > 0 ? (
<WorkspaceScriptsButton
serverId={normalizedServerId}
@@ -3547,10 +3273,6 @@ function WorkspaceScreenContent({
const containerStyle = [styles.container, styles.containerWorkspaceBackground];
const menuNewAgentIcon = MENU_NEW_AGENT_ICON;
const menuNewTerminalIcon = MENU_NEW_TERMINAL_ICON;
const menuCopyIcon = MENU_COPY_ICON;
const menuSettingsIcon = MENU_SETTINGS_ICON;
const workspaceScreenGate = renderWorkspaceRouteGate({
state: workspaceRouteState,
actions: {
@@ -3777,12 +3499,6 @@ function WorkspaceScreenContent({
createTerminalDisabled={createTerminalDisabled}
importAgentDisabled={!canOpenImportSheet}
copyPathDisabled={!workspaceDirectory}
menuNewAgentIcon={menuNewAgentIcon}
menuNewTerminalIcon={menuNewTerminalIcon}
menuNewBrowserIcon={MENU_NEW_BROWSER_ICON}
menuImportIcon={MENU_IMPORT_ICON}
menuCopyIcon={menuCopyIcon}
menuSettingsIcon={menuSettingsIcon}
onCreateDraftTab={handleCreateDraftTab}
onCreateTerminal={handleCreateTerminal}
onCreateTerminalWithProfile={handleCreateTerminalWithProfile}