"use client";
import { memo, 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 { 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 { 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 { 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: home → task name breadcrumb, integrations menu, executor info */
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 MoreToolsMenu({
showDebugOverlay,
onToggleDebugOverlay,
}: {
showDebugOverlay?: boolean;
onToggleDebugOverlay?: () => void;
}) {
const showDebugItem = isDebugUI() && 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 && (
<>
{/* 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 showDebugMenu = isDebugUI() && onToggleDebugOverlay;
return (
{!isArchived && (
<>
>
)}
{showDebugMenu ? (
) : (
)}
);
}
/** Right section: status/attention, tools 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;
}) {
const items: TopbarOverflowItem[] = [];
if (officeTaskHref) {
items.push({
id: "office-view",
label: "Open in office view",
priority: 90,
content: (
Open in office view
),
});
}
if (!isArchived && workspaceId) {
items.push({
id: "quick-chat",
label: "Quick chat",
priority: 20,
content: (
),
});
}
items.push({
id: "attention",
label: "Task status and attention",
priority: 80,
content: (
),
});
items.push({
id: "tools",
label: "Task tools",
priority: 10,
content: (
),
});
return (
);
}
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 };