"use client"; import { useCallback, useState } from "react"; import { IconBox, IconLoader2, IconTrash } from "@tabler/icons-react"; import { HoverCard, HoverCardContent, HoverCardTrigger } from "@kandev/ui/hover-card"; import { Button } from "@kandev/ui/button"; import { Tooltip, TooltipContent, TooltipTrigger } from "@kandev/ui/tooltip"; import { getExecutorStatusIcon } from "@/lib/executor-icons"; import { TaskResetEnvConfirmDialog } from "./task-reset-env-confirm-dialog"; import type { StatusTone } from "./executor-environment-status"; import { usePrepareSummary } from "@/hooks/domains/session/use-prepare-summary"; import { useTaskEnvironment } from "@/hooks/domains/session/use-task-environment"; import { isPreparingPhase } from "@/lib/prepare/summarize"; import { PrepareStatusSection } from "./executor-prepare-status"; import { EnvironmentInfo } from "./executor-environment-info"; import { useTranslation } from "react-i18next"; import { t } from "@/lib/i18n"; type ExecutorSettingsButtonProps = { taskId?: string | null; sessionId?: string | null; disabled?: boolean; }; function ResetEnvironmentTooltip({ hasWorktreePath }: { hasWorktreePath: boolean }) { const { t } = useTranslation(); return ( {t("task:resetEnvironment2")} {t("task:deletesTheCurrentTaskEnvironmentContainer")} {t("task:anyUncommittedOrUnpushedChangesAre")} {hasWorktreePath && ( {t("task:pushYourBranchToItsRemote")} )} ); } export function ExecutorSettingsButton({ taskId, sessionId, disabled, }: ExecutorSettingsButtonProps) { const { t } = useTranslation(); const [open, setOpen] = useState(false); const [resetDialogOpen, setResetDialogOpen] = useState(false); const prepare = usePrepareSummary(sessionId ?? null); const isPreparing = isPreparingPhase(prepare.phase); // Promote the foreground polling cadence while preparing so the icon flips // to "ready" without the user hovering over the trigger. const { env, container, ssh, loading, isResetting, reset, status } = useTaskEnvironment( taskId, open || isPreparing, ); const hasWorktreePath = Boolean(env?.worktree_path); const handleReset = useCallback( async (opts: { pushBranch: boolean }) => { const ok = await reset(opts); if (ok) { setResetDialogOpen(false); setOpen(false); } }, [reset], ); if (!taskId) return null; const executorType = env?.executor_type ?? null; const ariaLabel = computeAriaLabel(isPreparing, status); return ( <> {/* Real for free Enter/Space activation, focus rings and disabled semantics. variant="ghost" removes the outline so the trigger reads as info, not as a clickable action — but we keep the click→toggle behaviour the underlying HoverCard offers. */} setResetDialogOpen(true)} > {t("task:resetEnvironment2")} > ); } function computeAriaLabel( preparing: boolean, status: { label: string; tone: StatusTone } | null, ): string { if (preparing) return t("task:executorSettingsPreparing"); if (status) return t("task:executorSettingsWithStatus", { status: status.label }); return t("task:executorSettings"); } function ExecutorButtonIcon({ executorType, preparing, }: { executorType: string | null; preparing: boolean; }) { if (preparing) { return ( ); } if (!executorType) { return ; } const { Icon, testId } = getExecutorStatusIcon(executorType, false); return ; } const DOT_CLASSES: Record = { running: "bg-green-500", stopped: "bg-zinc-500", warn: "bg-amber-500", error: "bg-red-500", neutral: "bg-muted-foreground", }; function ExecutorStatusDot({ status, loading, }: { status: { label: string; tone: StatusTone } | null; loading: boolean; }) { const { t } = useTranslation(); const tone = status?.tone ?? "neutral"; const label = status?.label ?? "not created"; return ( ); }
{t("task:resetEnvironment2")}
{t("task:deletesTheCurrentTaskEnvironmentContainer")}
{t("task:anyUncommittedOrUnpushedChangesAre")}
{t("task:pushYourBranchToItsRemote")}