From 8c679db1dc68aa2554ef79500c64ffc09db2f1af Mon Sep 17 00:00:00 2001 From: tangweiyuan <1343053743@qq.com> Date: Wed, 18 Mar 2026 16:07:20 +0800 Subject: [PATCH] fix(webchat): slash menu clipped by overflow and missing keyboard scroll The slash command menu renders with position: absolute; bottom: 100% inside .agent-chat__input which has overflow: hidden, causing the popup to be completely invisible. Introduce .agent-chat__input-wrap as a positioning parent so the menu escapes the clipped container. Also add scrollIntoView({ block: nearest }) after ArrowUp/ArrowDown navigation so the active item auto-scrolls into view, scoped to the component .agent-chat__input-wrap to avoid cross-panel matches. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/styles/chat/layout.css | 8 ++++- ui/src/styles/layout.mobile.css | 2 +- ui/src/ui/views/chat.ts | 64 ++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css index 8c68a2327b4..437d5f9c998 100644 --- a/ui/src/styles/chat/layout.css +++ b/ui/src/styles/chat/layout.css @@ -363,11 +363,17 @@ box-sizing: border-box; } +.agent-chat__input-wrap { + position: relative; + margin: 0 18px 14px; + flex-shrink: 0; +} + .agent-chat__input { position: relative; display: flex; flex-direction: column; - margin: 0 18px 14px; + margin: 0; padding: 0; background: var(--card); border: 1px solid var(--border); diff --git a/ui/src/styles/layout.mobile.css b/ui/src/styles/layout.mobile.css index 6d943253804..726ed2b2c6f 100644 --- a/ui/src/styles/layout.mobile.css +++ b/ui/src/styles/layout.mobile.css @@ -513,7 +513,7 @@ font-size: 14px; } - .agent-chat__input { + .agent-chat__input-wrap { margin: 0 8px 10px; } diff --git a/ui/src/ui/views/chat.ts b/ui/src/ui/views/chat.ts index 88a712706f0..1c4603b1ed1 100644 --- a/ui/src/ui/views/chat.ts +++ b/ui/src/ui/views/chat.ts @@ -952,6 +952,16 @@ export function renderChat(props: ChatProps) { `; + /** After arrow-key navigation, scroll the active slash-menu item into view. */ + const scrollSlashMenuActiveIntoView = () => { + requestAnimationFrame(() => { + const active = document.querySelector(".agent-chat__input-wrap .slash-menu-item--active"); + if (active) { + active.scrollIntoView({ block: "nearest" }); + } + }); + }; + const handleKeyDown = (e: KeyboardEvent) => { // Slash menu navigation — arg mode if (vs.slashMenuOpen && vs.slashMenuMode === "args" && vs.slashMenuArgItems.length > 0) { @@ -961,11 +971,13 @@ export function renderChat(props: ChatProps) { e.preventDefault(); vs.slashMenuIndex = (vs.slashMenuIndex + 1) % len; requestUpdate(); + scrollSlashMenuActiveIntoView(); return; case "ArrowUp": e.preventDefault(); vs.slashMenuIndex = (vs.slashMenuIndex - 1 + len) % len; requestUpdate(); + scrollSlashMenuActiveIntoView(); return; case "Tab": e.preventDefault(); @@ -992,11 +1004,13 @@ export function renderChat(props: ChatProps) { e.preventDefault(); vs.slashMenuIndex = (vs.slashMenuIndex + 1) % len; requestUpdate(); + scrollSlashMenuActiveIntoView(); return; case "ArrowUp": e.preventDefault(); vs.slashMenuIndex = (vs.slashMenuIndex - 1 + len) % len; requestUpdate(); + scrollSlashMenuActiveIntoView(); return; case "Tab": e.preventDefault(); @@ -1182,34 +1196,35 @@ export function renderChat(props: ChatProps) { } -
+
${renderSlashMenu(requestUpdate, props)} - ${renderAttachmentPreview(props)} +
+ ${renderAttachmentPreview(props)} - handleFileSelect(e, props)} - /> + handleFileSelect(e, props)} + /> - ${vs.sttRecording && vs.sttInterimText ? html`
${vs.sttInterimText}
` : nothing} + ${vs.sttRecording && vs.sttInterimText ? html`
${vs.sttInterimText}
` : nothing} - + -
-
+
+
- +
+ `; }