fix(studio): show chat sidebar menu on touch devices (#7297)

* fix(studio): show chat sidebar menu on touch devices

Recents/Pinned chat row actions were hidden until hover, so iPad users
could not open the kebab menu to delete chats. Reveal actions on coarse
pointers using the same pattern as hub model rows.

Fixes #7276

* Fix coarse-pointer sidebar row action visibility (#7276)

Move the touch-device override into index.css after .sidebar-row-action so
it wins the cascade. Arbitrary Tailwind media utilities on the element had
equal specificity and were overridden by the base rule.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Scope coarse-pointer sidebar actions to chat rows (#7276)

Only chat kebabs/unpin buttons that reserve touch padding get
sidebar-touch-reveal, so project/run/nav rows stay hover-revealed.

* Tighten comments

* Reserve full kebab hit area on coarse-pointer unpinned rows

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Daniel Han <danielhanchen@gmail.com>
This commit is contained in:
Souravrajvi0 2026-07-24 07:41:05 +05:30 committed by GitHub
commit e2ccf4d376
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 38 additions and 7 deletions

View file

@ -901,8 +901,8 @@ export function AppSidebar() {
: "group/recent-item relative";
const actionClass =
variant === "project"
? "sidebar-row-action group-hover/project-chat-item:opacity-100 group-hover/project-chat-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto"
: "sidebar-row-action group-hover/recent-item:opacity-100 group-hover/recent-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto";
? "sidebar-row-action sidebar-touch-reveal group-hover/project-chat-item:opacity-100 group-hover/project-chat-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto"
: "sidebar-row-action sidebar-touch-reveal group-hover/recent-item:opacity-100 group-hover/recent-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto";
const buttonClass = cn(
"sidebar-nav-btn h-[33px] cursor-pointer rounded-full pr-4 text-ui-14p5 leading-ui-19 tracking-nav font-medium",
// pl-3 (12px) over the content's pl-1.5 (6px) = 18px, aligning the
@ -912,13 +912,14 @@ export function AppSidebar() {
isPinned && variant !== "project" && "gap-[8.5px]",
variant === "project"
? // Room for the hover pin quick-action plus the kebab.
"group-hover/project-chat-item:pr-14 group-has-[.sidebar-row-action[data-state=open]]/project-chat-item:pr-8"
"group-hover/project-chat-item:pr-14 group-has-[.sidebar-row-action[data-state=open]]/project-chat-item:pr-8 [@media(pointer:coarse)]:pr-14"
: isPinned
? // Pinned rows show an extra unpin button on hover, so reserve more room
// (pr-8 when the menu is open keeps the unpin button clear of the title).
"group-hover/recent-item:pr-16 group-has-[.sidebar-row-action[data-state=open]]/recent-item:pr-8"
"group-hover/recent-item:pr-16 group-has-[.sidebar-row-action[data-state=open]]/recent-item:pr-8 [@media(pointer:coarse)]:pr-16"
: // Hover room for the kebab only; title keeps one more character.
"group-hover/recent-item:pr-6 group-has-[.sidebar-row-action[data-state=open]]/recent-item:pr-6",
// Touch rows clear the full always-visible kebab hit area (pr-10).
"group-hover/recent-item:pr-6 group-has-[.sidebar-row-action[data-state=open]]/recent-item:pr-6 [@media(pointer:coarse)]:pr-10",
);
const isRenamingThis =
@ -987,7 +988,7 @@ export function AppSidebar() {
togglePinnedChat(item.id);
}}
aria-label={isPinned ? "Unpin chat" : "Pin chat"}
className="sidebar-row-action is-unpin-action group-hover/project-chat-item:opacity-100 group-hover/project-chat-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto"
className="sidebar-row-action sidebar-touch-reveal is-unpin-action group-hover/project-chat-item:opacity-100 group-hover/project-chat-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto"
>
<span className="sidebar-row-action-glyph">
<HugeiconsIcon icon={isPinned ? PinOffIcon : PinIcon} strokeWidth={1.75} className="size-icon" />
@ -1002,7 +1003,7 @@ export function AppSidebar() {
togglePinnedChat(item.id);
}}
aria-label="Unpin chat"
className="sidebar-row-action is-unpin-action group-hover/recent-item:opacity-100 group-hover/recent-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto"
className="sidebar-row-action sidebar-touch-reveal is-unpin-action group-hover/recent-item:opacity-100 group-hover/recent-item:pointer-events-auto focus-visible:opacity-100 focus-visible:pointer-events-auto"
>
<span className="sidebar-row-action-glyph">
<HugeiconsIcon icon={PinOffIcon} strokeWidth={1.75} className="size-icon" />

View file

@ -1018,6 +1018,12 @@ html[data-chat-font] .aui-root {
.sidebar-row-action {
@apply absolute top-0 bottom-0 right-0 inline-flex cursor-pointer items-center justify-end pl-2 pr-1.5 opacity-0 pointer-events-none outline-none;
}
@media (pointer: coarse) {
/* Only chat rows reserve touch padding (#7276); other rows stay hover-revealed to avoid clipped labels. */
.sidebar-row-action.sidebar-touch-reveal {
@apply opacity-100 pointer-events-auto;
}
}
.sidebar-row-action[data-state="open"] {
@apply opacity-100 pointer-events-auto;
}

View file

@ -14,6 +14,7 @@ DATA_TAB = FRONTEND / "features/settings/tabs/data-tab.tsx"
PROMPT_STORAGE = FRONTEND / "features/chat/prompt-storage/prompt-storage-dialog.tsx"
APP_SIDEBAR = FRONTEND / "components/app-sidebar.tsx"
INDEX_CSS = FRONTEND / "index.css"
THREAD = FRONTEND / "components/assistant-ui/thread.tsx"
THREAD_SIDEBAR = FRONTEND / "features/chat/thread-sidebar.tsx"
SHARED_COMPOSER = FRONTEND / "features/chat/shared-composer.tsx"
@ -129,3 +130,26 @@ def test_expanded_titlebar_button_and_corner_match_sidebar_edge():
'className="pointer-events-none absolute top-full size-3 -translate-x-px rounded-tl-[12px] border-l border-t border-sidebar-border bg-background"'
in source
)
def test_chat_sidebar_row_actions_visible_on_coarse_pointers():
"""unslothai/unsloth#7276: Recents chat kebab must be tappable on iPad."""
sidebar_source = APP_SIDEBAR.read_text(encoding = "utf-8")
css_source = INDEX_CSS.read_text(encoding = "utf-8")
assert "renderChatSidebarItem" in sidebar_source
block = sidebar_source.split("function renderChatSidebarItem", 1)[1].split("\n function ", 1)[
0
]
assert "[@media(pointer:coarse)]:pr-10" in block
assert "sidebar-touch-reveal" in block
# Coarse-pointer visibility must come after .sidebar-row-action { opacity-0 }.
coarse_idx = css_source.index("@media (pointer: coarse)")
base_idx = css_source.index(".sidebar-row-action {")
assert coarse_idx > base_idx
coarse_block = css_source[coarse_idx : coarse_idx + 280]
assert "sidebar-touch-reveal" in coarse_block
assert "opacity-100" in coarse_block
assert "pointer-events-auto" in coarse_block
# Must not reveal every sidebar-row-action (project/run/nav rows lack padding).
assert ".sidebar-row-action {\n\t\t\t@apply opacity-100" not in coarse_block
assert ".sidebar-row-action.sidebar-touch-reveal" in coarse_block