Merge remote-tracking branch 'origin/main'

This commit is contained in:
Mohamed Boudra
2026-06-12 12:12:46 +07:00
21 changed files with 492 additions and 13 deletions
@@ -399,6 +399,7 @@ interface WorkspaceDesktopTabsRowProps {
onCloseTab: (tabId: string) => Promise<void> | void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTabsToLeft: (tabId: string) => Promise<void> | void;
@@ -713,6 +714,7 @@ export function WorkspaceDesktopTabsRow({
onCloseTab,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTabsToLeft,
@@ -784,6 +786,7 @@ export function WorkspaceDesktopTabsRow({
() => ({
copyResumeCommand: t("workspace.tabs.menu.copyResumeCommand"),
copyAgentId: t("workspace.tabs.menu.copyAgentId"),
copyFilePath: t("workspace.tabs.menu.copyFilePath"),
rename: t("workspace.tabs.menu.rename"),
closeAbove: t("workspace.tabs.menu.closeAbove"),
closeBelow: t("workspace.tabs.menu.closeBelow"),
@@ -881,6 +884,7 @@ export function WorkspaceDesktopTabsRow({
normalizedWorkspaceId={normalizedWorkspaceId}
onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab}
onCloseTabsToLeft={onCloseTabsToLeft}
@@ -911,6 +915,7 @@ export function WorkspaceDesktopTabsRow({
onCloseTabsToLeft,
onCloseTabsToRight,
onCopyAgentId,
onCopyFilePath,
onCopyResumeCommand,
onNavigateTab,
onReloadAgent,
@@ -1003,6 +1008,7 @@ function ResolvedDesktopTabChip({
normalizedWorkspaceId,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTabsToLeft,
@@ -1028,6 +1034,7 @@ function ResolvedDesktopTabChip({
normalizedWorkspaceId: string;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTabsToLeft: (tabId: string) => Promise<void> | void;
@@ -1053,6 +1060,7 @@ function ResolvedDesktopTabChip({
tabCount,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -1069,6 +1077,7 @@ function ResolvedDesktopTabChip({
onCloseTabsToLeft,
onCloseTabsToRight,
onCopyAgentId,
onCopyFilePath,
onCopyResumeCommand,
labels,
onReloadAgent,
@@ -392,6 +392,7 @@ interface MobileWorkspaceTabSwitcherProps {
onSelectSwitcherTab: (key: string) => void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void;
@@ -579,6 +580,7 @@ function MobileWorkspaceTabOption({
onPress,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -596,6 +598,7 @@ function MobileWorkspaceTabOption({
onPress: () => void;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void;
@@ -608,6 +611,7 @@ function MobileWorkspaceTabOption({
() => ({
copyResumeCommand: t("workspace.tabs.menu.copyResumeCommand"),
copyAgentId: t("workspace.tabs.menu.copyAgentId"),
copyFilePath: t("workspace.tabs.menu.copyFilePath"),
rename: t("workspace.tabs.menu.rename"),
closeAbove: t("workspace.tabs.menu.closeAbove"),
closeBelow: t("workspace.tabs.menu.closeBelow"),
@@ -629,6 +633,7 @@ function MobileWorkspaceTabOption({
menuTestIDBase,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -695,6 +700,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
onSelectSwitcherTab,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -750,6 +756,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
onPress={onPress}
onCopyResumeCommand={onCopyResumeCommand}
onCopyAgentId={onCopyAgentId}
onCopyFilePath={onCopyFilePath}
onReloadAgent={onReloadAgent}
onRenameTab={onRenameTab}
onCloseTab={onCloseTab}
@@ -767,6 +774,7 @@ const MobileWorkspaceTabSwitcher = memo(function MobileWorkspaceTabSwitcher({
normalizedWorkspaceId,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -2712,6 +2720,19 @@ function WorkspaceScreenContent({
[toast, t],
);
const handleCopyFilePath = useCallback(
async (path: string) => {
if (!path) return;
try {
await Clipboard.setStringAsync(path);
toast.copied(t("workspace.tabs.toasts.filePathCopiedLabel"));
} catch {
toast.error(t("workspace.tabs.toasts.copyFailed"));
}
},
[toast, t],
);
const handleCopyResumeCommand = useCallback(
async (agentId: string) => {
if (!agentId) return;
@@ -3534,6 +3555,7 @@ function WorkspaceScreenContent({
onCloseTab={handleCloseTabById}
onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId}
onCopyFilePath={handleCopyFilePath}
onReloadAgent={handleReloadAgent}
onRenameTab={handleRenameTab}
onCloseTabsToLeft={handleCloseTabsToLeftInPane}
@@ -3568,6 +3590,7 @@ function WorkspaceScreenContent({
handleCloseTabById,
handleCopyResumeCommand,
handleCopyAgentId,
handleCopyFilePath,
handleReloadAgent,
handleRenameTab,
handleCloseTabsToLeftInPane,
@@ -3648,6 +3671,7 @@ function WorkspaceScreenContent({
onSelectSwitcherTab={handleSelectSwitcherTab}
onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId}
onCopyFilePath={handleCopyFilePath}
onReloadAgent={handleReloadAgent}
onRenameTab={handleRenameTab}
onCloseTab={handleCloseTabById}
@@ -3669,6 +3693,7 @@ function WorkspaceScreenContent({
onCloseTab={handleCloseTabById}
onCopyResumeCommand={handleCopyResumeCommand}
onCopyAgentId={handleCopyAgentId}
onCopyFilePath={handleCopyFilePath}
onReloadAgent={handleReloadAgent}
onRenameTab={handleRenameTab}
onCloseTabsToLeft={handleCloseTabsToLeft}
@@ -15,6 +15,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
it("uses desktop tab ordering labels for desktop menus", () => {
const onCopyResumeCommand = vi.fn();
const onCopyAgentId = vi.fn();
const onCopyFilePath = vi.fn();
const onReloadAgent = vi.fn();
const onRenameTab = vi.fn();
const onCloseTab = vi.fn();
@@ -30,6 +31,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -59,6 +61,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-menu-agent_123",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab: vi.fn(),
onCloseTab: vi.fn(),
@@ -93,6 +96,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-menu-draft_123",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab: vi.fn(),
onCloseTab: vi.fn(),
@@ -120,6 +124,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab: vi.fn(),
onCloseTab: vi.fn(),
@@ -148,6 +153,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-agent_123",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab,
onCloseTab: vi.fn(),
@@ -181,6 +187,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase: "workspace-tab-context-terminal_abc",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab,
onCloseTab: vi.fn(),
@@ -193,6 +200,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
expect(labels[0]).toBe("Rename");
expect(labels).not.toContain("Copy resume command");
expect(labels).not.toContain("Copy agent id");
expect(labels).not.toContain("Copy file path");
expect(labels).not.toContain("Reload agent");
const renameEntry = entries.find((entry) => entry.kind === "item" && entry.label === "Rename");
@@ -203,6 +211,48 @@ describe("buildWorkspaceTabMenuEntries", () => {
expect(onRenameTab).toHaveBeenCalledWith(terminalTab);
});
it("includes copy file path for file tabs", () => {
const onCopyFilePath = vi.fn();
const fileTab: WorkspaceTabDescriptor = {
key: "file_abc",
tabId: "file_abc",
kind: "file",
target: { kind: "file", path: "/some/path.ts", lineStart: 1, lineEnd: 10 },
};
const entries = buildWorkspaceTabMenuEntries({
surface: "desktop",
tab: fileTab,
index: 0,
tabCount: 1,
menuTestIDBase: "workspace-tab-context-file_abc",
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath,
onReloadAgent: vi.fn(),
onRenameTab: vi.fn(),
onCloseTab: vi.fn(),
onCloseTabsBefore: vi.fn(),
onCloseTabsAfter: vi.fn(),
onCloseOtherTabs: vi.fn(),
});
const labels = entries.filter((entry) => entry.kind === "item").map((entry) => entry.label);
expect(labels[0]).toBe("Copy file path");
expect(labels).not.toContain("Copy resume command");
expect(labels).not.toContain("Copy agent id");
expect(labels).not.toContain("Rename");
expect(labels).not.toContain("Reload agent");
const copyFilePathEntry = entries.find(
(entry) => entry.kind === "item" && entry.key === "copy-file-path",
);
if (!copyFilePathEntry || copyFilePathEntry.kind !== "item") {
throw new Error("Copy file path entry missing");
}
copyFilePathEntry.onSelect();
expect(onCopyFilePath).toHaveBeenCalledWith("/some/path.ts");
});
it("uses the same rename entry shape for agent and terminal tabs", () => {
const terminalTab: WorkspaceTabDescriptor = {
key: "terminal_abc",
@@ -218,6 +268,7 @@ describe("buildWorkspaceTabMenuEntries", () => {
menuTestIDBase,
onCopyResumeCommand: vi.fn(),
onCopyAgentId: vi.fn(),
onCopyFilePath: vi.fn(),
onReloadAgent: vi.fn(),
onRenameTab: vi.fn(),
onCloseTab: vi.fn(),
@@ -8,6 +8,7 @@ export type WorkspaceTabMenuSurface = "desktop" | "mobile";
export interface WorkspaceTabMenuLabels {
copyResumeCommand: string;
copyAgentId: string;
copyFilePath: string;
rename: string;
closeAbove: string;
closeBelow: string;
@@ -22,6 +23,7 @@ export interface WorkspaceTabMenuLabels {
export const DEFAULT_WORKSPACE_TAB_MENU_LABELS: WorkspaceTabMenuLabels = {
copyResumeCommand: i18n.t("workspace.tabs.menu.copyResumeCommand"),
copyAgentId: i18n.t("workspace.tabs.menu.copyAgentId"),
copyFilePath: i18n.t("workspace.tabs.menu.copyFilePath"),
rename: i18n.t("workspace.tabs.menu.rename"),
closeAbove: i18n.t("workspace.tabs.menu.closeAbove"),
closeBelow: i18n.t("workspace.tabs.menu.closeBelow"),
@@ -66,6 +68,7 @@ interface BuildWorkspaceTabMenuEntriesInput {
menuTestIDBase: string;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void;
@@ -81,6 +84,7 @@ interface BuildWorkspaceDesktopTabActionsInput {
tabCount: number;
onCopyResumeCommand: (agentId: string) => Promise<void> | void;
onCopyAgentId: (agentId: string) => Promise<void> | void;
onCopyFilePath: (path: string) => Promise<void> | void;
onReloadAgent: (agentId: string) => Promise<void> | void;
onRenameTab: (tab: WorkspaceTabDescriptor) => void;
onCloseTab: (tabId: string) => Promise<void> | void;
@@ -148,6 +152,7 @@ export function buildWorkspaceTabMenuEntries(
menuTestIDBase,
onCopyResumeCommand,
onCopyAgentId,
onCopyFilePath,
onReloadAgent,
onRenameTab,
onCloseTab,
@@ -186,6 +191,20 @@ export function buildWorkspaceTabMenuEntries(
});
}
if (tab.target.kind === "file") {
const filePath = tab.target.path;
entries.push({
kind: "item",
key: "copy-file-path",
label: labels.copyFilePath,
icon: "copy",
testID: `${menuTestIDBase}-copy-file-path`,
onSelect: () => {
void onCopyFilePath(filePath);
},
});
}
if (tab.target.kind === "agent" || tab.target.kind === "terminal") {
entries.push({
kind: "item",
@@ -278,6 +297,7 @@ export function buildWorkspaceDesktopTabActions(
menuTestIDBase: contextMenuTestId,
onCopyResumeCommand: input.onCopyResumeCommand,
onCopyAgentId: input.onCopyAgentId,
onCopyFilePath: input.onCopyFilePath,
onReloadAgent: input.onReloadAgent,
onRenameTab: input.onRenameTab,
onCloseTab: input.onCloseTab,