/** * @file WorkspaceRowMenu — the sidebar workspace row's context menu, opened at * the pointer (right-click) or under the row's `⋯` button. Every item is an * existing call: Start/Stop (`POST /api/lanes/:id/start` slot 0 / `stop`), * Rename (the row's inline field), New workspace here (the Add modal prefilled), * Copy path, and Reset/Remove through `DestructiveLaneModal` — Reset only on a * managed lane. The server's guards are the real ones; the menu only hides * what the lane's kind or tool makes meaningless. The shell (position, * Escape / outside-click close) is `ContextMenu`. * @author Nguyễn Ngọc Trí Vĩ */ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { api } from "../../lib/api"; import type { Lane } from "../../lib/types"; import { ContextMenu, MenuItem, MenuSeparator } from "../ContextMenu"; export type WorkspaceMenuAction = "rename" | "new-here" | "reset" | "remove" | "started"; export function WorkspaceRowMenu({ lane, x, y, onClose, onAction, }: { lane: Lane; x: number; y: number; onClose: () => void; /** Items the tree itself carries out (rename field, modals, selection). */ onAction: (action: WorkspaceMenuAction) => void; }) { const { t } = useTranslation("lanes"); const [error, setError] = useState(null); const run = async (fn: () => Promise, then?: WorkspaceMenuAction) => { setError(null); try { await fn(); if (then) onAction(then); onClose(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } }; const live = !!lane.run_id || lane.status === "running"; return ( live ? void run(() => api.lanes.action(lane.id, "stop")) : void run(() => api.lanes.action(lane.id, "start", { slot: 0, prompt: "" }), "started") } > {live ? t("menu.stop") : t("menu.start")} onAction("rename")}>{t("menu.rename")} onAction("new-here")}>{t("menu.newHere")} void run(() => navigator.clipboard.writeText(lane.cwd))}> {t("menu.copyPath")} {lane.kind === "managed" && ( onAction("reset")}> {t("menu.reset")} )} onAction("remove")}> {t("menu.remove")} {error && (

{error}

)}
); }