| author | |
| committer | |
| log | 65b005f1e4bfccb6a404b3b1e5bfa363d534ac2a |
| tree | 98f4a16a8df3dc1eaffa53eb8c4ba80a3cad906f |
| parent | 752acbf6549e6dd422ac8d8ed11c0fa02577965b |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>4 files changed, 37 insertions(+), 3 deletions(-)
apps/web/src/components/Sidebar.tsx+23-1| ... | @@ -29,7 +29,7 @@ import { | ... | @@ -29,7 +29,7 @@ import { |
| 29 | scopeThreadRef, | 29 | scopeThreadRef, |
| 30 | scopedThreadKey, | 30 | scopedThreadKey, |
| 31 | } from "@t3tools/client-runtime/environment"; | 31 | } from "@t3tools/client-runtime/environment"; |
| 32 | import type { ScopedThreadRef } from "@t3tools/contracts"; | 32 | import type { ScopedThreadRef, ThreadId } from "@t3tools/contracts"; |
| 33 | import type { TimestampFormat } from "@t3tools/contracts/settings"; | 33 | import type { TimestampFormat } from "@t3tools/contracts/settings"; |
| 34 | import { | 34 | import { |
| 35 | AlarmClockIcon, | 35 | AlarmClockIcon, |
| ... | @@ -1647,6 +1647,24 @@ export default function Sidebar() { | ... | @@ -1647,6 +1647,24 @@ export default function Sidebar() { |
| 1647 | ); | 1647 | ); |
| 1648 | }, | 1648 | }, |
| 1649 | }); | 1649 | }); |
| 1650 | const { copyToClipboard: copyThreadIdToClipboard } = useCopyToClipboard<{ threadId: ThreadId }>({ | ||
| 1651 | onCopy: ({ threadId }) => { | ||
| 1652 | toastManager.add({ | ||
| 1653 | type: "success", | ||
| 1654 | title: "Thread ID copied", | ||
| 1655 | description: threadId, | ||
| 1656 | }); | ||
| 1657 | }, | ||
| 1658 | onError: (error) => { | ||
| 1659 | toastManager.add( | ||
| 1660 | stackedThreadToast({ | ||
| 1661 | type: "error", | ||
| 1662 | title: "Failed to copy thread ID", | ||
| 1663 | description: error instanceof Error ? error.message : "An error occurred.", | ||
| 1664 | }), | ||
| 1665 | ); | ||
| 1666 | }, | ||
| 1667 | }); | ||
| 1650 | const [projectScopeMenuOpen, setProjectScopeMenuOpen] = useState(false); | 1668 | const [projectScopeMenuOpen, setProjectScopeMenuOpen] = useState(false); |
| 1651 | const newThreadContext = useHandleNewThread(); | 1669 | const newThreadContext = useHandleNewThread(); |
| 1652 | const openAddProjectCommandPalette = useCallback( | 1670 | const openAddProjectCommandPalette = useCallback( |
| ... | @@ -3037,6 +3055,9 @@ export default function Sidebar() { | ... | @@ -3037,6 +3055,9 @@ export default function Sidebar() { |
| 3037 | copyBranchToClipboard(thread.branch, { branch: thread.branch }); | 3055 | copyBranchToClipboard(thread.branch, { branch: thread.branch }); |
| 3038 | } | 3056 | } |
| 3039 | return; | 3057 | return; |
| 3058 | case "copy-thread-id": | ||
| 3059 | copyThreadIdToClipboard(thread.id, { threadId: thread.id }); | ||
| 3060 | return; | ||
| 3040 | case "delete": { | 3061 | case "delete": { |
| 3041 | if (confirmThreadDelete) { | 3062 | if (confirmThreadDelete) { |
| 3042 | const confirmed = await settlePromise(() => | 3063 | const confirmed = await settlePromise(() => |
| ... | @@ -3079,6 +3100,7 @@ export default function Sidebar() { | ... | @@ -3079,6 +3100,7 @@ export default function Sidebar() { |
| 3079 | confirmThreadDelete, | 3100 | confirmThreadDelete, |
| 3080 | copyBranchToClipboard, | 3101 | copyBranchToClipboard, |
| 3081 | copyPathToClipboard, | 3102 | copyPathToClipboard, |
| 3103 | copyThreadIdToClipboard, | ||
| 3082 | deleteThread, | 3104 | deleteThread, |
| 3083 | handleMultiSelectContextMenu, | 3105 | handleMultiSelectContextMenu, |
| 3084 | markThreadUnread, | 3106 | markThreadUnread, |
apps/web/src/components/threadActionMenu.logic.test.ts+1-1| ... | @@ -26,7 +26,7 @@ describe("buildThreadActionMenuItems", () => { | ... | @@ -26,7 +26,7 @@ describe("buildThreadActionMenuItems", () => { |
| 26 | ...baseState, | 26 | ...baseState, |
| 27 | supports: { settlement: false, snooze: false, pinning: false, titleRegeneration: false }, | 27 | supports: { settlement: false, snooze: false, pinning: false, titleRegeneration: false }, |
| 28 | }), | 28 | }), |
| 29 | ).toEqual(["rename", "mark-unread", "copy-path", "delete"]); | 29 | ).toEqual(["rename", "mark-unread", "copy-path", "copy-thread-id", "delete"]); |
| 30 | }); | 30 | }); |
| 31 | 31 | ||
| 32 | it("includes branch items only for threads with a branch", () => { | 32 | it("includes branch items only for threads with a branch", () => { |
apps/web/src/components/threadActionMenu.logic.ts+2| ... | @@ -20,6 +20,7 @@ export type ThreadActionMenuId = | ... | @@ -20,6 +20,7 @@ export type ThreadActionMenuId = |
| 20 | | "mark-unread" | 20 | | "mark-unread" |
| 21 | | "copy-path" | 21 | | "copy-path" |
| 22 | | "copy-branch" | 22 | | "copy-branch" |
| 23 | | "copy-thread-id" | ||
| 23 | | "delete"; | 24 | | "delete"; |
| 24 | 25 | ||
| 25 | export interface ThreadActionMenuState { | 26 | export interface ThreadActionMenuState { |
| ... | @@ -100,6 +101,7 @@ export function buildThreadActionMenuItems( | ... | @@ -100,6 +101,7 @@ export function buildThreadActionMenuItems( |
| 100 | { id: "mark-unread", label: "Mark unread" }, | 101 | { id: "mark-unread", label: "Mark unread" }, |
| 101 | { id: "copy-path", label: "Copy path", icon: "copy" }, | 102 | { id: "copy-path", label: "Copy path", icon: "copy" }, |
| 102 | ...(state.branch ? [{ id: "copy-branch" as const, label: "Copy branch", icon: "copy" }] : []), | 103 | ...(state.branch ? [{ id: "copy-branch" as const, label: "Copy branch", icon: "copy" }] : []), |
| 104 | { id: "copy-thread-id", label: "Copy thread ID", icon: "copy" }, | ||
| 103 | { id: "delete", label: "Delete", destructive: true, icon: "trash" }, | 105 | { id: "delete", label: "Delete", destructive: true, icon: "trash" }, |
| 104 | ]; | 106 | ]; |
| 105 | } | 107 | } |
apps/web/src/hooks/useThreadActionMenu.ts+11-1| ... | @@ -11,7 +11,7 @@ import { | ... | @@ -11,7 +11,7 @@ import { |
| 11 | effectiveSnoozed, | 11 | effectiveSnoozed, |
| 12 | type ChangeRequestStateLike, | 12 | type ChangeRequestStateLike, |
| 13 | } from "@t3tools/client-runtime/state/thread-settled"; | 13 | } from "@t3tools/client-runtime/state/thread-settled"; |
| 14 | import type { ScopedThreadRef } from "@t3tools/contracts"; | 14 | import type { ScopedThreadRef, ThreadId } from "@t3tools/contracts"; |
| 15 | import { useCallback } from "react"; | 15 | import { useCallback } from "react"; |
| 16 | 16 | ||
| 17 | import { resolveSnoozePresets, snoozeWakeDescription } from "../components/Sidebar.snooze"; | 17 | import { resolveSnoozePresets, snoozeWakeDescription } from "../components/Sidebar.snooze"; |
| ... | @@ -95,6 +95,12 @@ export function useThreadActionMenu(input: { | ... | @@ -95,6 +95,12 @@ export function useThreadActionMenu(input: { |
| 95 | }, | 95 | }, |
| 96 | onError: (error) => failureToast("Failed to copy branch", error), | 96 | onError: (error) => failureToast("Failed to copy branch", error), |
| 97 | }); | 97 | }); |
| 98 | const { copyToClipboard: copyThreadIdToClipboard } = useCopyToClipboard<{ threadId: ThreadId }>({ | ||
| 99 | onCopy: ({ threadId }) => { | ||
| 100 | toastManager.add({ type: "success", title: "Thread ID copied", description: threadId }); | ||
| 101 | }, | ||
| 102 | onError: (error) => failureToast("Failed to copy thread ID", error), | ||
| 103 | }); | ||
| 98 | 104 | ||
| 99 | const openMenu = useCallback( | 105 | const openMenu = useCallback( |
| 100 | (position: { x: number; y: number }) => { | 106 | (position: { x: number; y: number }) => { |
| ... | @@ -242,6 +248,9 @@ export function useThreadActionMenu(input: { | ... | @@ -242,6 +248,9 @@ export function useThreadActionMenu(input: { |
| 242 | copyBranchToClipboard(thread.branch, { branch: thread.branch }); | 248 | copyBranchToClipboard(thread.branch, { branch: thread.branch }); |
| 243 | } | 249 | } |
| 244 | return; | 250 | return; |
| 251 | case "copy-thread-id": | ||
| 252 | copyThreadIdToClipboard(thread.id, { threadId: thread.id }); | ||
| 253 | return; | ||
| 245 | case "delete": { | 254 | case "delete": { |
| 246 | if (confirmThreadDelete) { | 255 | if (confirmThreadDelete) { |
| 247 | const confirmed = await settlePromise(() => | 256 | const confirmed = await settlePromise(() => |
| ... | @@ -279,6 +288,7 @@ export function useThreadActionMenu(input: { | ... | @@ -279,6 +288,7 @@ export function useThreadActionMenu(input: { |
| 279 | confirmThreadDelete, | 288 | confirmThreadDelete, |
| 280 | copyBranchToClipboard, | 289 | copyBranchToClipboard, |
| 281 | copyPathToClipboard, | 290 | copyPathToClipboard, |
| 291 | copyThreadIdToClipboard, | ||
| 282 | deleteThread, | 292 | deleteThread, |
| 283 | handleNewThread, | 293 | handleNewThread, |
| 284 | markThreadUnread, | 294 | markThreadUnread, |