fix(webui): assign readable session handles

This commit is contained in:
chengyongru
2026-08-19 01:15:56 +08:00
committed by chengyongru
parent 251a1ccd40
commit 9e046815bc
20 changed files with 637 additions and 123 deletions
+11 -4
View File
@@ -7,7 +7,7 @@ import {
useRef,
useState,
} from "react";
import type { MouseEvent as ReactMouseEvent, ReactElement } from "react";
import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactElement } from "react";
import {
Archive,
ArchiveRestore,
@@ -50,7 +50,6 @@ import {
} from "@/components/ui/tooltip";
import { MAX_WORKBENCH_PANES } from "@/components/workbench/workbench-model";
import { SIDEBAR_SELECTION_ITEM_CLASS } from "@/components/SidebarSelectionHighlight";
import { SessionHandleLabel } from "@/components/SessionHandleLabel";
import { deriveTitle, relativeTime, visibleSessionPreview } from "@/lib/format";
import {
COLLAPSED_CHATS_VISIBLE_COUNT,
@@ -131,11 +130,19 @@ function SidebarSessionHandle({ handle }: { handle: ChatSummary["handle"] }) {
if (!handle) return null;
return (
<span
data-sidebar-session-handle
className="flex max-w-20 shrink-0 items-center overflow-hidden whitespace-nowrap text-[11px] font-medium leading-5"
>
<SessionHandleLabel id={handle.id}>
<span
data-sidebar-session-handle-underline
className="inline border-b-2 text-foreground"
style={{
"--sidebar-session-handle-color": sessionHandleColor(handle.id),
borderBottomColor: "var(--sidebar-session-handle-color)",
} as CSSProperties}
>
@{handle.name}
</SessionHandleLabel>
</span>
</span>
);
}
+85
View File
@@ -2,6 +2,7 @@ import { fireEvent, render, screen, within } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { ChatList } from "@/components/ChatList";
import { sessionHandleColor } from "@/lib/session-handle";
import { readDraggedSession, SESSION_DRAG_TYPE } from "@/lib/session-drag";
import type { ChatSummary } from "@/lib/types";
@@ -66,6 +67,90 @@ describe("ChatList", () => {
expect(onTogglePin).toHaveBeenCalledWith("websocket:review");
});
it("restores the colored handle underline and animated active track", () => {
render(
<ChatList
sessions={[session({
chatId: "review",
title: "Review the patch",
handle: { id: "handle_1234", name: "mira" },
})]}
activeKey="websocket:review"
onSelect={vi.fn()}
onRequestDelete={vi.fn()}
onTogglePin={vi.fn()}
onRequestRename={vi.fn()}
onToggleArchive={vi.fn()}
/>,
);
const conversation = screen.getByRole("button", {
name: "@mira Review the patch",
});
const handle = conversation.querySelector("[data-sidebar-session-handle]");
expect(handle).toHaveClass("max-w-20", "shrink-0");
const underline = handle?.querySelector("[data-sidebar-session-handle-underline]");
expect(underline).toHaveClass("border-b-2", "text-foreground");
expect(underline?.getAttribute("style"))
.toContain(sessionHandleColor("handle_1234"));
const activeTrack = conversation.querySelector("[data-sidebar-selection-track]");
expect(activeTrack).toHaveClass(
"origin-left",
"scale-x-100",
"transition-transform",
"motion-reduce:transition-none",
);
});
it("keeps handle columns intact inside grouped panes", () => {
render(
<ChatList
sessions={[session({
key: "tab:group",
chatId: "workbench-tab:group",
title: "Grouped work",
})]}
activeKey="websocket:root"
paneGroups={{
"tab:group": {
tabKey: "tab:group",
title: "Grouped work",
activePaneKey: "websocket:root",
visible: true,
panes: [
{
key: "websocket:root",
chatId: "root",
title: "Short",
handle: { id: "handle_1234", name: "mira" },
},
{
key: "websocket:child",
chatId: "child",
title: "A much longer conversation title",
handle: { id: "handle_5678", name: "nora" },
},
],
},
}}
onSelect={vi.fn()}
onRequestDelete={vi.fn()}
onTogglePin={vi.fn()}
onRequestRename={vi.fn()}
onToggleArchive={vi.fn()}
/>,
);
expect(screen.getByRole("button", { name: "@mira Short" }))
.toHaveTextContent("@mira");
expect(screen.getByRole("button", { name: "@nora A much longer conversation title" }))
.toHaveTextContent("@nora");
for (const handle of document.querySelectorAll("[data-sidebar-session-handle]")) {
expect(handle).toHaveClass("max-w-20", "shrink-0");
}
});
it("keeps tab grouping out of drag protocols while exposing inactive panes as mention sources", () => {
render(
<ChatList