Skip to content

Commit 19107e7

Browse files
shreyaskarnikclaudewesm
authored
feat: show copyable session ID in session detail header (#28)
## Summary - Adds a clickable session ID to the session detail breadcrumb bar - Shows first 8 characters of the session ID in a monospace styled element - Hovering shows the full session ID as a tooltip - Clicking copies the full session ID to clipboard with "Copied!" feedback (1.5s) - Extracted a reusable `copyToClipboard` utility with tests This lets users easily grab session IDs for `claude --resume <id>`. ## Demo https://github.com/user-attachments/assets/b7ef65c8-d80a-40c7-8a90-13d5e049c494 ## Tests Ran - [x] `copyToClipboard` utility tested (success + failure paths) - [x] All 383 frontend tests passing - [x] Go tests passing (no backend changes) - [x] Manual: navigate to session, verify truncated ID visible - [x] Manual: hover shows full ID tooltip - [x] Manual: click copies full ID, shows "Copied!" for 1.5s 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: Wes McKinney <wesmckinn+git@gmail.com>
1 parent 32b749a commit 19107e7

3 files changed

Lines changed: 74 additions & 0 deletions

File tree

frontend/src/App.svelte

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,16 @@
1616
import { ui } from "./lib/stores/ui.svelte.js";
1717
import { router } from "./lib/stores/router.svelte.js";
1818
import { registerShortcuts } from "./lib/utils/keyboard.js";
19+
import { copyToClipboard } from "./lib/utils/clipboard.js";
1920
import type { DisplayItem } from "./lib/utils/display-items.js";
2021
22+
let copiedSessionId = $state("");
23+
24+
function sessionDisplayId(id: string): string {
25+
const idx = id.indexOf(":");
26+
return idx >= 0 ? id.slice(idx + 1) : id;
27+
}
28+
2129
let messageListRef:
2230
| {
2331
scrollToOrdinal: (o: number) => void;
@@ -191,6 +199,25 @@
191199
})}
192200
</span>
193201
{/if}
202+
{#if session.agent === "claude" || session.agent === "codex"}
203+
{@const rawId = sessionDisplayId(session.id)}
204+
<button
205+
class="session-id"
206+
title={rawId}
207+
onclick={async () => {
208+
const ok = await copyToClipboard(rawId);
209+
if (ok) {
210+
const id = session.id;
211+
copiedSessionId = id;
212+
setTimeout(() => {
213+
if (copiedSessionId === id) copiedSessionId = "";
214+
}, 1500);
215+
}
216+
}}
217+
>
218+
{copiedSessionId === session.id ? "Copied!" : rawId.slice(0, 8)}
219+
</button>
220+
{/if}
194221
</span>
195222
{/if}
196223
</div>
@@ -295,4 +322,22 @@
295322
white-space: nowrap;
296323
flex-shrink: 0;
297324
}
325+
326+
.session-id {
327+
font-size: 10px;
328+
font-family: "SF Mono", "Menlo", "Consolas", monospace;
329+
color: var(--text-muted);
330+
cursor: pointer;
331+
padding: 1px 5px;
332+
border-radius: 4px;
333+
background: var(--bg-tertiary);
334+
transition: color 0.15s, background 0.15s;
335+
white-space: nowrap;
336+
flex-shrink: 0;
337+
}
338+
339+
.session-id:hover {
340+
color: var(--text-secondary);
341+
background: var(--bg-hover);
342+
}
298343
</style>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, it, expect, vi } from "vitest";
2+
import { copyToClipboard } from "./clipboard.js";
3+
4+
describe("copyToClipboard", () => {
5+
it("copies text and returns true on success", async () => {
6+
const writeText = vi.fn().mockResolvedValue(undefined);
7+
vi.stubGlobal("navigator", { clipboard: { writeText } });
8+
const result = await copyToClipboard("test-id");
9+
expect(writeText).toHaveBeenCalledWith("test-id");
10+
expect(result).toBe(true);
11+
vi.unstubAllGlobals();
12+
});
13+
14+
it("returns false when clipboard write fails", async () => {
15+
const writeText = vi.fn().mockRejectedValue(new Error("denied"));
16+
vi.stubGlobal("navigator", { clipboard: { writeText } });
17+
const result = await copyToClipboard("test-id");
18+
expect(result).toBe(false);
19+
vi.unstubAllGlobals();
20+
});
21+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export async function copyToClipboard(text: string): Promise<boolean> {
2+
try {
3+
await navigator.clipboard.writeText(text);
4+
return true;
5+
} catch {
6+
return false;
7+
}
8+
}

0 commit comments

Comments
 (0)