From ad191f0f666e8bb4232d802927165d0fc510e0b0 Mon Sep 17 00:00:00 2001 From: Mohtasham Murshid <154406804+MohtashamMurshid@users.noreply.github.com> Date: Fri, 21 Aug 2026 17:42:56 +0000 Subject: [PATCH 1/2] fix(web): keep grouped project renames --- .../settings/ProjectSettingsPanel.logic.test.ts | 15 +++++++++++++++ .../settings/ProjectSettingsPanel.logic.ts | 6 ++++++ .../components/settings/ProjectSettingsPanel.tsx | 13 ++++++++++--- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/components/settings/ProjectSettingsPanel.logic.test.ts create mode 100644 apps/web/src/components/settings/ProjectSettingsPanel.logic.ts diff --git a/apps/web/src/components/settings/ProjectSettingsPanel.logic.test.ts b/apps/web/src/components/settings/ProjectSettingsPanel.logic.test.ts new file mode 100644 index 000000000000..38c6a743ea7f --- /dev/null +++ b/apps/web/src/components/settings/ProjectSettingsPanel.logic.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { projectGroupTitleNeedsUpdate } from "./ProjectSettingsPanel.logic"; + +describe("projectGroupTitleNeedsUpdate", () => { + it("updates divergent member titles even when the next title is the derived group label", () => { + expect(projectGroupTitleNeedsUpdate(["local-title", "remote-title"], "Repository name")).toBe( + true, + ); + }); + + it("skips an update when every member already has the next title", () => { + expect(projectGroupTitleNeedsUpdate(["Shared name", "Shared name"], "Shared name")).toBe(false); + }); +}); diff --git a/apps/web/src/components/settings/ProjectSettingsPanel.logic.ts b/apps/web/src/components/settings/ProjectSettingsPanel.logic.ts new file mode 100644 index 000000000000..0d0fc6a059a0 --- /dev/null +++ b/apps/web/src/components/settings/ProjectSettingsPanel.logic.ts @@ -0,0 +1,6 @@ +export function projectGroupTitleNeedsUpdate( + memberTitles: ReadonlyArray, + nextTitle: string, +): boolean { + return memberTitles.some((title) => title !== nextTitle); +} diff --git a/apps/web/src/components/settings/ProjectSettingsPanel.tsx b/apps/web/src/components/settings/ProjectSettingsPanel.tsx index 0fb0415a34bb..16ef2e39b99e 100644 --- a/apps/web/src/components/settings/ProjectSettingsPanel.tsx +++ b/apps/web/src/components/settings/ProjectSettingsPanel.tsx @@ -109,6 +109,7 @@ import { SettingsSection, } from "./settingsLayout"; import { ProjectFaviconPickerDialog } from "./ProjectFaviconPickerDialog"; +import { projectGroupTitleNeedsUpdate } from "./ProjectSettingsPanel.logic"; export const PROJECT_GROUPING_MODE_LABELS: Record = { repository: "Group by repository", @@ -384,11 +385,17 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) { toastManager.add({ type: "warning", title: "Project title cannot be empty" }); return; } - if (title === group.displayName) return; - if (group.memberProjects.every((member) => member.title === title)) return; + if ( + !projectGroupTitleNeedsUpdate( + group.memberProjects.map((member) => member.title), + title, + ) + ) { + return; + } await updateAllMembers({ title }, "Failed to rename project"); }, - [group.displayName, group.memberProjects, updateAllMembers], + [group.memberProjects, updateAllMembers], ); // ----- default model ----- From 6122cc812e2e6224158fb7923dc6c306a5013b85 Mon Sep 17 00:00:00 2001 From: Mohtasham Murshid <154406804+MohtashamMurshid@users.noreply.github.com> Date: Fri, 21 Aug 2026 17:52:25 +0000 Subject: [PATCH 2/2] fix(web): avoid saving untouched project names --- .../settings/ProjectSettingsPanel.logic.test.ts | 14 +++++++++++--- .../settings/ProjectSettingsPanel.logic.ts | 3 ++- .../components/settings/ProjectSettingsPanel.tsx | 11 +++++++++-- 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/settings/ProjectSettingsPanel.logic.test.ts b/apps/web/src/components/settings/ProjectSettingsPanel.logic.test.ts index 38c6a743ea7f..8a72b3510ceb 100644 --- a/apps/web/src/components/settings/ProjectSettingsPanel.logic.test.ts +++ b/apps/web/src/components/settings/ProjectSettingsPanel.logic.test.ts @@ -4,12 +4,20 @@ import { projectGroupTitleNeedsUpdate } from "./ProjectSettingsPanel.logic"; describe("projectGroupTitleNeedsUpdate", () => { it("updates divergent member titles even when the next title is the derived group label", () => { - expect(projectGroupTitleNeedsUpdate(["local-title", "remote-title"], "Repository name")).toBe( - true, + expect( + projectGroupTitleNeedsUpdate(["local-title", "remote-title"], "Repository name", true), + ).toBe(true); + }); + + it("skips an untouched blur when the derived label differs from member titles", () => { + expect(projectGroupTitleNeedsUpdate(["repo-slug", "repo-slug"], "Repository Name", false)).toBe( + false, ); }); it("skips an update when every member already has the next title", () => { - expect(projectGroupTitleNeedsUpdate(["Shared name", "Shared name"], "Shared name")).toBe(false); + expect(projectGroupTitleNeedsUpdate(["Shared name", "Shared name"], "Shared name", true)).toBe( + false, + ); }); }); diff --git a/apps/web/src/components/settings/ProjectSettingsPanel.logic.ts b/apps/web/src/components/settings/ProjectSettingsPanel.logic.ts index 0d0fc6a059a0..17ff824099fb 100644 --- a/apps/web/src/components/settings/ProjectSettingsPanel.logic.ts +++ b/apps/web/src/components/settings/ProjectSettingsPanel.logic.ts @@ -1,6 +1,7 @@ export function projectGroupTitleNeedsUpdate( memberTitles: ReadonlyArray, nextTitle: string, + wasEdited: boolean, ): boolean { - return memberTitles.some((title) => title !== nextTitle); + return wasEdited && memberTitles.some((title) => title !== nextTitle); } diff --git a/apps/web/src/components/settings/ProjectSettingsPanel.tsx b/apps/web/src/components/settings/ProjectSettingsPanel.tsx index 16ef2e39b99e..9a2e7b116422 100644 --- a/apps/web/src/components/settings/ProjectSettingsPanel.tsx +++ b/apps/web/src/components/settings/ProjectSettingsPanel.tsx @@ -300,6 +300,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) { const removeKeybinding = useAtomCommand(serverEnvironment.removeKeybinding, { reportFailure: false, }); + const projectNameEditedRef = useRef(false); const { copyToClipboard: copyPathToClipboard } = useCopyToClipboard<{ path: string }>({ onCopy: ({ path }) => { toastManager.add({ type: "success", title: "Path copied", description: path }); @@ -379,7 +380,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) { ); const renameGroup = useCallback( - async (nextTitle: string) => { + async (nextTitle: string, wasEdited: boolean) => { const title = nextTitle.trim(); if (!title) { toastManager.add({ type: "warning", title: "Project title cannot be empty" }); @@ -389,6 +390,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) { !projectGroupTitleNeedsUpdate( group.memberProjects.map((member) => member.title), title, + wasEdited, ) ) { return; @@ -759,8 +761,13 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) { className="w-full sm:w-64" aria-label="Project name" defaultValue={group.displayName} + onChange={() => { + projectNameEditedRef.current = true; + }} onBlur={(event) => { - void renameGroup(event.currentTarget.value); + const wasEdited = projectNameEditedRef.current; + projectNameEditedRef.current = false; + void renameGroup(event.currentTarget.value, wasEdited); }} onKeyDown={(event) => { if (event.key === "Enter") event.currentTarget.blur();