"use client"; import { memo, type ReactNode } from "react"; import Link from "next/link"; import { IconBug } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { Breadcrumb, BreadcrumbItem, BreadcrumbList, BreadcrumbPage } from "@kandev/ui/breadcrumb"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { EditorsMenu } from "@/components/task/editors-menu"; import { LayoutPresetSelector } from "@/components/task/layout-preset-selector"; import { DocumentControls } from "@/components/task/document/document-controls"; import { PRTopbarButton } from "@/components/github/pr-topbar-button"; import { MRTopbarButton } from "@/components/gitlab/mr-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 "@/hooks/domains/jira/use-jira-availability"; import { useLinearAvailable } from "@/hooks/domains/linear/use-linear-availability"; import { PortForwardButton } from "@/components/task/port-forward-dialog"; import { ExecutorSettingsButton } from "@/components/task/executor-settings-button"; import { WorkflowStepper, type WorkflowStepperStep } from "@/components/task/workflow-stepper"; import { isDebugUI } from "@/lib/config"; type TaskTopBarProps = { taskId?: string | null; activeSessionId?: string | null; taskTitle?: string; onStartAgent?: (agentProfileId: string) => void; onStopAgent?: () => void; isAgentRunning?: boolean; isAgentLoading?: boolean; showDebugOverlay?: boolean; onToggleDebugOverlay?: () => void; workflowSteps?: WorkflowStepperStep[]; currentStepId?: string | null; workflowId?: string | null; workspaceId?: string | null; isArchived?: boolean; isRemoteExecutor?: boolean; isAgentctlReady?: boolean; remoteExecutorType?: string | null; officeTaskHref?: string | null; }; type TopBarLeftProps = { taskId?: string | null; activeSessionId?: string | null; taskTitle?: string; remoteExecutorType?: string | null; isArchived?: boolean; }; const TaskTopBar = memo(function TaskTopBar({ taskId, activeSessionId, taskTitle, showDebugOverlay, onToggleDebugOverlay, workflowSteps, currentStepId, workflowId, workspaceId, isArchived, isRemoteExecutor, isAgentctlReady, remoteExecutorType, officeTaskHref, }: TaskTopBarProps) { 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(); const linearAvailable = useLinearAvailable(); const jiraKey = extractJiraKey(taskTitle); const linearKey = extractLinearKey(taskTitle); if (jiraKey && jiraAvailable) { return ; } if (linearKey && linearAvailable) { return ; } return ( <> ); } /** Left section: task name breadcrumb + executor info. Home + integrations * moved to the unified AppSidebar in the UI overhaul. */ function TopBarLeft({ taskId, activeSessionId, taskTitle, remoteExecutorType, isArchived, }: TopBarLeftProps) { const showExecutorSettings = shouldShowExecutorEnvironmentControls(remoteExecutorType); return (
{taskTitle ?? "Task details"} {taskTitle ?? "Task details"} {!isArchived && showExecutorSettings && ( )}
); } function TopbarCluster({ label, className = "", children, }: { label: string; className?: string; children: ReactNode; }) { return (
{children}
); } function DebugOverlayToggle({ showDebugOverlay, onToggleDebugOverlay, }: { showDebugOverlay?: boolean; onToggleDebugOverlay: () => void; }) { const label = showDebugOverlay ? "Hide Debug Info" : "Show Debug Info"; return ( {label} ); } 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 && ( <> {/* PR (GitHub) and MR (GitLab) buttons each render nothing when no rows match, so showing both covers GitHub-only, GitLab-only, and multi-repo tasks without needing an explicit provider switch. */} )} ); } function TopbarToolsGroup({ activeSessionId, showDebugOverlay, onToggleDebugOverlay, isArchived, }: { activeSessionId?: string | null; showDebugOverlay?: boolean; onToggleDebugOverlay?: () => void; isArchived?: boolean; }) { const showDebugToggle = isDebugUI() && onToggleDebugOverlay; return ( {!isArchived && ( <> )} {showDebugToggle && ( )} ); } /** Right section: status/attention + tools rendered inline. * The former overflow popover was removed in the UI overhaul — every cluster * is always visible so users don't have to discover the dots menu. */ function TopBarRight({ taskId, activeSessionId, showDebugOverlay, onToggleDebugOverlay, isArchived, workspaceId, isRemoteExecutor, isAgentctlReady, taskTitle, officeTaskHref, }: { taskId?: string | null; activeSessionId?: string | null; showDebugOverlay?: boolean; onToggleDebugOverlay?: () => void; isArchived?: boolean; workspaceId?: string | null; isRemoteExecutor?: boolean; isAgentctlReady?: boolean; taskTitle?: string; officeTaskHref?: string | null; }) { return (
{officeTaskHref && ( )}
); } function shouldShowExecutorEnvironmentControls(executorType?: string | null): boolean { switch (executorType) { case "local_docker": case "remote_docker": case "sprites": case "ssh": return true; default: return false; } } export { TaskTopBar };