Skip to content

Commit 65b005f

Browse files
feat(web): add Copy Thread ID to the sidebar and chat header thread context menu (#5574)
Co-authored-by: shivam <91240327+shivamhwp@users.noreply.github.com>
1 parent 752acbf commit 65b005f

4 files changed

Lines changed: 37 additions & 3 deletions

File tree

apps/web/src/components/Sidebar.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
scopeThreadRef,
3030
scopedThreadKey,
3131
} from "@t3tools/client-runtime/environment";
32-
import type { ScopedThreadRef } from "@t3tools/contracts";
32+
import type { ScopedThreadRef, ThreadId } from "@t3tools/contracts";
3333
import type { TimestampFormat } from "@t3tools/contracts/settings";
3434
import {
3535
AlarmClockIcon,
@@ -1647,6 +1647,24 @@ export default function Sidebar() {
16471647
);
16481648
},
16491649
});
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+
});
16501668
const [projectScopeMenuOpen, setProjectScopeMenuOpen] = useState(false);
16511669
const newThreadContext = useHandleNewThread();
16521670
const openAddProjectCommandPalette = useCallback(
@@ -3037,6 +3055,9 @@ export default function Sidebar() {
30373055
copyBranchToClipboard(thread.branch, { branch: thread.branch });
30383056
}
30393057
return;
3058+
case "copy-thread-id":
3059+
copyThreadIdToClipboard(thread.id, { threadId: thread.id });
3060+
return;
30403061
case "delete": {
30413062
if (confirmThreadDelete) {
30423063
const confirmed = await settlePromise(() =>
@@ -3079,6 +3100,7 @@ export default function Sidebar() {
30793100
confirmThreadDelete,
30803101
copyBranchToClipboard,
30813102
copyPathToClipboard,
3103+
copyThreadIdToClipboard,
30823104
deleteThread,
30833105
handleMultiSelectContextMenu,
30843106
markThreadUnread,

apps/web/src/components/threadActionMenu.logic.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe("buildThreadActionMenuItems", () => {
2626
...baseState,
2727
supports: { settlement: false, snooze: false, pinning: false, titleRegeneration: false },
2828
}),
29-
).toEqual(["rename", "mark-unread", "copy-path", "delete"]);
29+
).toEqual(["rename", "mark-unread", "copy-path", "copy-thread-id", "delete"]);
3030
});
3131

3232
it("includes branch items only for threads with a branch", () => {

apps/web/src/components/threadActionMenu.logic.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type ThreadActionMenuId =
2020
| "mark-unread"
2121
| "copy-path"
2222
| "copy-branch"
23+
| "copy-thread-id"
2324
| "delete";
2425

2526
export interface ThreadActionMenuState {
@@ -100,6 +101,7 @@ export function buildThreadActionMenuItems(
100101
{ id: "mark-unread", label: "Mark unread" },
101102
{ id: "copy-path", label: "Copy path", icon: "copy" },
102103
...(state.branch ? [{ id: "copy-branch" as const, label: "Copy branch", icon: "copy" }] : []),
104+
{ id: "copy-thread-id", label: "Copy thread ID", icon: "copy" },
103105
{ id: "delete", label: "Delete", destructive: true, icon: "trash" },
104106
];
105107
}

apps/web/src/hooks/useThreadActionMenu.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
effectiveSnoozed,
1212
type ChangeRequestStateLike,
1313
} from "@t3tools/client-runtime/state/thread-settled";
14-
import type { ScopedThreadRef } from "@t3tools/contracts";
14+
import type { ScopedThreadRef, ThreadId } from "@t3tools/contracts";
1515
import { useCallback } from "react";
1616

1717
import { resolveSnoozePresets, snoozeWakeDescription } from "../components/Sidebar.snooze";
@@ -95,6 +95,12 @@ export function useThreadActionMenu(input: {
9595
},
9696
onError: (error) => failureToast("Failed to copy branch", error),
9797
});
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+
});
98104

99105
const openMenu = useCallback(
100106
(position: { x: number; y: number }) => {
@@ -242,6 +248,9 @@ export function useThreadActionMenu(input: {
242248
copyBranchToClipboard(thread.branch, { branch: thread.branch });
243249
}
244250
return;
251+
case "copy-thread-id":
252+
copyThreadIdToClipboard(thread.id, { threadId: thread.id });
253+
return;
245254
case "delete": {
246255
if (confirmThreadDelete) {
247256
const confirmed = await settlePromise(() =>
@@ -279,6 +288,7 @@ export function useThreadActionMenu(input: {
279288
confirmThreadDelete,
280289
copyBranchToClipboard,
281290
copyPathToClipboard,
291+
copyThreadIdToClipboard,
282292
deleteThread,
283293
handleNewThread,
284294
markThreadUnread,

0 commit comments

Comments
 (0)