| ... | ... | @@ -561,7 +561,6 @@ export default function Sidebar() { |
| 561 | 561 | if (!api) return; |
| 562 | 562 | const thread = threads.find((t) => t.id === threadId); |
| 563 | 563 | if (!thread) return; |
| 564 | | |
| 565 | 564 | const threadProject = projects.find((project) => project.id === thread.projectId); |
| 566 | 565 | // When bulk-deleting, exclude the other threads being deleted so |
| 567 | 566 | // getOrphanedWorktreePathForThread correctly detects that no surviving |
| ... | ... | @@ -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 | 668 | onCopy: (ctx) => { |
| 670 | 669 | toastManager.add({ |
| 671 | 670 | type: "success", |
| ... | ... | @@ -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 | 699 | const handleThreadContextMenu = useCallback( |
| 685 | 700 | async (threadId: ThreadId, position: { x: number; y: number }) => { |
| 686 | 701 | const api = readNativeApi(); |
| 687 | 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 | 707 | const clicked = await api.contextMenu.show( |
| 689 | 708 | [ |
| 690 | 709 | { id: "rename", label: "Rename thread" }, |
| 691 | 710 | { id: "mark-unread", label: "Mark unread" }, |
| 711 | { id: "copy-path", label: "Copy Path" }, |
| 692 | 712 | { id: "copy-thread-id", label: "Copy Thread ID" }, |
| 693 | 713 | { id: "delete", label: "Delete", destructive: true }, |
| 694 | 714 | ], |
| 695 | 715 | position, |
| 696 | 716 | ); |
| 697 | | const thread = threads.find((t) => t.id === threadId); |
| 698 | | if (!thread) return; |
| 699 | 717 | |
| 700 | 718 | if (clicked === "rename") { |
| 701 | 719 | setRenamingThreadId(threadId); |
| ... | ... | @@ -708,8 +726,20 @@ export default function Sidebar() { |
| 708 | 726 | markThreadUnread(threadId); |
| 709 | 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 | 741 | if (clicked === "copy-thread-id") { |
| 712 | | copyToClipboard(threadId, { threadId }); |
| 742 | copyThreadIdToClipboard(threadId, { threadId }); |
| 713 | 743 | return; |
| 714 | 744 | } |
| 715 | 745 | if (clicked !== "delete") return; |
| ... | ... | @@ -726,7 +756,15 @@ export default function Sidebar() { |
| 726 | 756 | } |
| 727 | 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 | 770 | const handleMultiSelectContextMenu = useCallback( |