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
@@ -45,7 +45,7 @@ function projectOptionId(projectId: string): string {
function computeProjectOptionData(projects: readonly HostProjectListItem[]) {
const projectByOptionId = new Map<string, HostProjectListItem>();
const options = projects.map((project) => {
const id = projectOptionId(project.projectKey);
const id = projectOptionId(project.viewKey);
projectByOptionId.set(id, project);
return { id, label: project.projectName };
});
@@ -104,15 +104,14 @@ export function useNewWorkspaceProjectPicker({
[allowAllProjects, lastActiveProject, routeProject, selectableProjects, selectedServerId],
);
const routeProjectKey = routeProject?.projectKey ?? null;
const routeProjectViewKey = routeProject?.viewKey ?? null;
const selectionContextKey = createProjectSelectionContextKey({
selectedServerId,
routeProjectKey,
routeProjectViewKey,
allowAllProjects,
});
const manualSelectionContextKey = createManualProjectSelectionContextKey({
selectedServerId,
routeProjectKey,
routeProjectViewKey,
});
const shouldPreserveMissingProject = useCallback(
(project: HostProjectListItem) =>
@@ -126,6 +125,7 @@ export function useNewWorkspaceProjectPicker({
() => ({
contextKey: selectionContextKey,
manualContextKey: manualSelectionContextKey,
selectedServerId,
initialProject,
initialProjectSource: resolveInitialProjectSelectionSource({
initialProject,
@@ -143,6 +143,7 @@ export function useNewWorkspaceProjectPicker({
manualSelectionContextKey,
routeProject,
selectableProjects,
selectedServerId,
selectionContextKey,
shouldPreserveMissingProject,
],
@@ -168,8 +169,8 @@ export function useNewWorkspaceProjectPicker({
if (!allowAllProjects && !project.hosts.some((host) => host.canCreateWorktree)) return;
setProjectSelection({
contextKey: manualSelectionContextKey,
projectKey: project.projectKey,
project,
originProject: project,
source: "manual",
});
},
@@ -183,7 +184,7 @@ export function useNewWorkspaceProjectPicker({
: null,
projectPickerOptions,
projectByOptionId,
selectedProjectOptionId: selectedProject ? projectOptionId(selectedProject.projectKey) : "",
selectedProjectOptionId: selectedProject ? projectOptionId(selectedProject.viewKey) : "",
projectTriggerLabel: selectedProject?.projectName ?? "Choose project",
handleSelectProjectOption,
};
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
import type { HostProjectListItem } from "@/projects/host-projects";
import { filterWorkspaceProjectsForHost, type HostProjectListItem } from "@/projects/host-projects";
import {
createManualProjectSelectionContextKey,
createProjectSelectionContextKey,
@@ -13,6 +13,7 @@ import {
function project(projectKey: string, serverId = "host"): HostProjectListItem {
return {
viewKey: `view:${serverId}:${projectKey}`,
projectKey,
projectName: projectKey,
projectKind: "git",
@@ -40,7 +41,8 @@ function context(
const lastActiveProject = input.lastActiveProject ?? null;
return {
contextKey,
manualContextKey: input.manualContextKey ?? contextKey,
manualContextKey: input.manualContextKey ?? "",
selectedServerId: input.selectedServerId ?? "host",
routeProject,
lastActiveProject,
initialProjectSource:
@@ -71,7 +73,6 @@ describe("reconcileProjectSelection", () => {
expect(reconciled).toEqual({
contextKey: "host:",
projectKey: remembered.projectKey,
project: remembered,
source: "initial",
});
@@ -80,15 +81,16 @@ describe("reconcileProjectSelection", () => {
it("resets stale selection when the route project context changes", () => {
const manual = project("manual");
const routeProject = project("route-project");
const routeProject = { ...project("route-project"), projectKey: null };
const current: ProjectSelection = {
contextKey: "host:previous-route",
projectKey: manual.projectKey,
contextKey: "previous-route",
project: manual,
originProject: manual,
source: "manual",
};
const nextContext = context({
contextKey: "host:route-project",
manualContextKey: routeProject.viewKey,
initialProject: routeProject,
projects: [manual, routeProject],
routeProject,
@@ -96,7 +98,6 @@ describe("reconcileProjectSelection", () => {
expect(reconcileProjectSelection(current, nextContext)).toEqual({
contextKey: "host:route-project",
projectKey: routeProject.projectKey,
project: routeProject,
source: "initial",
});
@@ -112,7 +113,6 @@ describe("reconcileProjectSelection", () => {
expect(reconcileProjectSelection(current, hydratedContext)).toEqual({
contextKey: "host:",
projectKey: initialProject.projectKey,
project: initialProject,
source: "initial",
});
@@ -121,6 +121,7 @@ describe("reconcileProjectSelection", () => {
it("adopts a routed project's hydrated cross-host key", () => {
const routedProject = {
...project("local-project-id"),
projectKey: null,
hosts: [
{
serverId: "host",
@@ -152,7 +153,6 @@ describe("reconcileProjectSelection", () => {
expect(reconcileProjectSelection(current, hydratedContext)).toEqual({
contextKey: "host:",
projectKey: hydratedProject.projectKey,
project: hydratedProject,
source: "initial",
});
@@ -177,7 +177,6 @@ describe("reconcileProjectSelection", () => {
expect(hydratedSelection).toEqual({
contextKey: "host:",
projectKey: hydratedProject.projectKey,
project: hydratedProject,
source: "initial",
});
@@ -207,7 +206,6 @@ describe("reconcileProjectSelection", () => {
expect(reconcileProjectSelection(current, afterRememberedHydration)).toEqual({
contextKey: "host:",
projectKey: remembered.projectKey,
project: remembered,
source: "initial",
});
@@ -217,9 +215,9 @@ describe("reconcileProjectSelection", () => {
const manual = project("manual");
const remembered = project("remembered");
const current: ProjectSelection = {
contextKey: "host:",
projectKey: manual.projectKey,
contextKey: "",
project: manual,
originProject: manual,
source: "manual",
};
const afterRememberedHydration = context({
@@ -231,6 +229,295 @@ describe("reconcileProjectSelection", () => {
expect(reconcileProjectSelection(current, afterRememberedHydration)).toEqual(current);
});
it("resolves a manual selection on another host while retaining its origin snapshot", () => {
const selected = project("shared", "host-a");
const equivalent = project("shared", "host-b");
const fallback = project("fallback", "host-b");
const current: ProjectSelection = {
contextKey: "",
project: selected,
originProject: selected,
source: "manual",
};
const nextHost = context({
selectedServerId: "host-b",
initialProject: fallback,
projects: [fallback, equivalent],
});
const reconciled = reconcileProjectSelection(current, nextHost);
expect(reconciled).toEqual({
contextKey: "",
project: equivalent,
originProject: selected,
source: "manual",
});
expect(resolveProjectSelection(reconciled, nextHost)).toBe(equivalent);
});
it("ignores a stale placement view collision and selects the persisted equivalent", () => {
const stalePlacementViewKey = JSON.stringify(["host-b", "opaque-project"]);
const selected = {
...project("shared", "host-a"),
viewKey: stalePlacementViewKey,
};
const unrelated = {
...project(stalePlacementViewKey, "host-b"),
viewKey: stalePlacementViewKey,
};
const equivalent = project("shared", "host-b");
const current: ProjectSelection = {
contextKey: "",
project: selected,
originProject: selected,
source: "manual",
};
const nextHost = context({
selectedServerId: "host-b",
initialProject: unrelated,
projects: [unrelated, equivalent],
});
const reconciled = reconcileProjectSelection(current, nextHost);
expect(reconciled).toEqual({
contextKey: "",
project: equivalent,
originProject: selected,
source: "manual",
});
expect(resolveProjectSelection(reconciled, nextHost)).toBe(equivalent);
});
it("chooses the same equivalent clone regardless of input order", () => {
const selected = project("shared", "host-a");
const cloneA = {
...project("shared", "host-b"),
viewKey: "view:host-b:clone-a",
projectName: "Shared",
hosts: [
{
serverId: "host-b",
projectId: "prj_a",
iconWorkingDir: "/work/a",
canCreateWorktree: true,
},
],
};
const cloneB = {
...cloneA,
viewKey: "view:host-b:clone-b",
hosts: [{ ...cloneA.hosts[0]!, projectId: "prj_b", iconWorkingDir: "/work/b" }],
};
const current: ProjectSelection = {
contextKey: "",
project: selected,
originProject: selected,
source: "manual",
};
const resolve = (projects: HostProjectListItem[]) => {
const nextHost = context({
selectedServerId: "host-b",
initialProject: cloneB,
projects,
});
return resolveProjectSelection(reconcileProjectSelection(current, nextHost), nextHost);
};
expect(resolve([cloneB, cloneA])).toBe(cloneA);
expect(resolve([cloneA, cloneB])).toBe(cloneA);
});
it("restores the exact manually selected clone after switching away and back", () => {
const selected = {
...project("shared", "host-a"),
viewKey: "view:host-a:selected",
hosts: [
{
serverId: "host-a",
projectId: "prj_b",
iconWorkingDir: "/work/selected",
canCreateWorktree: true,
},
],
};
const sibling = {
...selected,
viewKey: "view:host-a:sibling",
hosts: [{ ...selected.hosts[0]!, projectId: "prj_a", iconWorkingDir: "/work/sibling" }],
};
const destination = project("shared", "host-b");
const current: ProjectSelection = {
contextKey: "",
project: selected,
originProject: selected,
source: "manual",
};
const otherHost = context({
selectedServerId: "host-b",
initialProject: destination,
projects: [destination],
});
const retained = reconcileProjectSelection(current, otherHost);
expect(retained).toEqual({
contextKey: "",
project: destination,
originProject: selected,
source: "manual",
});
expect(resolveProjectSelection(retained, otherHost)).toBe(destination);
const originHost = context({
selectedServerId: "host-a",
initialProject: sibling,
projects: [sibling, selected],
});
const restored = reconcileProjectSelection(retained, originHost);
expect(restored.project).toBe(selected);
expect(restored.source === "manual" ? restored.originProject : null).toBe(selected);
expect(resolveProjectSelection(restored, originHost)).toBe(selected);
});
it("preserves the current destination clone during its pending archive gap", () => {
const selected = project("shared", "host-a");
const destination = {
...project("shared", "host-b"),
workspaceKeys: ["host-b:workspace"],
};
const fallback = project("fallback", "host-b");
const current: ProjectSelection = {
contextKey: "",
project: selected,
originProject: selected,
source: "manual",
};
const destinationHost = context({
selectedServerId: "host-b",
initialProject: fallback,
projects: [fallback, destination],
});
const resolvedDestination = reconcileProjectSelection(current, destinationHost);
const archiveGap = context({
selectedServerId: "host-b",
initialProject: fallback,
projects: [fallback],
shouldPreserveMissingProject: (candidate) => candidate === destination,
});
expect(reconcileProjectSelection(resolvedDestination, archiveGap)).toBe(resolvedDestination);
expect(resolveProjectSelection(resolvedDestination, archiveGap)).toBe(destination);
});
it("preserves the exact origin clone during its pending archive gap", () => {
const selected = {
...project("shared", "host-a"),
viewKey: "view:host-a:selected",
workspaceKeys: ["host-a:workspace"],
hosts: [
{
serverId: "host-a",
projectId: "prj_b",
iconWorkingDir: "/work/selected",
canCreateWorktree: true,
},
],
};
const sibling = {
...selected,
viewKey: "view:host-a:sibling",
workspaceKeys: [],
hosts: [{ ...selected.hosts[0]!, projectId: "prj_a", iconWorkingDir: "/work/sibling" }],
};
const destination = project("shared", "host-b");
const current: ProjectSelection = {
contextKey: "",
project: selected,
originProject: selected,
source: "manual",
};
const destinationHost = context({
selectedServerId: "host-b",
initialProject: destination,
projects: [destination],
});
const resolvedDestination = reconcileProjectSelection(current, destinationHost);
const archiveGap = context({
selectedServerId: "host-a",
initialProject: sibling,
projects: [sibling],
shouldPreserveMissingProject: (candidate) => candidate === selected,
});
const preservedOrigin = reconcileProjectSelection(resolvedDestination, archiveGap);
expect(preservedOrigin.project).toBe(selected);
expect(resolveProjectSelection(preservedOrigin, archiveGap)).toBe(selected);
});
it("falls back when the only equivalent project is not selectable", () => {
const selected = project("shared", "host-a");
const unselectableEquivalent = {
...project("shared", "host-b"),
hosts: [
{
serverId: "host-b",
projectId: "shared",
iconWorkingDir: "/work/shared",
canCreateWorktree: false,
},
],
};
const fallback = project("fallback", "host-b");
const current: ProjectSelection = {
contextKey: "",
project: selected,
originProject: selected,
source: "manual",
};
const selectableProjects = filterWorkspaceProjectsForHost({
projects: [unselectableEquivalent, fallback],
serverId: "host-b",
allowAllProjects: false,
});
const selectableContext = context({
selectedServerId: "host-b",
initialProject: fallback,
projects: selectableProjects,
});
expect(reconcileProjectSelection(current, selectableContext).project).toBe(fallback);
});
it("does not equate projects without a persisted project key", () => {
const selected = { ...project("selected", "host-a"), projectKey: null };
const sameName = {
...project("same-name", "host-b"),
projectKey: null,
projectName: selected.projectName,
};
const fallback = project("fallback", "host-b");
const current: ProjectSelection = {
contextKey: "",
project: selected,
originProject: selected,
source: "manual",
};
expect(
reconcileProjectSelection(
current,
context({
selectedServerId: "host-b",
initialProject: fallback,
projects: [sameName, fallback],
}),
).project,
).toBe(fallback);
});
it("preserves an opaque project key ending in whitespace", () => {
const selected = project("host:project ");
const current = createProjectSelection(
@@ -249,7 +536,7 @@ describe("reconcileProjectSelection", () => {
context({
contextKey: createProjectSelectionContextKey({
selectedServerId: "host",
routeProjectKey: null,
routeProjectViewKey: null,
allowAllProjects: false,
}),
initialProject: fallback,
@@ -259,7 +546,7 @@ describe("reconcileProjectSelection", () => {
const afterCapabilityHydration = context({
contextKey: createProjectSelectionContextKey({
selectedServerId: "host",
routeProjectKey: null,
routeProjectViewKey: null,
allowAllProjects: true,
}),
initialProject: remembered,
@@ -268,7 +555,6 @@ describe("reconcileProjectSelection", () => {
expect(reconcileProjectSelection(current, afterCapabilityHydration)).toEqual({
contextKey: "host:all-projects:",
projectKey: remembered.projectKey,
project: remembered,
source: "initial",
});
@@ -280,22 +566,20 @@ describe("reconcileProjectSelection", () => {
const remembered = project("remembered-directory");
const current: ProjectSelection = {
contextKey: createManualProjectSelectionContextKey({
selectedServerId: "host",
routeProjectKey: null,
routeProjectViewKey: null,
}),
projectKey: manual.projectKey,
project: manual,
originProject: manual,
source: "manual",
};
const afterCapabilityHydration = context({
contextKey: createProjectSelectionContextKey({
selectedServerId: "host",
routeProjectKey: null,
routeProjectViewKey: null,
allowAllProjects: true,
}),
manualContextKey: createManualProjectSelectionContextKey({
selectedServerId: "host",
routeProjectKey: null,
routeProjectViewKey: null,
}),
initialProject: remembered,
projects: [fallback, manual, remembered],
@@ -325,6 +609,34 @@ describe("reconcileProjectSelection", () => {
expect(resolveProjectSelection(reconciled, withoutRemembered)).toEqual(remembered);
});
it("keeps a pending archived clone instead of jumping to a same-host equivalent", () => {
const remembered = project("shared");
const sibling = {
...project("shared"),
viewKey: "view:host:sibling",
hosts: [
{
serverId: "host",
projectId: "sibling",
iconWorkingDir: "/work/sibling",
canCreateWorktree: true,
},
],
};
const fallback = project("fallback");
const current = createProjectSelection(
context({ initialProject: remembered, projects: [remembered, sibling] }),
);
const archiveGap = context({
initialProject: fallback,
projects: [sibling, fallback],
shouldPreserveMissingProject: (candidate) => candidate.viewKey === remembered.viewKey,
});
expect(reconcileProjectSelection(current, archiveGap)).toEqual(current);
expect(resolveProjectSelection(current, archiveGap)).toEqual(remembered);
});
it("falls back when the selected project disappears without a pending archive", () => {
const remembered = project("remembered");
const fallback = project("fallback");
@@ -338,7 +650,6 @@ describe("reconcileProjectSelection", () => {
expect(reconcileProjectSelection(current, withoutRemembered)).toEqual({
contextKey: "host:",
projectKey: fallback.projectKey,
project: fallback,
source: "initial",
});
@@ -349,13 +660,14 @@ describe("reconcileProjectSelection", () => {
const routeProject = project("route-project");
const remembered = project("remembered");
const current: ProjectSelection = {
contextKey: "host:route-project",
projectKey: manual.projectKey,
contextKey: routeProject.viewKey,
project: manual,
originProject: manual,
source: "manual",
};
const selectionContext = context({
contextKey: "host:route-project",
manualContextKey: routeProject.viewKey,
initialProject: routeProject,
projects: [manual],
routeProject,
@@ -1,18 +1,32 @@
import type { HostProjectListItem } from "@/projects/host-projects";
import {
getHostProjectId,
resolveEquivalentHostProjectCandidate,
resolveExactHostProjectCandidate,
type HostProjectListItem,
} from "@/projects/host-projects";
export type ProjectSelectionSource = "initial" | "manual";
export type InitialProjectSelectionSource = "route" | "lastActive" | "fallback" | null;
export interface ProjectSelection {
interface InitialProjectSelection {
contextKey: string;
projectKey: string | null;
project: HostProjectListItem | null;
source: ProjectSelectionSource;
source: "initial";
}
interface ManualProjectSelection {
contextKey: string;
project: HostProjectListItem;
originProject: HostProjectListItem;
source: "manual";
}
export type ProjectSelection = InitialProjectSelection | ManualProjectSelection;
export interface ProjectSelectionContext {
contextKey: string;
manualContextKey: string;
selectedServerId: string;
initialProject: HostProjectListItem | null;
initialProjectSource: InitialProjectSelectionSource;
projects: HostProjectListItem[];
@@ -23,18 +37,17 @@ export interface ProjectSelectionContext {
export function createProjectSelectionContextKey(input: {
selectedServerId: string;
routeProjectKey: string | null;
routeProjectViewKey: string | null;
allowAllProjects: boolean;
}): string {
const projectScope = input.allowAllProjects ? "all-projects" : "worktree-projects";
return `${input.selectedServerId}:${projectScope}:${input.routeProjectKey ?? ""}`;
return `${input.selectedServerId}:${projectScope}:${input.routeProjectViewKey ?? ""}`;
}
export function createManualProjectSelectionContextKey(input: {
selectedServerId: string;
routeProjectKey: string | null;
routeProjectViewKey: string | null;
}): string {
return `${input.selectedServerId}:${input.routeProjectKey ?? ""}`;
return input.routeProjectViewKey ?? "";
}
export function createProjectSelection({
@@ -43,7 +56,6 @@ export function createProjectSelection({
}: ProjectSelectionContext): ProjectSelection {
return {
contextKey,
projectKey: initialProject?.projectKey ?? null,
project: initialProject,
source: "initial",
};
@@ -58,7 +70,7 @@ export function resolveInitialProjectSelectionSource(input: {
return null;
}
if (
input.routeProject?.projectKey === input.initialProject.projectKey ||
input.routeProject?.viewKey === input.initialProject.viewKey ||
input.routeProject?.hosts.some((routeHost) =>
input.initialProject?.hosts.some(
(host) =>
@@ -70,24 +82,24 @@ export function resolveInitialProjectSelectionSource(input: {
) {
return "route";
}
if (input.lastActiveProject?.projectKey === input.initialProject.projectKey) {
if (
input.lastActiveProject?.viewKey === input.initialProject.viewKey ||
(input.lastActiveProject?.projectKey !== null &&
input.lastActiveProject?.projectKey !== undefined &&
input.lastActiveProject.projectKey === input.initialProject.projectKey)
) {
return "lastActive";
}
return "fallback";
}
function resolveProjectSelectionKey(selection: ProjectSelection): string | null {
const projectKey = selection.projectKey ?? "";
return projectKey.trim() ? projectKey : null;
}
function resolveSelectedProjectFromInitialInputs(
projectKey: string,
project: HostProjectListItem,
context: ProjectSelectionContext,
): HostProjectListItem | null {
return (
(context.routeProject?.projectKey === projectKey ? context.routeProject : null) ??
(context.lastActiveProject?.projectKey === projectKey ? context.lastActiveProject : null)
(context.routeProject?.viewKey === project.viewKey ? context.routeProject : null) ??
(context.lastActiveProject?.viewKey === project.viewKey ? context.lastActiveProject : null)
);
}
@@ -95,16 +107,40 @@ function refreshSelectionProject(
selection: ProjectSelection,
project: HostProjectListItem,
): ProjectSelection {
if (selection.projectKey === project.projectKey && selection.project === project) {
if (selection.project === project) {
return selection;
}
return {
...selection,
projectKey: project.projectKey,
project,
};
}
function resolveManualOriginProject(
selection: ProjectSelection,
context: ProjectSelectionContext,
): HostProjectListItem | null {
if (selection.source !== "manual") {
return null;
}
const exactOriginProject = resolveExactHostProjectCandidate({
candidate: selection.originProject,
projects: context.projects,
serverId: context.selectedServerId,
});
if (exactOriginProject) {
return exactOriginProject;
}
const originBelongsToSelectedHost =
getHostProjectId(selection.originProject, context.selectedServerId) !== null;
return originBelongsToSelectedHost &&
context.shouldPreserveMissingProject(selection.originProject)
? selection.originProject
: null;
}
function shouldResetHydratedInitialSelection(
selection: ProjectSelection,
context: ProjectSelectionContext,
@@ -117,32 +153,48 @@ function shouldResetHydratedInitialSelection(
return false;
}
return selection.projectKey !== context.initialProject.projectKey;
return selection.project?.viewKey !== context.initialProject.viewKey;
}
export function resolveProjectSelection(
selection: ProjectSelection,
context: ProjectSelectionContext,
): HostProjectListItem | null {
const projectKey = resolveProjectSelectionKey(selection);
if (!projectKey) {
if (!selection.project) {
return null;
}
const selectableProject = context.projects.find((project) => project.projectKey === projectKey);
if (selectableProject) {
return selectableProject;
if (selection.source === "manual") {
const originProject = resolveManualOriginProject(selection, context);
if (originProject) {
return originProject;
}
}
if (
selection.project?.projectKey === projectKey &&
context.shouldPreserveMissingProject(selection.project)
) {
const exactProject = resolveExactHostProjectCandidate({
candidate: selection.project,
projects: context.projects,
serverId: context.selectedServerId,
});
if (exactProject) {
return exactProject;
}
if (context.shouldPreserveMissingProject(selection.project)) {
return selection.project;
}
const equivalentProject = resolveEquivalentHostProjectCandidate({
candidate: selection.project,
projects: context.projects,
serverId: context.selectedServerId,
});
if (equivalentProject) {
return equivalentProject;
}
if (selection.source !== "manual") {
return resolveSelectedProjectFromInitialInputs(projectKey, context);
return resolveSelectedProjectFromInitialInputs(selection.project, context);
}
return null;
@@ -163,6 +215,34 @@ export function reconcileProjectSelection(
return initialSelection;
}
if (!current.project) {
return initialSelection;
}
if (current.source === "manual") {
const originProject = resolveManualOriginProject(current, context);
if (originProject) {
return {
...current,
project: originProject,
originProject,
};
}
}
const exactProject = resolveExactHostProjectCandidate({
candidate: current.project,
projects: context.projects,
serverId: context.selectedServerId,
});
if (exactProject) {
return refreshSelectionProject(current, exactProject);
}
if (context.shouldPreserveMissingProject(current.project)) {
return current;
}
const resolvedProject = resolveProjectSelection(current, context);
if (resolvedProject) {
return refreshSelectionProject(current, resolvedProject);