{mode.name}
diff --git a/apps/web/components/task/task-top-bar.tsx b/apps/web/components/task/task-top-bar.tsx
index 1c275c0525..99fb5afca2 100644
--- a/apps/web/components/task/task-top-bar.tsx
+++ b/apps/web/components/task/task-top-bar.tsx
@@ -1,18 +1,9 @@
"use client";
-import { memo, useState, useRef, useEffect, useCallback } from "react";
+import { memo, useCallback } from "react";
import Link from "next/link";
-import {
- IconBug,
- IconCopy,
- IconGitBranch,
- IconCheck,
- IconHome,
- IconSettings,
- IconPencil,
-} from "@tabler/icons-react";
+import { IconBug, IconHome, IconSettings } from "@tabler/icons-react";
import { Button } from "@kandev/ui/button";
-import { Input } from "@kandev/ui/input";
import {
Breadcrumb,
BreadcrumbItem,
@@ -22,11 +13,10 @@ import {
BreadcrumbSeparator,
} from "@kandev/ui/breadcrumb";
import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip";
-import { Popover, PopoverContent, PopoverTrigger } from "@kandev/ui/popover";
import { CommitStatBadge } from "@/components/diff-stat";
import { useSessionGit } from "@/hooks/domains/session/use-session-git";
-import { formatUserHomePath } from "@/lib/utils";
import { EditorsMenu } from "@/components/task/editors-menu";
+import { BranchPathPopover } from "@/components/task/branch-path-popover";
import { LayoutPresetSelector } from "@/components/task/layout-preset-selector";
import { DocumentControls } from "@/components/task/document/document-controls";
import { VcsSplitButton } from "@/components/vcs-split-button";
@@ -155,250 +145,6 @@ const TaskTopBar = memo(function TaskTopBar({
);
});
-/** Copies text to clipboard with a brief "copied" state */
-function useCopyToClipboard(): [boolean, (text: string) => void] {
- const [copied, setCopied] = useState(false);
- const handleCopy = (text: string) => {
- navigator.clipboard?.writeText(text);
- setCopied(true);
- setTimeout(() => setCopied(false), 500);
- };
- return [copied, handleCopy];
-}
-
-/** Copy button with check/copy icon toggle */
-function CopyIconButton({
- copied,
- onClick,
- className,
-}: {
- copied: boolean;
- onClick: (e: React.MouseEvent) => void;
- className?: string;
-}) {
- return (
-
- );
-}
-
-/** Copiable path row used in the branch popover */
-function PathRow({ label, path }: { label: string; path: string }) {
- const [copied, handleCopy] = useCopyToClipboard();
- return (
-
-
{label}
-
-
- {formatUserHomePath(path)}
-
-
{
- e.stopPropagation();
- handleCopy(formatUserHomePath(path));
- }}
- className="absolute right-1 top-1 p-1 rounded bg-background/80 backdrop-blur-sm hover:bg-background transition-all shadow-sm"
- />
-
-
- );
-}
-
-/** Inline branch rename input */
-function BranchRenameInput({
- editValue,
- onEditValueChange,
- onConfirm,
- onCancel,
- isRenaming,
-}: {
- editValue: string;
- onEditValueChange: (v: string) => void;
- onConfirm: () => void;
- onCancel: () => void;
- isRenaming: boolean;
-}) {
- const inputRef = useRef
(null);
-
- useEffect(() => {
- inputRef.current?.focus();
- inputRef.current?.select();
- }, []);
-
- const handleKeyDown = useCallback(
- (e: React.KeyboardEvent) => {
- if (e.key === "Enter") {
- e.preventDefault();
- onConfirm();
- } else if (e.key === "Escape") {
- onCancel();
- }
- },
- [onConfirm, onCancel],
- );
-
- return (
-
-
- onEditValueChange(e.target.value)}
- onKeyDown={handleKeyDown}
- onBlur={onConfirm}
- disabled={isRenaming}
- className="h-5 text-xs px-1 py-0 w-32 bg-background border-primary/50"
- />
-
- );
-}
-
-/** Hook for branch rename editing state */
-function useBranchRenameEdit(
- displayBranch: string | undefined,
- onRenameBranch: ((newName: string) => Promise) | undefined,
-) {
- const [isEditing, setIsEditing] = useState(false);
- const [editValue, setEditValue] = useState(displayBranch ?? "");
- const [isRenaming, setIsRenaming] = useState(false);
-
- useEffect(() => {
- if (!isEditing) setEditValue(displayBranch ?? "");
- }, [displayBranch, isEditing]);
-
- const handleStartEdit = useCallback((e: React.MouseEvent) => {
- e.preventDefault();
- e.stopPropagation();
- setIsEditing(true);
- }, []);
-
- const handleCancelEdit = useCallback(() => {
- setIsEditing(false);
- setEditValue(displayBranch ?? "");
- }, [displayBranch]);
-
- const handleConfirmRename = useCallback(async () => {
- const trimmed = editValue.trim();
- if (!onRenameBranch || !trimmed || trimmed === displayBranch?.trim() || isRenaming) {
- handleCancelEdit();
- return;
- }
- setIsRenaming(true);
- try {
- await onRenameBranch(trimmed);
- setIsEditing(false);
- } catch {
- // Error is handled by onRenameBranch (shows toast), keep edit mode open
- } finally {
- setIsRenaming(false);
- }
- }, [onRenameBranch, editValue, displayBranch, handleCancelEdit, isRenaming]);
-
- return {
- isEditing,
- editValue,
- setEditValue,
- isRenaming,
- handleStartEdit,
- handleCancelEdit,
- handleConfirmRename,
- };
-}
-
-function BranchPathPopover({
- displayBranch,
- repositoryPath,
- worktreePath,
- onRenameBranch,
-}: {
- displayBranch?: string;
- repositoryPath?: string | null;
- worktreePath?: string | null;
- onRenameBranch?: (newName: string) => Promise;
-}) {
- const [copiedBranch, handleCopyBranch] = useCopyToClipboard();
- const [popoverOpen, setPopoverOpen] = useState(false);
- const {
- isEditing,
- editValue,
- setEditValue,
- isRenaming,
- handleStartEdit: startEdit,
- handleCancelEdit,
- handleConfirmRename,
- } = useBranchRenameEdit(displayBranch, onRenameBranch);
- const handleStartEdit = useCallback(
- (e: React.MouseEvent) => {
- startEdit(e);
- setPopoverOpen(false);
- },
- [startEdit],
- );
-
- if (!displayBranch) return null;
-
- if (isEditing) {
- return (
-
- );
- }
-
- return (
-
-
-
-
-
-
- {displayBranch}
- {onRenameBranch && (
-
- )}
- {
- e.stopPropagation();
- handleCopyBranch(displayBranch);
- }}
- className="opacity-0 group-hover:opacity-100 transition-opacity cursor-pointer ml-0.5"
- />
-
-
-
-
- {onRenameBranch ? "Click to see paths, or click pencil to rename" : "Click to see paths"}
-
-
-
-
- {repositoryPath &&
}
- {worktreePath &&
}
-
-
-
- );
-}
-
/** Left section: breadcrumbs, branch pill */
function TopBarLeft({
taskId,
@@ -441,7 +187,7 @@ function TopBarLeft({