From fff69c01d5b47db9c83af53a171438641f2ff392 Mon Sep 17 00:00:00 2001 From: AviPeltz Date: Mon, 27 Apr 2026 02:46:45 -0700 Subject: [PATCH] feat(desktop): rename branch from workspace hover card Click the branch name in a workspace hover card to open a modal that renames the local git branch via the host service. The GitHub external- link icon is split out as a separate affordance so the branch name itself is always the rename trigger. In the v2 dashboard sidebar, the rename also writes the new branch through the v2Workspaces optimistic action so the hover card reflects the new name immediately without waiting for a manual refresh. --- .../DashboardSidebarWorkspaceItem.tsx | 34 +++++ ...hboardSidebarWorkspaceHoverCardContent.tsx | 57 ++++--- .../CollapsedWorkspaceItem.tsx | 25 +++- .../WorkspaceContextMenu.tsx | 21 ++- .../RenameBranchDialog/RenameBranchDialog.tsx | 140 ++++++++++++++++++ .../components/RenameBranchDialog/index.ts | 1 + .../WorkspaceHoverCard/WorkspaceHoverCard.tsx | 60 +++++--- .../WorkspaceListItem/components/index.ts | 1 + 8 files changed, 297 insertions(+), 42 deletions(-) create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/RenameBranchDialog/RenameBranchDialog.tsx create mode 100644 apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/RenameBranchDialog/index.ts diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx index 0878c23cd53..068c1267b63 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsx @@ -1,6 +1,9 @@ import { useNavigate } from "@tanstack/react-router"; +import { useState } from "react"; import { useDiffStats } from "renderer/hooks/host-service/useDiffStats"; +import { useOptimisticCollectionActions } from "renderer/routes/_authenticated/hooks/useOptimisticCollectionActions"; import { useDeletingWorkspaces } from "renderer/routes/_authenticated/providers/DeletingWorkspacesProvider"; +import { RenameBranchDialog } from "renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components"; import { useV2WorkspaceNotificationStatus } from "renderer/stores/v2-notifications"; import type { DashboardSidebarWorkspace } from "../../types"; import { DashboardSidebarDeleteDialog } from "../DashboardSidebarDeleteDialog"; @@ -67,6 +70,13 @@ export function DashboardSidebarWorkspaceItem({ }); const navigate = useNavigate(); + const { v2Workspaces: v2WorkspaceActions } = useOptimisticCollectionActions(); + const [renameBranchTarget, setRenameBranchTarget] = useState( + null, + ); + const handleAfterBranchRename = (newBranchName: string) => { + v2WorkspaceActions.updateWorkspace(id, { branch: newBranchName }); + }; const isPending = !!creationStatus; // Keep the delete dialog outside the hidden wrapper below — the destroy // flow reopens it into an error pane on conflict/teardown-failed. @@ -122,6 +132,7 @@ export function DashboardSidebarWorkspaceItem({ } isLocalWorkspace={hostType === "local-device"} @@ -153,6 +164,17 @@ export function DashboardSidebarWorkspaceItem({ onDeleted={handleDeleted} /> )} + {renameBranchTarget && ( + { + if (!open) setRenameBranchTarget(null); + }} + onAfterRename={handleAfterBranchRename} + /> + )} ); } @@ -196,6 +218,7 @@ export function DashboardSidebarWorkspaceItem({ } onCreateSection={handleCreateSection} @@ -227,6 +250,17 @@ export function DashboardSidebarWorkspaceItem({ onDeleted={handleDeleted} /> )} + {renameBranchTarget && ( + { + if (!open) setRenameBranchTarget(null); + }} + onAfterRename={handleAfterBranchRename} + /> + )} ); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/DashboardSidebarWorkspaceHoverCardContent.tsx b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/DashboardSidebarWorkspaceHoverCardContent.tsx index 667b816e84e..e3dffa07be2 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/DashboardSidebarWorkspaceHoverCardContent.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/DashboardSidebarWorkspaceHoverCardContent.tsx @@ -2,7 +2,12 @@ import { Button } from "@superset/ui/button"; import { Kbd, KbdGroup } from "@superset/ui/kbd"; import { formatDistanceToNow } from "date-fns"; import { FaGithub } from "react-icons/fa"; -import { LuExternalLink, LuGlobe, LuTriangleAlert } from "react-icons/lu"; +import { + LuExternalLink, + LuGlobe, + LuPencil, + LuTriangleAlert, +} from "react-icons/lu"; import type { DiffStats } from "renderer/hooks/host-service/useDiffStats"; import { useHotkeyDisplay } from "renderer/hotkeys"; import type { DashboardSidebarWorkspace } from "../../../../types"; @@ -14,11 +19,13 @@ import { ReviewStatus } from "./components/ReviewStatus"; interface DashboardSidebarWorkspaceHoverCardContentProps { workspace: DashboardSidebarWorkspace; diffStats: DiffStats | null; + onEditBranchClick?: (branchName: string) => void; } export function DashboardSidebarWorkspaceHoverCardContent({ workspace, diffStats, + onEditBranchClick, }: DashboardSidebarWorkspaceHoverCardContentProps) { const { name, @@ -59,23 +66,37 @@ export function DashboardSidebarWorkspaceHoverCardContent({ Branch - {repoUrl && branchExistsOnRemote ? ( - - {branch} - - - ) : ( - - {branch} - - )} +
+ {onEditBranchClick ? ( + + ) : ( + + {branch} + + )} + {repoUrl && branchExistsOnRemote && ( + e.stopPropagation()} + > + + + )} +
{formatDistanceToNow(createdAt, { addSuffix: true })} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/CollapsedWorkspaceItem.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/CollapsedWorkspaceItem.tsx index fb910d4fd49..14a052c8356 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/CollapsedWorkspaceItem.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/CollapsedWorkspaceItem.tsx @@ -17,7 +17,11 @@ import { LuCopy, LuGitBranch, LuX } from "react-icons/lu"; import { createContextMenuDeleteDialogCoordinator } from "renderer/react-query/workspaces/useWorkspaceDeleteHandler"; import type { ActivePaneStatus } from "shared/tabs-types"; import { STROKE_WIDTH } from "../constants"; -import { DeleteWorkspaceDialog, WorkspaceHoverCardContent } from "./components"; +import { + DeleteWorkspaceDialog, + RenameBranchDialog, + WorkspaceHoverCardContent, +} from "./components"; import { HOVER_CARD_CLOSE_DELAY, HOVER_CARD_OPEN_DELAY } from "./constants"; import { WorkspaceIcon } from "./WorkspaceIcon"; @@ -62,6 +66,9 @@ export function CollapsedWorkspaceItem({ [onDeleteClick], ); const [isContextMenuOpen, setIsContextMenuOpen] = useState(false); + const [renameBranchTarget, setRenameBranchTarget] = useState( + null, + ); const collapsedButton = ( + + + + + + ); +} diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/RenameBranchDialog/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/RenameBranchDialog/index.ts new file mode 100644 index 00000000000..4c810563485 --- /dev/null +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/RenameBranchDialog/index.ts @@ -0,0 +1 @@ +export { RenameBranchDialog } from "./RenameBranchDialog"; diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx index 4fab6df7245..42ef7472217 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/WorkspaceHoverCard/WorkspaceHoverCard.tsx @@ -6,6 +6,7 @@ import { LuExternalLink, LuGlobe, LuLoaderCircle, + LuPencil, LuTriangleAlert, } from "react-icons/lu"; import { useHotkeyDisplay } from "renderer/hotkeys"; @@ -20,11 +21,13 @@ import { ReviewStatus } from "./components/ReviewStatus"; interface WorkspaceHoverCardContentProps { workspaceId: string; workspaceAlias?: string; + onEditBranchClick?: (branchName: string) => void; } export function WorkspaceHoverCardContent({ workspaceId, workspaceAlias, + onEditBranchClick, }: WorkspaceHoverCardContentProps) { const { data: worktreeInfo } = electronTrpc.workspaces.getWorktreeInfo.useQuery( @@ -80,26 +83,43 @@ export function WorkspaceHoverCardContent({ Branch - {repoUrl && branchExistsOnRemote ? ( - - {branchName} - - - ) : ( - - {branchName} - - )} +
+ {onEditBranchClick ? ( + + ) : ( + + {branchName} + + )} + {repoUrl && branchExistsOnRemote && ( + e.stopPropagation()} + > + + + )} +
)} {worktreeInfo?.createdAt && ( diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/index.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/index.ts index 489dd1ba41a..4d0ce41efb9 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/index.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceSidebar/WorkspaceListItem/components/index.ts @@ -1,2 +1,3 @@ export { DeleteWorkspaceDialog } from "./DeleteWorkspaceDialog"; +export { RenameBranchDialog } from "./RenameBranchDialog"; export { WorkspaceHoverCardContent } from "./WorkspaceHoverCard";