| ... | @@ -561,7 +561,6 @@ export default function Sidebar() { | ... | @@ -561,7 +561,6 @@ export default function Sidebar() { |
| 561 | if (!api) return; | 561 | if (!api) return; |
| 562 | const thread = threads.find((t) => t.id === threadId); | 562 | const thread = threads.find((t) => t.id === threadId); |
| 563 | if (!thread) return; | 563 | if (!thread) return; |
| 564 | | | |
| 565 | const threadProject = projects.find((project) => project.id === thread.projectId); | 564 | const threadProject = projects.find((project) => project.id === thread.projectId); |
| 566 | // When bulk-deleting, exclude the other threads being deleted so | 565 | // When bulk-deleting, exclude the other threads being deleted so |
| 567 | // getOrphanedWorktreePathForThread correctly detects that no surviving | 566 | // getOrphanedWorktreePathForThread correctly detects that no surviving |
| ... | @@ -665,7 +664,7 @@ export default function Sidebar() { | ... | @@ -665,7 +664,7 @@ export default function Sidebar() { |
| 665 | ], | 664 | ], |
| 666 | ); | 665 | ); |
| 667 | | 666 | |
| 668 | const { copyToClipboard } = useCopyToClipboard<{ threadId: ThreadId }>({ | 667 | const { copyToClipboard: copyThreadIdToClipboard } = useCopyToClipboard<{ threadId: ThreadId }>({ |
| 669 | onCopy: (ctx) => { | 668 | onCopy: (ctx) => { |
| 670 | toastManager.add({ | 669 | toastManager.add({ |
| 671 | type: "success", | 670 | type: "success", |
| ... | @@ -681,21 +680,40 @@ export default function Sidebar() { | ... | @@ -681,21 +680,40 @@ export default function Sidebar() { |
| 681 | }); | 680 | }); |
| 682 | }, | 681 | }, |
| 683 | }); | 682 | }); |
| | 683 | const { copyToClipboard: copyPathToClipboard } = useCopyToClipboard<{ path: string }>({ |
| | 684 | onCopy: (ctx) => { |
| | 685 | toastManager.add({ |
| | 686 | type: "success", |
| | 687 | title: "Path copied", |
| | 688 | description: ctx.path, |
| | 689 | }); |
| | 690 | }, |
| | 691 | onError: (error) => { |
| | 692 | toastManager.add({ |
| | 693 | type: "error", |
| | 694 | title: "Failed to copy path", |
| | 695 | description: error instanceof Error ? error.message : "An error occurred.", |
| | 696 | }); |
| | 697 | }, |
| | 698 | }); |
| 684 | const handleThreadContextMenu = useCallback( | 699 | const handleThreadContextMenu = useCallback( |
| 685 | async (threadId: ThreadId, position: { x: number; y: number }) => { | 700 | async (threadId: ThreadId, position: { x: number; y: number }) => { |
| 686 | const api = readNativeApi(); | 701 | const api = readNativeApi(); |
| 687 | if (!api) return; | 702 | if (!api) return; |
| | 703 | const thread = threads.find((t) => t.id === threadId); |
| | 704 | if (!thread) return; |
| | 705 | const threadWorkspacePath = |
| | 706 | thread.worktreePath ?? projectCwdById.get(thread.projectId) ?? null; |
| 688 | const clicked = await api.contextMenu.show( | 707 | const clicked = await api.contextMenu.show( |
| 689 | [ | 708 | [ |
| 690 | { id: "rename", label: "Rename thread" }, | 709 | { id: "rename", label: "Rename thread" }, |
| 691 | { id: "mark-unread", label: "Mark unread" }, | 710 | { id: "mark-unread", label: "Mark unread" }, |
| | 711 | { id: "copy-path", label: "Copy Path" }, |
| 692 | { id: "copy-thread-id", label: "Copy Thread ID" }, | 712 | { id: "copy-thread-id", label: "Copy Thread ID" }, |
| 693 | { id: "delete", label: "Delete", destructive: true }, | 713 | { id: "delete", label: "Delete", destructive: true }, |
| 694 | ], | 714 | ], |
| 695 | position, | 715 | position, |
| 696 | ); | 716 | ); |
| 697 | const thread = threads.find((t) => t.id === threadId); | | |
| 698 | if (!thread) return; | | |
| 699 | | 717 | |
| 700 | if (clicked === "rename") { | 718 | if (clicked === "rename") { |
| 701 | setRenamingThreadId(threadId); | 719 | setRenamingThreadId(threadId); |
| ... | @@ -708,8 +726,20 @@ export default function Sidebar() { | ... | @@ -708,8 +726,20 @@ export default function Sidebar() { |
| 708 | markThreadUnread(threadId); | 726 | markThreadUnread(threadId); |
| 709 | return; | 727 | return; |
| 710 | } | 728 | } |
| | 729 | if (clicked === "copy-path") { |
| | 730 | if (!threadWorkspacePath) { |
| | 731 | toastManager.add({ |
| | 732 | type: "error", |
| | 733 | title: "Path unavailable", |
| | 734 | description: "This thread does not have a workspace path to copy.", |
| | 735 | }); |
| | 736 | return; |
| | 737 | } |
| | 738 | copyPathToClipboard(threadWorkspacePath, { path: threadWorkspacePath }); |
| | 739 | return; |
| | 740 | } |
| 711 | if (clicked === "copy-thread-id") { | 741 | if (clicked === "copy-thread-id") { |
| 712 | copyToClipboard(threadId, { threadId }); | 742 | copyThreadIdToClipboard(threadId, { threadId }); |
| 713 | return; | 743 | return; |
| 714 | } | 744 | } |
| 715 | if (clicked !== "delete") return; | 745 | if (clicked !== "delete") return; |
| ... | @@ -726,7 +756,15 @@ export default function Sidebar() { | ... | @@ -726,7 +756,15 @@ export default function Sidebar() { |
| 726 | } | 756 | } |
| 727 | await deleteThread(threadId); | 757 | await deleteThread(threadId); |
| 728 | }, | 758 | }, |
| 729 | [appSettings.confirmThreadDelete, copyToClipboard, deleteThread, markThreadUnread, threads], | 759 | [ |
| | 760 | appSettings.confirmThreadDelete, |
| | 761 | copyPathToClipboard, |
| | 762 | copyThreadIdToClipboard, |
| | 763 | deleteThread, |
| | 764 | markThreadUnread, |
| | 765 | projectCwdById, |
| | 766 | threads, |
| | 767 | ], |
| 730 | ); | 768 | ); |
| 731 | | 769 | |
| 732 | const handleMultiSelectContextMenu = useCallback( | 770 | const handleMultiSelectContextMenu = useCallback( |