"use client"; import { memo, useCallback, type ReactNode } from "react"; import Link from "next/link"; import { IconBug, IconDots, IconHome, IconSettings } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator, } from "@kandev/ui/breadcrumb"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, } from "@kandev/ui/dropdown-menu"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { useSessionGit } from "@/hooks/domains/session/use-session-git"; 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"; import { PRTopbarButton } from "@/components/github/pr-topbar-button"; import { JiraTicketButton, extractJiraKey } from "@/components/jira/jira-ticket-button"; import { JiraLinkButton } from "@/components/jira/jira-link-button"; import { LinearIssueButton, extractLinearKey } from "@/components/linear/linear-issue-button"; import { LinearLinkButton } from "@/components/linear/linear-link-button"; import { useJiraAvailable } from "@/components/jira/my-jira/use-jira-availability"; import { useLinearAvailable } from "@/components/linear/use-linear-availability"; import { PortForwardButton } from "@/components/task/port-forward-dialog"; import { WorkflowStepper, type WorkflowStepperStep } from "@/components/task/workflow-stepper"; import { RemoteCloudTooltip } from "@/components/task/remote-cloud-tooltip"; import { QuickChatButton } from "@/components/task/quick-chat-button"; import { TopbarActionOverflow, type TopbarOverflowItem, } from "@/components/task/topbar-action-overflow"; import { IntegrationsMenu } from "@/components/integrations/integrations-menu"; import { DEBUG_UI } from "@/lib/config"; import { toast } from "sonner"; type TaskTopBarProps = { taskId?: string | null; activeSessionId?: string | null; taskTitle?: string; baseBranch?: string; onStartAgent?: (agentProfileId: string) => void; onStopAgent?: () => void; isAgentRunning?: boolean; isAgentLoading?: boolean; worktreePath?: string | null; worktreeBranch?: string | null; repositoryPath?: string | null; repositoryName?: string | null; showDebugOverlay?: boolean; onToggleDebugOverlay?: () => void; workflowSteps?: WorkflowStepperStep[]; currentStepId?: string | null; workflowId?: string | null; workspaceId?: string | null; isArchived?: boolean; isRemoteExecutor?: boolean; isAgentctlReady?: boolean; remoteExecutorName?: string | null; remoteExecutorType?: string | null; remoteState?: string | null; remoteCreatedAt?: string | null; remoteCheckedAt?: string | null; remoteStatusError?: string | null; }; type TopBarLeftProps = { taskId?: string | null; activeSessionId?: string | null; taskTitle?: string; repositoryName?: string | null; displayBranch?: string; repositoryPath?: string | null; worktreePath?: string | null; isRemoteExecutor?: boolean; remoteExecutorName?: string | null; remoteExecutorType?: string | null; remoteState?: string | null; remoteCreatedAt?: string | null; remoteCheckedAt?: string | null; remoteStatusError?: string | null; workspaceId?: string | null; onRenameBranch?: (newName: string) => Promise; }; const TaskTopBar = memo(function TaskTopBar({ taskId, activeSessionId, taskTitle, baseBranch, worktreePath, worktreeBranch, repositoryPath, repositoryName, showDebugOverlay, onToggleDebugOverlay, workflowSteps, currentStepId, workflowId, workspaceId, isArchived, isRemoteExecutor, isAgentctlReady, remoteExecutorName, remoteExecutorType, remoteState, remoteCreatedAt, remoteCheckedAt, remoteStatusError, }: TaskTopBarProps) { const git = useSessionGit(activeSessionId); // Prefer live git status branch (updates after rename), fallback to session worktree branch const displayBranch = git.branch || worktreeBranch || baseBranch; // Callback for renaming branch const handleRenameBranch = useCallback( async (newName: string) => { const result = await git.renameBranch(newName); if (result.success) { toast.success(`Branch renamed to "${newName}"`); } else { const msg = result.error || "Failed to rename branch"; toast.error(msg); throw new Error(msg); } }, [git], ); return (
{workflowSteps && workflowSteps.length > 0 && ( )}
); }); // IssueTrackerButtons picks the right ticket button for a task. Jira and // Linear use the same TEAM-NUMBER identifier shape, so both `extract` calls // would match "ENG-123" — we resolve ambiguity by preferring whichever // integration is currently available for the workspace, with Jira winning the // tie-break since it shipped first. When the title carries no identifier, // both link buttons are offered (each gated on its own availability). function IssueTrackerButtons({ taskId, workspaceId, taskTitle, }: { taskId: string | null | undefined; workspaceId: string | null | undefined; taskTitle: string | null | undefined; }) { const jiraAvailable = useJiraAvailable(workspaceId); const linearAvailable = useLinearAvailable(workspaceId); const jiraKey = extractJiraKey(taskTitle); const linearKey = extractLinearKey(taskTitle); if (jiraKey && jiraAvailable) { return ; } if (linearKey && linearAvailable) { return ; } return ( <> ); } /** Left section: breadcrumbs, branch pill */ function TopBarLeft({ taskId, activeSessionId, taskTitle, repositoryName, displayBranch, repositoryPath, worktreePath, isRemoteExecutor, remoteExecutorName, remoteExecutorType, remoteState, remoteCreatedAt, remoteCheckedAt, remoteStatusError, workspaceId, onRenameBranch, }: TopBarLeftProps) { return (
{repositoryName && ( <> {repositoryName} {repositoryName} )} {taskTitle ?? "Task details"} {taskTitle ?? "Task details"}
{isRemoteExecutor && ( )}
); } function TopbarCluster({ label, className = "", children, }: { label: string; className?: string; children: ReactNode; }) { return (
{children}
); } function MoreToolsMenu({ showDebugOverlay, onToggleDebugOverlay, }: { showDebugOverlay?: boolean; onToggleDebugOverlay?: () => void; }) { const showDebugItem = DEBUG_UI && onToggleDebugOverlay; const debugLabel = showDebugOverlay ? "Hide Debug Info" : "Show Debug Info"; return ( More tools More tools {showDebugItem && ( <> {debugLabel} )} Settings ); } function SettingsButton() { return ( Settings ); } function AttentionStatusGroup({ taskId, activeSessionId, isArchived, workspaceId, isRemoteExecutor, isAgentctlReady, taskTitle, }: { taskId?: string | null; activeSessionId?: string | null; isArchived?: boolean; workspaceId?: string | null; isRemoteExecutor?: boolean; isAgentctlReady?: boolean; taskTitle?: string; }) { return ( {!isArchived && ( <> )} ); } function TopbarToolsGroup({ activeSessionId, showDebugOverlay, onToggleDebugOverlay, isArchived, }: { activeSessionId?: string | null; showDebugOverlay?: boolean; onToggleDebugOverlay?: () => void; isArchived?: boolean; }) { const showDebugMenu = DEBUG_UI && onToggleDebugOverlay; return ( {!isArchived && ( <> )} {showDebugMenu ? ( ) : ( )} ); } /** Right section: primary VCS action, status/attention, tools menu */ function TopBarRight({ taskId, activeSessionId, baseBranch, showDebugOverlay, onToggleDebugOverlay, isArchived, workspaceId, isRemoteExecutor, isAgentctlReady, taskTitle, }: { taskId?: string | null; activeSessionId?: string | null; baseBranch?: string; showDebugOverlay?: boolean; onToggleDebugOverlay?: () => void; isArchived?: boolean; workspaceId?: string | null; isRemoteExecutor?: boolean; isAgentctlReady?: boolean; taskTitle?: string; }) { const items: TopbarOverflowItem[] = []; if (!isArchived && workspaceId) { items.push({ id: "quick-chat", label: "Quick chat", priority: 20, content: ( ), }); } if (!isArchived) { items.push({ id: "vcs", label: "Primary version control action", priority: 100, required: true, content: ( ), }); } items.push({ id: "attention", label: "Task status and attention", priority: 80, content: ( ), }); items.push({ id: "tools", label: "Task tools", priority: 10, content: ( ), }); return ( ); } export { TaskTopBar };