authorgravatar for 73941998+UtkarshUsername@users.noreply.github.comUtkarsh Patil <73941998+UtkarshUsername@users.noreply.github.com> 2026-04-14 00:56:41+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-04-13 12:26:41-07:00
logcadd7086462c2db3c92185d4a5488f9ad132cc34
tree3a7889c0f0a774cc27caec9005d7d8cfeccd8c9e
parent5f7becf38688ddbd305f352b6cd9bd57d82c164c
signature Signed by PGP key B5690EEEBB952194

feat: show full thread title in a tooltip when hovering sidebar thread names (#1994)


2 files changed, 45 insertions(+), 2 deletions(-)

apps/web/src/components/ChatView.browser.tsx+30-1
...@@ -57,6 +57,7 @@ vi.mock("../lib/gitStatusState", () => ({...@@ -57,6 +57,7 @@ vi.mock("../lib/gitStatusState", () => ({
57}));57}));
5858
59const THREAD_ID = "thread-browser-test" as ThreadId;59const THREAD_ID = "thread-browser-test" as ThreadId;
60const THREAD_TITLE = "Browser test thread";
60const ARCHIVED_SECONDARY_THREAD_ID = "thread-secondary-project-archived" as ThreadId;61const ARCHIVED_SECONDARY_THREAD_ID = "thread-secondary-project-archived" as ThreadId;
61const PROJECT_ID = "project-1" as ProjectId;62const PROJECT_ID = "project-1" as ProjectId;
62const SECOND_PROJECT_ID = "project-2" as ProjectId;63const SECOND_PROJECT_ID = "project-2" as ProjectId;
...@@ -288,7 +289,7 @@ function createSnapshotForTargetUser(options: {...@@ -288,7 +289,7 @@ function createSnapshotForTargetUser(options: {
288 {289 {
289 id: THREAD_ID,290 id: THREAD_ID,
290 projectId: PROJECT_ID,291 projectId: PROJECT_ID,
291 title: "Browser test thread",292 title: THREAD_TITLE,
292 modelSelection: {293 modelSelection: {
293 provider: "codex",294 provider: "codex",
294 model: "gpt-5",295 model: "gpt-5",
...@@ -3413,6 +3414,34 @@ describe("ChatView timeline estimator parity (full app)", () => {...@@ -3413,6 +3414,34 @@ describe("ChatView timeline estimator parity (full app)", () => {
3413 }3414 }
3414 });3415 });
34153416
3417 it("exposes the full thread title on the sidebar row tooltip", async () => {
3418 const mounted = await mountChatView({
3419 viewport: DEFAULT_VIEWPORT,
3420 snapshot: createSnapshotForTargetUser({
3421 targetMessageId: "msg-user-thread-tooltip-target" as MessageId,
3422 targetText: "thread tooltip target",
3423 }),
3424 });
3425
3426 try {
3427 const threadTitle = page.getByTestId(`thread-title-${THREAD_ID}`);
3428
3429 await expect.element(threadTitle).toBeInTheDocument();
3430 await threadTitle.hover();
3431
3432 await vi.waitFor(
3433 () => {
3434 const tooltip = document.querySelector<HTMLElement>('[data-slot="tooltip-popup"]');
3435 expect(tooltip).not.toBeNull();
3436 expect(tooltip?.textContent).toContain(THREAD_TITLE);
3437 },
3438 { timeout: 8_000, interval: 16 },
3439 );
3440 } finally {
3441 await mounted.cleanup();
3442 }
3443 });
3444
3416 it("shows the confirm archive action after clicking the archive button", async () => {3445 it("shows the confirm archive action after clicking the archive button", async () => {
3417 localStorage.setItem(3446 localStorage.setItem(
3418 "t3code:client-settings:v1",3447 "t3code:client-settings:v1",
apps/web/src/components/Sidebar.tsx+15-1
...@@ -647,7 +647,21 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP...@@ -647,7 +647,21 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: SidebarThreadRowP
647 onClick={handleRenameInputClick}647 onClick={handleRenameInputClick}
648 />648 />
649 ) : (649 ) : (
650 <span className="min-w-0 flex-1 truncate text-xs">{thread.title}</span>650 <Tooltip>
651 <TooltipTrigger
652 render={
653 <span
654 className="min-w-0 flex-1 truncate text-xs"
655 data-testid={`thread-title-${thread.id}`}
656 >
657 {thread.title}
658 </span>
659 }
660 />
661 <TooltipPopup side="top" className="max-w-80 whitespace-normal leading-tight">
662 {thread.title}
663 </TooltipPopup>
664 </Tooltip>
651 )}665 )}
652 </div>666 </div>
653 <div className="ml-auto flex shrink-0 items-center gap-1.5">667 <div className="ml-auto flex shrink-0 items-center gap-1.5">