From e8832bf344de755c4d3a2fcf4d020d7b1a94e9fa Mon Sep 17 00:00:00 2001 From: Lambda Date: Wed, 20 May 2026 15:11:15 +0800 Subject: [PATCH] =?UTF-8?q?refactor(desktop):=20pin=20tab=20=E2=80=94=20dr?= =?UTF-8?q?op=20accent=20left=20border,=20swap=20leading=20icon=20to=20Pin?= =?UTF-8?q?=20(MUL-2449)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Jiayuan reported that the accent left border on pinned tabs reads as a heavy black edge in light mode and looks unrefined. Replace it with a quieter identifier: pinned tabs swap their route icon for a Pin glyph in the leading slot (same size, no extra horizontal space). The hidden X close button stays as the secondary cue. RFC §3 D1v moves from iii FINAL to iv FINAL; iii is demoted to v2 FINAL → v3 REMOVED. Co-authored-by: multica-agent --- .../renderer/src/components/tab-bar.test.tsx | 17 +++++++++++++++++ .../src/renderer/src/components/tab-bar.tsx | 17 ++++++++++------- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/apps/desktop/src/renderer/src/components/tab-bar.test.tsx b/apps/desktop/src/renderer/src/components/tab-bar.test.tsx index 9938af3f7..f1b59d026 100644 --- a/apps/desktop/src/renderer/src/components/tab-bar.test.tsx +++ b/apps/desktop/src/renderer/src/components/tab-bar.test.tsx @@ -131,4 +131,21 @@ describe("TabBar hover action buttons", () => { const pinnedTab = getByLabelText("Issues (pinned)"); expect(within(pinnedTab).getByText("Issues")).toBeTruthy(); }); + + it("renders the Pin glyph as the leading icon on a pinned tab and the route icon on an unpinned tab", () => { + state.byWorkspace.acme.tabs = [ + { id: "tA", path: "/acme/issues", title: "Issues", icon: "ListTodo", pinned: true }, + { id: "tB", path: "/acme/projects", title: "Projects", icon: "ListTodo", pinned: false }, + ]; + const { getByLabelText } = render(); + const pinnedTab = getByLabelText("Issues (pinned)"); + const unpinnedTab = getByLabelText("Projects"); + // lucide-react renders the icon name into the class list. The leading + // slot icon is size-3.5; the hover Pin/Unpin action button is size-2.5, + // so we qualify on size to avoid matching the action glyph. + expect(pinnedTab.querySelector(".lucide-pin.size-3\\.5")).toBeTruthy(); + expect(pinnedTab.querySelector(".lucide-list-todo")).toBeNull(); + expect(unpinnedTab.querySelector(".lucide-list-todo.size-3\\.5")).toBeTruthy(); + expect(unpinnedTab.querySelector(".lucide-pin.size-3\\.5")).toBeNull(); + }); }); diff --git a/apps/desktop/src/renderer/src/components/tab-bar.tsx b/apps/desktop/src/renderer/src/components/tab-bar.tsx index ff033dffc..35f4e5c78 100644 --- a/apps/desktop/src/renderer/src/components/tab-bar.tsx +++ b/apps/desktop/src/renderer/src/components/tab-bar.tsx @@ -84,7 +84,11 @@ function SortableTabItem({ isDragging, } = useSortable({ id: tab.id }); - const Icon = TAB_ICONS[tab.icon]; + // Pinned tabs swap the route icon for a Pin glyph as the static "I am + // pinned" indicator (RFC §3 D1v-iv FINAL). The route information is still + // present in the title, and this avoids a hard left accent border that read + // as visually heavy in light mode. + const LeadingIcon = tab.pinned ? Pin : TAB_ICONS[tab.icon]; const style = { transform: CSS.Transform.toString(transform), @@ -112,10 +116,10 @@ function SortableTabItem({ e.stopPropagation(); }; - // Pinned tabs keep their full title (RFC §3 D1v-ii FINAL). The only weak - // visual differences vs. unpinned tabs are the accent left border and the - // suppressed X (closing requires explicit Unpin). Pin/Unpin is reachable - // via the hover action button below and the right-click menu fallback. + // Pinned tabs keep their full title (RFC §3 D1v-ii FINAL). The only visual + // differences vs. unpinned tabs are the leading Pin icon (swapped in above) + // and the suppressed X (closing requires explicit Unpin). Pin/Unpin is + // reachable via the hover action button below and the right-click menu. const showCloseButton = !tab.pinned && !isOnly; const tabButton = ( @@ -133,11 +137,10 @@ function SortableTabItem({ isActive ? "bg-sidebar-accent font-medium text-sidebar-accent-foreground" : "bg-sidebar-accent/50 text-muted-foreground hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", - tab.pinned && "border-l-2 border-l-primary/60", isDragging && "opacity-60", )} > - {Icon && } + {LeadingIcon && }