"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 (
);
});
// 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 && (
Open in office view
)}
);
}
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 };