feat: add iOS simulator worktree preview
Adds the worktree service, setup cache seeding, and Metro preview mount for /.sim. Also includes the related startup and mobile sidebar fixes needed by the updated worktree app flow.
This commit is contained in:
@@ -655,21 +655,6 @@ function buildComposerConfig(input: {
|
||||
};
|
||||
}
|
||||
|
||||
function computeWorkspaceTitle(
|
||||
workspace: ReturnType<typeof normalizeWorkspaceDescriptor> | null,
|
||||
displayName: string,
|
||||
sourceDirectory: string | null,
|
||||
): string {
|
||||
const fallbackDirectoryName = sourceDirectory?.split(/[\\/]/).findLast(Boolean) ?? null;
|
||||
return (
|
||||
workspace?.name ||
|
||||
workspace?.projectDisplayName ||
|
||||
displayName ||
|
||||
fallbackDirectoryName ||
|
||||
"Choose project"
|
||||
);
|
||||
}
|
||||
|
||||
function collectAttachedPrNumbers(attachments: ReadonlyArray<UserComposerAttachment>): Set<number> {
|
||||
const numbers = new Set<number>();
|
||||
for (const attachment of attachments) {
|
||||
@@ -801,7 +786,6 @@ export function NewWorkspaceScreen({
|
||||
projects,
|
||||
selectedProject,
|
||||
selectedSourceDirectory,
|
||||
selectedDisplayName,
|
||||
projectPickerOptions,
|
||||
projectByOptionId,
|
||||
selectedProjectOptionId,
|
||||
@@ -1091,12 +1075,6 @@ export function NewWorkspaceScreen({
|
||||
[composerState, draftKey, ensureWorkspace, serverId, toast],
|
||||
);
|
||||
|
||||
const workspaceTitle = computeWorkspaceTitle(
|
||||
workspace,
|
||||
selectedDisplayName,
|
||||
selectedSourceDirectory,
|
||||
);
|
||||
|
||||
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null);
|
||||
const handleAddImagesCallback = useCallback((addImages: (images: ImageAttachment[]) => void) => {
|
||||
addImagesRef.current = addImages;
|
||||
@@ -1308,30 +1286,18 @@ export function NewWorkspaceScreen({
|
||||
triggerLabel,
|
||||
],
|
||||
);
|
||||
const screenHeaderLeft = useMemo(() => <SidebarMenuToggle />, []);
|
||||
|
||||
return (
|
||||
<FileDropZone onFilesDropped={handleFilesDropped}>
|
||||
<View style={styles.container}>
|
||||
<ScreenHeader
|
||||
left={
|
||||
<>
|
||||
<SidebarMenuToggle />
|
||||
<View style={styles.headerTitleContainer}>
|
||||
<Text style={styles.headerTitle} numberOfLines={1}>
|
||||
New workspace
|
||||
</Text>
|
||||
<Text style={styles.headerProjectTitle} numberOfLines={1}>
|
||||
{workspaceTitle}
|
||||
</Text>
|
||||
</View>
|
||||
</>
|
||||
}
|
||||
leftStyle={styles.headerLeft}
|
||||
borderless
|
||||
/>
|
||||
<ScreenHeader left={screenHeaderLeft} borderless />
|
||||
<View style={contentStyle}>
|
||||
<TitlebarDragRegion />
|
||||
<View style={styles.centered}>
|
||||
<View style={styles.composerTitleContainer}>
|
||||
<Text style={styles.composerTitle}>New workspace</Text>
|
||||
</View>
|
||||
<Composer
|
||||
agentId={draftKey}
|
||||
serverId={serverId}
|
||||
@@ -1385,29 +1351,15 @@ const styles = StyleSheet.create((theme) => ({
|
||||
width: "100%",
|
||||
maxWidth: MAX_CONTENT_WIDTH,
|
||||
},
|
||||
headerLeft: {
|
||||
gap: theme.spacing[2],
|
||||
composerTitleContainer: {
|
||||
marginBottom: theme.spacing[8],
|
||||
paddingLeft: theme.spacing[6],
|
||||
paddingRight: theme.spacing[4],
|
||||
},
|
||||
headerTitleContainer: {
|
||||
flexShrink: 1,
|
||||
minWidth: 0,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing[2],
|
||||
},
|
||||
headerTitle: {
|
||||
fontSize: theme.fontSize.base,
|
||||
fontWeight: {
|
||||
xs: "400",
|
||||
md: "300",
|
||||
},
|
||||
composerTitle: {
|
||||
fontSize: theme.fontSize.xl,
|
||||
fontWeight: theme.fontWeight.normal,
|
||||
color: theme.colors.foreground,
|
||||
flexShrink: 0,
|
||||
},
|
||||
headerProjectTitle: {
|
||||
color: theme.colors.foregroundMuted,
|
||||
fontSize: theme.fontSize.base,
|
||||
flexShrink: 1,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: theme.fontSize.sm,
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
RotateCw,
|
||||
Rows2,
|
||||
Globe,
|
||||
Plus,
|
||||
SquarePen,
|
||||
SquareTerminal,
|
||||
X,
|
||||
@@ -48,6 +49,7 @@ import { Shortcut } from "@/components/ui/shortcut";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useShortcutKeys } from "@/hooks/use-shortcut-keys";
|
||||
import { WORKSPACE_SECONDARY_HEADER_HEIGHT } from "@/constants/layout";
|
||||
import type { ShortcutKey } from "@/utils/format-shortcut";
|
||||
import { useWorkspaceTabLayout } from "@/screens/workspace/use-workspace-tab-layout";
|
||||
import {
|
||||
WorkspaceTabPresentationResolver,
|
||||
@@ -66,6 +68,7 @@ import { RenderProfile } from "@/utils/render-profiler";
|
||||
|
||||
const DROPDOWN_WIDTH = 220;
|
||||
const LOADING_TAB_LABEL_SKELETON_WIDTH = 80;
|
||||
const DEFAULT_INLINE_ADD_BUTTON_RESERVED_WIDTH = 36;
|
||||
|
||||
const ThemedActivityIndicator = withUnistyles(ActivityIndicator);
|
||||
const ThemedX = withUnistyles(X);
|
||||
@@ -80,6 +83,7 @@ const ThemedSquareTerminal = withUnistyles(SquareTerminal);
|
||||
const ThemedGlobe = withUnistyles(Globe);
|
||||
const ThemedColumns2 = withUnistyles(Columns2);
|
||||
const ThemedRows2 = withUnistyles(Rows2);
|
||||
const ThemedPlus = withUnistyles(Plus);
|
||||
|
||||
const foregroundColorMapping = (theme: Theme) => ({ color: theme.colors.foreground });
|
||||
const mutedColorMapping = (theme: Theme) => ({ color: theme.colors.foregroundMuted });
|
||||
@@ -88,6 +92,51 @@ function newTabActionButtonStyle({ hovered, pressed }: PressableStateCallbackTyp
|
||||
return [styles.newTabActionButton, (hovered || pressed) && styles.newTabActionButtonHovered];
|
||||
}
|
||||
|
||||
function inlineAddActionButtonStyle({ hovered, pressed }: PressableStateCallbackType) {
|
||||
return [styles.inlineAddActionButton, (hovered || pressed) && styles.newTabActionButtonHovered];
|
||||
}
|
||||
|
||||
function updateMeasuredWidth(setWidth: Dispatch<SetStateAction<number>>, event: LayoutChangeEvent) {
|
||||
const nextWidth = Math.round(event.nativeEvent.layout.width);
|
||||
setWidth((current) => (Math.abs(current - nextWidth) > 1 ? nextWidth : current));
|
||||
}
|
||||
|
||||
interface WorkspaceInlineAddTabButtonProps {
|
||||
shortcutKeys: ShortcutKey[][] | null;
|
||||
onPress: () => void;
|
||||
onLayout: (event: LayoutChangeEvent) => void;
|
||||
}
|
||||
|
||||
function WorkspaceInlineAddTabButton({
|
||||
shortcutKeys,
|
||||
onPress,
|
||||
onLayout,
|
||||
}: WorkspaceInlineAddTabButtonProps) {
|
||||
return (
|
||||
<View style={styles.inlineAddButton} onLayout={onLayout}>
|
||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||
<TooltipTrigger
|
||||
testID="workspace-new-agent-tab-inline"
|
||||
onPress={onPress}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel="New agent tab"
|
||||
style={inlineAddActionButtonStyle}
|
||||
>
|
||||
<ThemedPlus size={16} uniProps={mutedColorMapping} />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" align="center" offset={8}>
|
||||
<View style={styles.newTabTooltipRow}>
|
||||
<Text style={styles.newTabTooltipText}>New agent tab</Text>
|
||||
{shortcutKeys ? (
|
||||
<Shortcut chord={shortcutKeys} style={styles.newTabTooltipShortcut} />
|
||||
) : null}
|
||||
</View>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function TabContextMenuItem({
|
||||
entry,
|
||||
}: {
|
||||
@@ -490,21 +539,27 @@ export function WorkspaceDesktopTabsRow({
|
||||
const splitDownKeys = useShortcutKeys("workspace-pane-split-down");
|
||||
const [tabsContainerWidth, setTabsContainerWidth] = useState<number>(0);
|
||||
const [tabsActionsWidth, setTabsActionsWidth] = useState<number>(0);
|
||||
const [inlineAddButtonWidth, setInlineAddButtonWidth] = useState<number>(0);
|
||||
|
||||
const handleTabsContainerLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const nextWidth = Math.round(event.nativeEvent.layout.width);
|
||||
setTabsContainerWidth((current) => (Math.abs(current - nextWidth) > 1 ? nextWidth : current));
|
||||
updateMeasuredWidth(setTabsContainerWidth, event);
|
||||
}, []);
|
||||
|
||||
const handleTabsActionsLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
const nextWidth = Math.round(event.nativeEvent.layout.width);
|
||||
setTabsActionsWidth((current) => (Math.abs(current - nextWidth) > 1 ? nextWidth : current));
|
||||
updateMeasuredWidth(setTabsActionsWidth, event);
|
||||
}, []);
|
||||
|
||||
const handleInlineAddButtonLayout = useCallback((event: LayoutChangeEvent) => {
|
||||
updateMeasuredWidth(setInlineAddButtonWidth, event);
|
||||
}, []);
|
||||
|
||||
const layoutMetrics = useMemo(
|
||||
() => ({
|
||||
rowHorizontalInset: 0,
|
||||
actionsReservedWidth: Math.max(0, tabsActionsWidth),
|
||||
actionsReservedWidth: Math.max(
|
||||
0,
|
||||
tabsActionsWidth + (inlineAddButtonWidth || DEFAULT_INLINE_ADD_BUTTON_RESERVED_WIDTH),
|
||||
),
|
||||
rowPaddingHorizontal: 0,
|
||||
tabGap: 0,
|
||||
maxTabWidth: 200,
|
||||
@@ -513,7 +568,7 @@ export function WorkspaceDesktopTabsRow({
|
||||
estimatedCharWidth: 7,
|
||||
closeButtonWidth: 22,
|
||||
}),
|
||||
[tabsActionsWidth],
|
||||
[inlineAddButtonWidth, tabsActionsWidth],
|
||||
);
|
||||
|
||||
const tabLabelLengths = useMemo(
|
||||
@@ -672,6 +727,11 @@ export function WorkspaceDesktopTabsRow({
|
||||
getItemData={getTabDragData}
|
||||
renderItem={renderTab}
|
||||
/>
|
||||
<WorkspaceInlineAddTabButton
|
||||
shortcutKeys={newTabKeys}
|
||||
onPress={handleCreateAgentTab}
|
||||
onLayout={handleInlineAddButtonLayout}
|
||||
/>
|
||||
</ScrollView>
|
||||
<View style={styles.tabsActions} onLayout={handleTabsActionsLayout}>
|
||||
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||
@@ -928,6 +988,11 @@ const styles = StyleSheet.create((theme) => ({
|
||||
alignItems: "center",
|
||||
paddingHorizontal: theme.spacing[2],
|
||||
},
|
||||
inlineAddButton: {
|
||||
paddingHorizontal: theme.spacing[1],
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
tab: {
|
||||
paddingHorizontal: theme.spacing[3],
|
||||
paddingVertical: theme.spacing[2],
|
||||
@@ -1028,6 +1093,13 @@ const styles = StyleSheet.create((theme) => ({
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
inlineAddActionButton: {
|
||||
width: 28,
|
||||
height: 28,
|
||||
borderRadius: theme.borderRadius.md,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
newTabActionButtonDisabled: {
|
||||
opacity: 0.5,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user