authorgravatar for 154406804+MohtashamMurshid@users.noreply.github.comMohtasham Murshid <154406804+MohtashamMurshid@users.noreply.github.com> 2026-08-25 22:55:42+08:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-25 20:25:42+05:30
loge67074f80933a27bd3cdc4e24f486358407690fb
tree6f7ba1fbc6d253fd5a984e4578555d3c29e905f2
parent5d7665396083d285132d67038813862a93337ca5
signature Signed by PGP key B5690EEEBB952194

fix(web): keep grouped project renames (#7831)


3 files changed, 49 insertions(+), 5 deletions(-)

apps/web/src/components/settings/ProjectSettingsPanel.logic.test.ts created+23
......@@ -0,0 +1,23 @@
1import { describe, expect, it } from "vite-plus/test";
2
3import { projectGroupTitleNeedsUpdate } from "./ProjectSettingsPanel.logic";
4
5describe("projectGroupTitleNeedsUpdate", () => {
6 it("updates divergent member titles even when the next title is the derived group label", () => {
7 expect(
8 projectGroupTitleNeedsUpdate(["local-title", "remote-title"], "Repository name", true),
9 ).toBe(true);
10 });
11
12 it("skips an untouched blur when the derived label differs from member titles", () => {
13 expect(projectGroupTitleNeedsUpdate(["repo-slug", "repo-slug"], "Repository Name", false)).toBe(
14 false,
15 );
16 });
17
18 it("skips an update when every member already has the next title", () => {
19 expect(projectGroupTitleNeedsUpdate(["Shared name", "Shared name"], "Shared name", true)).toBe(
20 false,
21 );
22 });
23});
apps/web/src/components/settings/ProjectSettingsPanel.logic.ts created+7
......@@ -0,0 +1,7 @@
1export function projectGroupTitleNeedsUpdate(
2 memberTitles: ReadonlyArray<string>,
3 nextTitle: string,
4 wasEdited: boolean,
5): boolean {
6 return wasEdited && memberTitles.some((title) => title !== nextTitle);
7}
apps/web/src/components/settings/ProjectSettingsPanel.tsx+19-5
......@@ -113,6 +113,7 @@ import {
113113 canPickExternalProjectFavicon,
114114 ProjectFaviconPickerDialog,
115115} from "./ProjectFaviconPickerDialog";
116import { projectGroupTitleNeedsUpdate } from "./ProjectSettingsPanel.logic";
116117
117118export const PROJECT_GROUPING_MODE_LABELS: Record<SidebarProjectGroupingMode, string> = {
118119 repository: "Group by repository",
......@@ -304,6 +305,7 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
304305 const removeKeybinding = useAtomCommand(serverEnvironment.removeKeybinding, {
305306 reportFailure: false,
306307 });
308 const projectNameEditedRef = useRef(false);
307309 const { copyToClipboard: copyPathToClipboard } = useCopyToClipboard<{ path: string }>({
308310 onCopy: ({ path }) => {
309311 toastManager.add({ type: "success", title: "Path copied", description: path });
......@@ -392,17 +394,24 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
392394 );
393395
394396 const renameGroup = useCallback(
395 async (nextTitle: string) => {
397 async (nextTitle: string, wasEdited: boolean) => {
396398 const title = nextTitle.trim();
397399 if (!title) {
398400 toastManager.add({ type: "warning", title: "Project title cannot be empty" });
399401 return;
400402 }
401 if (title === group.displayName) return;
402 if (group.memberProjects.every((member) => member.title === title)) return;
403 if (
404 !projectGroupTitleNeedsUpdate(
405 group.memberProjects.map((member) => member.title),
406 title,
407 wasEdited,
408 )
409 ) {
410 return;
411 }
403412 await updateAllMembers({ title }, "Failed to rename project");
404413 },
405 [group.displayName, group.memberProjects, updateAllMembers],
414 [group.memberProjects, updateAllMembers],
406415 );
407416
408417 // ----- default model -----
......@@ -767,8 +776,13 @@ function ProjectDetail({ group }: { group: SidebarProjectSnapshot }) {
767776 className="w-full sm:w-64"
768777 aria-label="Project name"
769778 defaultValue={group.displayName}
779 onChange={() => {
780 projectNameEditedRef.current = true;
781 }}
770782 onBlur={(event) => {
771 void renameGroup(event.currentTarget.value);
783 const wasEdited = projectNameEditedRef.current;
784 projectNameEditedRef.current = false;
785 void renameGroup(event.currentTarget.value, wasEdited);
772786 }}
773787 onKeyDown={(event) => {
774788 if (event.key === "Enter") event.currentTarget.blur();