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:
@@ -18,7 +18,7 @@ interface SidebarModel extends SidebarWorkspacesListResult {
|
||||
statusGroups: StatusGroup[];
|
||||
pinnedGroups: PinnedSidebarGroups;
|
||||
collapsedProjectKeys: ReadonlySet<string>;
|
||||
toggleProjectCollapsed: (projectKey: string) => void;
|
||||
toggleProjectCollapsed: (projectViewKey: string) => void;
|
||||
shortcutModel: SidebarShortcutModel;
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export function SidebarModelProvider({
|
||||
projects: list.projects,
|
||||
pinnedKeys,
|
||||
workspaceEntriesByKey: projectionWorkspaceEntriesByKey,
|
||||
projectNamesByKey: list.projectNamesByKey,
|
||||
projectNamesByViewKey: list.projectNamesByViewKey,
|
||||
groupMode,
|
||||
pinnedCollapsed,
|
||||
collapsedProjectKeys,
|
||||
@@ -69,7 +69,7 @@ export function SidebarModelProvider({
|
||||
collapsedProjectKeys,
|
||||
collapsedStatusGroupKeys,
|
||||
groupMode,
|
||||
list.projectNamesByKey,
|
||||
list.projectNamesByViewKey,
|
||||
list.projects,
|
||||
pinnedCollapsed,
|
||||
pinnedKeys,
|
||||
|
||||
@@ -11,7 +11,7 @@ function makeWorkspace(id: string, statusBucket: SidebarWorkspaceEntry["statusBu
|
||||
workspaceKey: `srv:${id}`,
|
||||
serverId: "srv",
|
||||
workspaceId: id,
|
||||
projectKey: "project",
|
||||
projectViewKey: "project",
|
||||
projectName: "Project",
|
||||
projectKind: "git",
|
||||
workspaceKind: "worktree",
|
||||
@@ -36,7 +36,7 @@ function makeWorkspace(id: string, statusBucket: SidebarWorkspaceEntry["statusBu
|
||||
|
||||
function makeProject(workspaces: SidebarWorkspacePlacement[]): SidebarProjectEntry {
|
||||
return {
|
||||
projectKey: "project",
|
||||
viewKey: "project",
|
||||
projectName: "Project",
|
||||
projectKind: "git",
|
||||
iconWorkingDir: "/repo",
|
||||
@@ -68,7 +68,7 @@ function projectionInput(options?: {
|
||||
[pinned.entry.workspaceKey, pinned.entry],
|
||||
[unpinned.entry.workspaceKey, unpinned.entry],
|
||||
]),
|
||||
projectNamesByKey: new Map([["project", "Project"]]),
|
||||
projectNamesByViewKey: new Map([["project", "Project"]]),
|
||||
groupMode: options?.groupMode ?? ("project" as const),
|
||||
pinnedCollapsed: options?.pinnedCollapsed ?? false,
|
||||
collapsedProjectKeys: new Set<string>(),
|
||||
|
||||
@@ -25,7 +25,7 @@ export function buildSidebarProjection(input: {
|
||||
projects: SidebarProjectEntry[];
|
||||
pinnedKeys: PinnedSidebarKeys;
|
||||
workspaceEntriesByKey: ReadonlyMap<string, SidebarWorkspaceEntry>;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
groupMode: SidebarGroupMode;
|
||||
pinnedCollapsed: boolean;
|
||||
collapsedProjectKeys: ReadonlySet<string>;
|
||||
@@ -42,7 +42,7 @@ export function buildSidebarProjection(input: {
|
||||
Array.from(input.workspaceEntriesByKey.values()).filter(
|
||||
(workspace) => !pinnedWorkspaceKeys.has(workspace.workspaceKey),
|
||||
),
|
||||
input.projectNamesByKey,
|
||||
input.projectNamesByViewKey,
|
||||
)
|
||||
: [];
|
||||
|
||||
@@ -61,7 +61,7 @@ export function buildSidebarProjection(input: {
|
||||
sections.push(
|
||||
...pinnedGroups.unpinnedProjects.map((project) => ({
|
||||
workspaces: project.workspaces,
|
||||
collapsed: input.collapsedProjectKeys.has(project.projectKey),
|
||||
collapsed: input.collapsedProjectKeys.has(project.viewKey),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -64,8 +64,8 @@ const ThemedCircleX = withUnistyles(CircleX);
|
||||
interface StatusWorkspaceListProps {
|
||||
groups: StatusGroup[];
|
||||
pinnedWorkspaces: SidebarWorkspaceEntry[];
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectIconByProjectKey: ReadonlyMap<string, string | null>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
projectIconByProjectViewKey: ReadonlyMap<string, string | null>;
|
||||
shortcutIndexByWorkspaceKey: Map<string, number>;
|
||||
showShortcutBadges: boolean;
|
||||
onWorkspacePress?: () => void;
|
||||
@@ -79,8 +79,8 @@ interface StatusWorkspaceListProps {
|
||||
export function SidebarStatusWorkspaceList({
|
||||
groups,
|
||||
pinnedWorkspaces,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
shortcutIndexByWorkspaceKey,
|
||||
showShortcutBadges,
|
||||
onWorkspacePress,
|
||||
@@ -118,8 +118,8 @@ export function SidebarStatusWorkspaceList({
|
||||
workspace={workspace}
|
||||
{...buildStatusRowProjectPresentation({
|
||||
workspace,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
hostLabelByServerId,
|
||||
showHostLabels,
|
||||
})}
|
||||
@@ -145,8 +145,8 @@ export function SidebarStatusWorkspaceList({
|
||||
<StatusGroupList
|
||||
groups={groups}
|
||||
collapsedStatusGroupKeys={collapsedStatusGroupKeys}
|
||||
projectNamesByKey={projectNamesByKey}
|
||||
projectIconByProjectKey={projectIconByProjectKey}
|
||||
projectNamesByViewKey={projectNamesByViewKey}
|
||||
projectIconByProjectViewKey={projectIconByProjectViewKey}
|
||||
shortcutIndex={statusShortcutIndex}
|
||||
showShortcutBadges={showShortcutBadges}
|
||||
onWorkspacePress={onWorkspacePress}
|
||||
@@ -186,8 +186,8 @@ export function SidebarStatusWorkspaceList({
|
||||
function StatusGroupList({
|
||||
groups,
|
||||
collapsedStatusGroupKeys,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
shortcutIndex,
|
||||
showShortcutBadges,
|
||||
onWorkspacePress,
|
||||
@@ -198,8 +198,8 @@ function StatusGroupList({
|
||||
}: {
|
||||
groups: StatusGroup[];
|
||||
collapsedStatusGroupKeys: ReadonlySet<string>;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectIconByProjectKey: ReadonlyMap<string, string | null>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
projectIconByProjectViewKey: ReadonlyMap<string, string | null>;
|
||||
shortcutIndex: Map<string, number>;
|
||||
showShortcutBadges: boolean;
|
||||
onWorkspacePress?: () => void;
|
||||
@@ -215,8 +215,8 @@ function StatusGroupList({
|
||||
key={group.bucket}
|
||||
group={group}
|
||||
collapsed={collapsedStatusGroupKeys.has(group.bucket)}
|
||||
projectNamesByKey={projectNamesByKey}
|
||||
projectIconByProjectKey={projectIconByProjectKey}
|
||||
projectNamesByViewKey={projectNamesByViewKey}
|
||||
projectIconByProjectViewKey={projectIconByProjectViewKey}
|
||||
shortcutIndex={shortcutIndex}
|
||||
showShortcutBadges={showShortcutBadges}
|
||||
onWorkspacePress={onWorkspacePress}
|
||||
@@ -233,8 +233,8 @@ function StatusGroupList({
|
||||
function StatusGroupRows({
|
||||
group,
|
||||
collapsed,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
shortcutIndex,
|
||||
showShortcutBadges,
|
||||
onWorkspacePress,
|
||||
@@ -245,8 +245,8 @@ function StatusGroupRows({
|
||||
}: {
|
||||
group: StatusGroup;
|
||||
collapsed: boolean;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectIconByProjectKey: ReadonlyMap<string, string | null>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
projectIconByProjectViewKey: ReadonlyMap<string, string | null>;
|
||||
shortcutIndex: Map<string, number>;
|
||||
showShortcutBadges: boolean;
|
||||
onWorkspacePress?: () => void;
|
||||
@@ -276,8 +276,8 @@ function StatusGroupRows({
|
||||
workspace={workspace}
|
||||
{...buildStatusRowProjectPresentation({
|
||||
workspace,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
hostLabelByServerId,
|
||||
showHostLabels,
|
||||
})}
|
||||
@@ -324,25 +324,25 @@ interface StatusRowProjectPresentation {
|
||||
|
||||
function buildStatusRowProjectPresentation({
|
||||
workspace,
|
||||
projectNamesByKey,
|
||||
projectIconByProjectKey,
|
||||
projectNamesByViewKey,
|
||||
projectIconByProjectViewKey,
|
||||
hostLabelByServerId,
|
||||
showHostLabels,
|
||||
}: {
|
||||
workspace: SidebarWorkspaceEntry;
|
||||
projectNamesByKey: Map<string, string>;
|
||||
projectIconByProjectKey: ReadonlyMap<string, string | null>;
|
||||
projectNamesByViewKey: Map<string, string>;
|
||||
projectIconByProjectViewKey: ReadonlyMap<string, string | null>;
|
||||
hostLabelByServerId: ReadonlyMap<string, string>;
|
||||
showHostLabels: boolean;
|
||||
}): StatusRowProjectPresentation {
|
||||
const projectName = projectNamesByKey.get(workspace.projectKey) ?? "";
|
||||
const projectName = projectNamesByViewKey.get(workspace.projectViewKey) ?? "";
|
||||
const hostLabel = showHostLabels
|
||||
? (hostLabelByServerId.get(workspace.serverId) ?? workspace.serverId)
|
||||
: null;
|
||||
return {
|
||||
subtitle: buildStatusRowSubtitle({ projectName, hostLabel }),
|
||||
projectName,
|
||||
projectIconDataUri: projectIconByProjectKey.get(workspace.projectKey) ?? null,
|
||||
projectIconDataUri: projectIconByProjectViewKey.get(workspace.projectViewKey) ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ import { projectIconPlaceholderLabelFromDisplayName } from "@/utils/project-disp
|
||||
* the meaning, so the icon itself stays out of the accessibility tree.
|
||||
*/
|
||||
export function SidebarSubtitleProjectIcon({
|
||||
projectKey,
|
||||
projectViewKey,
|
||||
projectName,
|
||||
iconDataUri,
|
||||
testID,
|
||||
}: {
|
||||
projectKey: string;
|
||||
projectViewKey: string;
|
||||
projectName: string;
|
||||
iconDataUri: string | null;
|
||||
testID?: string;
|
||||
@@ -37,7 +37,7 @@ export function SidebarSubtitleProjectIcon({
|
||||
<ProjectIconView
|
||||
iconDataUri={iconDataUri}
|
||||
initial={placeholderInitial}
|
||||
projectKey={projectKey}
|
||||
projectViewKey={projectViewKey}
|
||||
imageStyle={styles.icon}
|
||||
fallbackStyle={styles.fallback}
|
||||
textStyle={styles.fallbackText}
|
||||
|
||||
@@ -157,7 +157,7 @@ export const SidebarWorkspaceRowContent = memo(function SidebarWorkspaceRowConte
|
||||
<View style={styles.workspaceSubtitleRow}>
|
||||
{subtitleProjectName ? (
|
||||
<SidebarSubtitleProjectIcon
|
||||
projectKey={workspace.projectKey}
|
||||
projectViewKey={workspace.projectViewKey}
|
||||
projectName={subtitleProjectName}
|
||||
iconDataUri={subtitleProjectIconDataUri}
|
||||
testID={`sidebar-row-project-icon-${workspace.workspaceKey}`}
|
||||
|
||||
Reference in New Issue
Block a user