Keep selected projects when switching hosts (#2700)

* fix(app): preserve project selection across hosts

Rendered project identity and persisted equivalence were conflated after duplicate-project handling. Keep them separate so host changes resolve selectable equivalents deterministically without resetting legacy sidebar identity.

* fix(app): restore selected clone after host round trip

Keep manual origin and current destination placements separately so switching back restores the chosen checkout while archive gaps preserve the active host selection.

* fix(app): key status icons by project view

* fix(app): preserve origin during archive gaps
This commit is contained in:
Mohamed Boudra
2026-07-31 15:42:08 +02:00
committed by GitHub
parent 87ef631ac4
commit 99eca19a76
56 changed files with 1234 additions and 428 deletions
@@ -72,7 +72,7 @@ import {
useHostProjects,
type HostProjectListItem,
} from "@/projects/host-projects";
import { useProjectIconDataByProjectKey } from "@/projects/project-icons";
import { useProjectIconDataByProjectViewKey } from "@/projects/project-icons";
import { ICON_SIZE, type Theme } from "@/styles/theme";
import type { ComposerAttachment } from "@/attachments/types";
import { useDraftWorkspaceAttachmentScopeKey } from "@/attachments/workspace-attachments-store";
@@ -273,7 +273,7 @@ function ProjectPickerTrigger({
disabled,
badgePressableStyle,
label,
projectKey,
projectViewKey,
iconDataUri,
iconColor,
iconSize,
@@ -283,7 +283,7 @@ function ProjectPickerTrigger({
disabled: boolean;
badgePressableStyle: React.ComponentProps<typeof Pressable>["style"];
label: string;
projectKey: string | null;
projectViewKey: string | null;
iconDataUri: string | null;
iconColor: string;
iconSize: number;
@@ -303,11 +303,11 @@ function ProjectPickerTrigger({
accessibilityLabel="Workspace project"
>
<View style={styles.badgeIconBox}>
{projectKey ? (
{projectViewKey ? (
<ProjectIconView
iconDataUri={iconDataUri}
initial={placeholderInitial}
projectKey={projectKey}
projectViewKey={projectViewKey}
imageStyle={styles.projectIcon}
fallbackStyle={styles.projectIconFallback}
textStyle={styles.projectIconFallbackText}
@@ -423,7 +423,7 @@ function IsolationOptionItem({
function ProjectOptionItem({
testID,
projectKey,
projectViewKey,
iconDataUri,
label,
description,
@@ -433,7 +433,7 @@ function ProjectOptionItem({
onPress,
}: {
testID: string;
projectKey: string;
projectViewKey: string;
iconDataUri: string | null;
label: string;
description: string | undefined;
@@ -450,14 +450,14 @@ function ProjectOptionItem({
<ProjectIconView
iconDataUri={iconDataUri}
initial={placeholderInitial}
projectKey={projectKey}
projectViewKey={projectViewKey}
imageStyle={styles.projectIcon}
fallbackStyle={styles.projectIconFallback}
textStyle={styles.projectIconFallbackText}
/>
</View>
),
[iconDataUri, placeholderInitial, projectKey],
[iconDataUri, placeholderInitial, projectViewKey],
);
return (
@@ -533,7 +533,7 @@ function NewWorkspaceProjectPickerOption({
active,
onPress,
projectByOptionId,
projectIconDataByProjectKey,
projectIconDataByProjectViewKey,
selectedServerId,
isPending,
supportsWorkspaceMultiplicity,
@@ -543,7 +543,7 @@ function NewWorkspaceProjectPickerOption({
active: boolean;
onPress: () => void;
projectByOptionId: Map<string, HostProjectListItem>;
projectIconDataByProjectKey: Map<string, string | null>;
projectIconDataByProjectViewKey: Map<string, string | null>;
selectedServerId: string;
isPending: boolean;
supportsWorkspaceMultiplicity: boolean;
@@ -555,9 +555,9 @@ function NewWorkspaceProjectPickerOption({
return (
<ProjectOptionItem
testID={`new-workspace-project-picker-option-${project.projectKey}`}
projectKey={project.projectKey}
iconDataUri={projectIconDataByProjectKey.get(project.projectKey) ?? null}
testID={`new-workspace-project-picker-option-${project.viewKey}`}
projectViewKey={project.viewKey}
iconDataUri={projectIconDataByProjectViewKey.get(project.viewKey) ?? null}
label={project.projectName}
description={sourceDirectory}
selected={selected}
@@ -1305,7 +1305,7 @@ interface NewWorkspaceFormStackInput {
options: ComboboxOptionType[];
triggerLabel: string;
selectedProject: HostProjectListItem | null;
iconDataByProjectKey: Map<string, string | null>;
iconDataByProjectViewKey: Map<string, string | null>;
selectedOptionId: string;
onSelect: (id: string) => void;
onAddProject: () => void;
@@ -1369,10 +1369,10 @@ function useNewWorkspaceFormStack(input: NewWorkspaceFormStackInput): ReactEleme
disabled={isPending}
badgePressableStyle={badgePressableStyle}
label={project.triggerLabel}
projectKey={project.selectedProject?.projectKey ?? null}
projectViewKey={project.selectedProject?.viewKey ?? null}
iconDataUri={
project.selectedProject
? (project.iconDataByProjectKey.get(project.selectedProject.projectKey) ?? null)
? (project.iconDataByProjectViewKey.get(project.selectedProject.viewKey) ?? null)
: null
}
iconColor={theme.colors.foregroundMuted}
@@ -1592,12 +1592,12 @@ export function NewWorkspaceScreen({
if (!iconWorkingDir) {
return [];
}
return [{ projectKey: project.projectKey, serverId: selectedServerId, iconWorkingDir }];
return [{ projectViewKey: project.viewKey, serverId: selectedServerId, iconWorkingDir }];
}),
[projects, selectedServerId],
);
const projectIconDataByProjectKey = useProjectIconDataByProjectKey({
const projectIconDataByProjectViewKey = useProjectIconDataByProjectViewKey({
projects: projectIconTargets,
});
const draftKey = buildNewWorkspaceDraftKey(draftId);
@@ -2039,7 +2039,7 @@ export function NewWorkspaceScreen({
<NewWorkspaceProjectPickerOption
{...props}
projectByOptionId={projectByOptionId}
projectIconDataByProjectKey={projectIconDataByProjectKey}
projectIconDataByProjectViewKey={projectIconDataByProjectViewKey}
selectedServerId={selectedServerId}
isPending={isPending}
supportsWorkspaceMultiplicity={supportsWorkspaceMultiplicity}
@@ -2048,7 +2048,7 @@ export function NewWorkspaceScreen({
[
isPending,
projectByOptionId,
projectIconDataByProjectKey,
projectIconDataByProjectViewKey,
selectedServerId,
supportsWorkspaceMultiplicity,
],
@@ -2093,7 +2093,7 @@ export function NewWorkspaceScreen({
options: projectPickerOptions,
triggerLabel: projectTriggerLabel,
selectedProject,
iconDataByProjectKey: projectIconDataByProjectKey,
iconDataByProjectViewKey: projectIconDataByProjectViewKey,
selectedOptionId: selectedProjectOptionId,
onSelect: handleSelectProjectOption,
onAddProject: handleAddProject,