diff --git a/ui/src/i18n/.i18n/raw-copy-baseline.json b/ui/src/i18n/.i18n/raw-copy-baseline.json index 1217edbf61ce..64b99621d445 100644 --- a/ui/src/i18n/.i18n/raw-copy-baseline.json +++ b/ui/src/i18n/.i18n/raw-copy-baseline.json @@ -449,48 +449,6 @@ "path": "ui/src/pages/channels/view.nostr.ts", "text": "NIP-05" }, - { - "count": 1, - "kind": "html-text", - "name": "text", - "path": "ui/src/pages/chat/components/chat-composer-skill-menu.ts", - "text": "Enter" - }, - { - "count": 1, - "kind": "html-text", - "name": "text", - "path": "ui/src/pages/chat/components/chat-composer-skill-menu.ts", - "text": "Esc" - }, - { - "count": 1, - "kind": "html-text", - "name": "text", - "path": "ui/src/pages/chat/components/chat-composer-skill-menu.ts", - "text": "Tab" - }, - { - "count": 2, - "kind": "html-text", - "name": "text", - "path": "ui/src/pages/chat/components/chat-composer-slash-menu.ts", - "text": "Enter" - }, - { - "count": 2, - "kind": "html-text", - "name": "text", - "path": "ui/src/pages/chat/components/chat-composer-slash-menu.ts", - "text": "Esc" - }, - { - "count": 2, - "kind": "html-text", - "name": "text", - "path": "ui/src/pages/chat/components/chat-composer-slash-menu.ts", - "text": "Tab" - }, { "count": 2, "kind": "html-text", diff --git a/ui/src/i18n/locales/en.ts b/ui/src/i18n/locales/en.ts index 4d00e6001a8a..2cdb29a7cbe8 100644 --- a/ui/src/i18n/locales/en.ts +++ b/ui/src/i18n/locales/en.ts @@ -4789,15 +4789,7 @@ export const en: TranslationMap = { commands: { arguments: "Command arguments", menu: "Slash commands", - instant: "instant", optionCount: "{count} options", - showMoreOne: "Show 1 more command", - showMoreMany: "Show {count} more commands", - navigate: "navigate", - fill: "fill", - run: "run", - select: "select", - close: "close", clearDescription: "Clear chat history", redirectDescription: "Abort and restart with a new message", steerDescription: "Inject a message into the active run", diff --git a/ui/src/lib/chat/commands.ts b/ui/src/lib/chat/commands.ts index fa40a03a85ac..021e7eca99f0 100644 --- a/ui/src/lib/chat/commands.ts +++ b/ui/src/lib/chat/commands.ts @@ -531,11 +531,6 @@ export function getSkillCommandCompletions(filter: string): SlashCommandDef[] { .toSorted((left, right) => left.name.localeCompare(right.name)); } -/** Count of commands hidden by tier filtering (for "Show N more" UI). */ -export function getHiddenCommandCount(): number { - return SLASH_COMMANDS.filter((cmd) => (cmd.tier ?? "standard") === "power").length; -} - type ParsedSlashCommand = { command: SlashCommandDef; args: string; diff --git a/ui/src/pages/chat/chat-responsive.browser.test.ts b/ui/src/pages/chat/chat-responsive.browser.test.ts index 0e203443c00a..5fc5cda88421 100644 --- a/ui/src/pages/chat/chat-responsive.browser.test.ts +++ b/ui/src/pages/chat/chat-responsive.browser.test.ts @@ -3078,20 +3078,20 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => { it("scrolls the keyboard-active slash option into view in short landscape", async () => { const initiallyHidden = await page.evaluate(() => { - const menu = document.querySelector(".slash-menu"); + const scrollRegion = document.querySelector(".slash-menu__scroll"); const options = Array.from( document.querySelectorAll(".slash-menu-item[role='option']"), ); const hiddenOption = options.find((option) => { - const menuRect = menu?.getBoundingClientRect(); + const menuRect = scrollRegion?.getBoundingClientRect(); const optionRect = option.getBoundingClientRect(); return Boolean(menuRect && optionRect.bottom > menuRect.bottom + 1); }); - if (!menu || !hiddenOption) { + if (!scrollRegion || !hiddenOption) { throw new Error("Expected an initially hidden slash option"); } - menu.scrollTop = 0; - const menuRect = menu.getBoundingClientRect(); + scrollRegion.scrollTop = 0; + const menuRect = scrollRegion.getBoundingClientRect(); const itemRect = hiddenOption.getBoundingClientRect(); return { id: hiddenOption.id, @@ -3112,11 +3112,11 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => { }, initiallyHidden.id); await page.waitForFunction((expectedId) => { const active = document.getElementById(expectedId); - const menu = active?.closest(".slash-menu"); - if (!active || !menu) { + const scrollRegion = active?.closest(".slash-menu__scroll"); + if (!active || !scrollRegion) { return false; } - const menuRect = menu.getBoundingClientRect(); + const menuRect = scrollRegion.getBoundingClientRect(); const activeRect = active.getBoundingClientRect(); return activeRect.top >= menuRect.top - 1 && activeRect.bottom <= menuRect.bottom + 1; }, initiallyHidden.id); @@ -3125,17 +3125,17 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => { const input = document.querySelector( ".agent-chat__composer-combobox > textarea", ); - const menu = document.querySelector(".slash-menu"); + const scrollRegion = document.querySelector(".slash-menu__scroll"); const active = document.querySelector(".slash-menu-item--active"); - if (!input || !menu || !active) { + if (!input || !scrollRegion || !active) { throw new Error("Expected active slash option after keyboard navigation"); } - const menuRect = menu.getBoundingClientRect(); + const menuRect = scrollRegion.getBoundingClientRect(); const activeRect = active.getBoundingClientRect(); return { activeDescendant: input.getAttribute("aria-activedescendant"), focusedTag: document.activeElement?.tagName, - scrollTop: menu.scrollTop, + scrollTop: scrollRegion.scrollTop, visible: activeRect.top >= menuRect.top - 1 && activeRect.bottom <= menuRect.bottom + 1, }; }); @@ -3147,6 +3147,52 @@ describeBrowserLayout.concurrent("chat responsive browser layout", () => { }); }); + it("keeps overflowing skill suggestions on the nested scroll viewport", async () => { + const page = await openBrowserPage(568, 320); + try { + const items = Array.from({ length: 16 }, (_, index) => { + const active = index === 15 ? " slash-menu-item--active" : ""; + return `
+ + ${iconSvg()} + $skill_${index + 1} + +
`; + }).join(""); + await page.setContent(` +
+
${items}
+
+ `); + + const result = await page.evaluate(() => { + const active = document.querySelector(".slash-menu-item--active"); + const scrollRegion = active?.closest(".slash-menu__scroll"); + if (!active || !scrollRegion) { + throw new Error("Expected an active skill inside the nested viewport"); + } + const viewport = scrollRegion.getBoundingClientRect(); + const option = active.getBoundingClientRect(); + scrollRegion.scrollTop += option.bottom - viewport.bottom; + const settledOption = active.getBoundingClientRect(); + const settledViewport = scrollRegion.getBoundingClientRect(); + return { + outerScrollTop: active.closest(".skill-menu")?.scrollTop, + scrollTop: scrollRegion.scrollTop, + visible: + settledOption.top >= settledViewport.top - 1 && + settledOption.bottom <= settledViewport.bottom + 1, + }; + }); + + expect(result.outerScrollTop).toBe(0); + expect(result.scrollTop).toBeGreaterThan(0); + expect(result.visible).toBe(true); + } finally { + await closeBrowserPage(page); + } + }); + it("uses the compact mobile grid when the agent filter is not rendered", async () => { const page = await openFixture(320, 568, { singleAgent: true }); try { diff --git a/ui/src/pages/chat/chat-view.test.ts b/ui/src/pages/chat/chat-view.test.ts index 3627a548202f..27934d1638b1 100644 --- a/ui/src/pages/chat/chat-view.test.ts +++ b/ui/src/pages/chat/chat-view.test.ts @@ -3686,6 +3686,65 @@ describe("chat slash menu accessibility", () => { expect(container.querySelector(".skill-menu")).toBeNull(); }); + it("scrolls the keyboard-active skill inside the nested menu viewport", () => { + replaceSkillCommands( + ...Array.from({ length: 8 }, (_, index) => ({ + key: `skill_${index + 1}`, + description: `Skill ${index + 1}.`, + })), + ); + const animationFrames: FrameRequestCallback[] = []; + vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => { + animationFrames.push(callback); + return animationFrames.length; + }); + vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockImplementation( + function (this: HTMLElement) { + const height = 28; + let top = 0; + if (this.classList.contains("slash-menu-item")) { + const scrollRegion = this.closest(".slash-menu__scroll"); + const options = Array.from( + scrollRegion?.querySelectorAll(".slash-menu-item") ?? [], + ); + top = options.indexOf(this) * height - (scrollRegion?.scrollTop ?? 0); + } + const bottom = this.classList.contains("slash-menu__scroll") ? height * 2 : top + height; + return { + bottom, + height: bottom - top, + left: 0, + right: 240, + top, + width: 240, + x: 0, + y: top, + toJSON: () => ({}), + }; + }, + ); + const { container } = createReactiveDraftHarness(); + document.body.append(container); + inputDraftAtEnd(container, "Use $"); + animationFrames.length = 0; + + for (let index = 0; index < 4; index += 1) { + keydownComposer(container, "ArrowDown"); + } + animationFrames.at(-1)?.(0); + + const scrollRegion = container.querySelector(".skill-menu .slash-menu__scroll"); + const outerMenu = container.querySelector(".skill-menu"); + const activeOption = container.querySelector(".slash-menu-item--active"); + const viewportBounds = scrollRegion?.getBoundingClientRect(); + const optionBounds = activeOption?.getBoundingClientRect(); + expect(scrollRegion?.scrollTop).toBeGreaterThan(0); + expect(outerMenu?.scrollTop).toBe(0); + expect(optionBounds?.top).toBeGreaterThanOrEqual(viewportBounds?.top ?? 0); + expect(optionBounds?.bottom).toBeLessThanOrEqual(viewportBounds?.bottom ?? 0); + container.remove(); + }); + it("does not reopen a dismissed skill picker after a slow refresh", async () => { replaceSkillCommands({ key: "prose", description: "Prose skill." }); const refresh = createDeferred(); @@ -3985,6 +4044,47 @@ describe("chat slash menu accessibility", () => { expect(listbox?.querySelector(`#${activeId}`)?.getAttribute("role")).toBe("option"); }); + it("removes instant implementation badges without hiding option counts", () => { + const harness = createSlashRerenderHarness(); + const container = harness.inputAndRender(harness.container, "/"); + const stopOption = Array.from( + container.querySelectorAll(".slash-menu [role='option']"), + ).find((option) => option.querySelector(".slash-menu-name")?.textContent?.trim() === "/stop"); + + expect(stopOption).toBeDefined(); + expect(stopOption?.querySelector(".slash-menu-badge")).toBeNull(); + expect(container.querySelector(".slash-menu-badge")).not.toBeNull(); + }); + + it("shows every command directly without an expander or keyboard footer", () => { + replaceSlashCommands([ + { + key: "standard-command", + name: "standard-command", + description: "Standard command.", + tier: "standard", + category: "session", + }, + { + key: "power-command", + name: "power-command", + description: "Power command.", + tier: "power", + category: "tools", + }, + ]); + const harness = createSlashRerenderHarness(); + const container = harness.inputAndRender(harness.container, "/"); + + expect( + Array.from(container.querySelectorAll(".slash-menu [role='option']")).map( + (option) => option.querySelector(".slash-menu-name")?.textContent?.trim(), + ), + ).toEqual(["/standard-command", "/power-command"]); + expect(container.querySelector(".slash-menu-show-more")).toBeNull(); + expect(container.querySelector(".slash-menu-footer")).toBeNull(); + }); + it("keeps filtered command DOM and keyboard order aligned with relevance", () => { replaceSlashCommands([ { diff --git a/ui/src/pages/chat/components/chat-composer-skill-menu.ts b/ui/src/pages/chat/components/chat-composer-skill-menu.ts index 31c6a7dac626..34d87f1c006d 100644 --- a/ui/src/pages/chat/components/chat-composer-skill-menu.ts +++ b/ui/src/pages/chat/components/chat-composer-skill-menu.ts @@ -188,16 +188,16 @@ export function scrollActiveSkillMenuOptionIntoView( } requestAnimationFrame(() => { const activeOption = document.getElementById(activeId); - const menu = activeOption?.closest(".skill-menu"); - if (!activeOption || !menu) { + const scrollRegion = activeOption?.closest(".slash-menu__scroll"); + if (!activeOption || !scrollRegion) { return; } - const menuBounds = menu.getBoundingClientRect(); + const menuBounds = scrollRegion.getBoundingClientRect(); const optionBounds = activeOption.getBoundingClientRect(); if (optionBounds.top < menuBounds.top) { - menu.scrollTop -= menuBounds.top - optionBounds.top; + scrollRegion.scrollTop -= menuBounds.top - optionBounds.top; } else if (optionBounds.bottom > menuBounds.bottom) { - menu.scrollTop += optionBounds.bottom - menuBounds.bottom; + scrollRegion.scrollTop += optionBounds.bottom - menuBounds.bottom; } }); } @@ -254,38 +254,40 @@ export function renderSkillMenu( role="listbox" aria-label=${t("chat.skills.menu")} > - ${state.skillCommandRefreshPending || state.skillMenuItems.length === 0 - ? html`
-
${t("chat.skills.loading")}
-
` - : html`
-
${t("chat.skills.label")}
- ${state.skillMenuItems.map( - (command, index) => html` -
event.preventDefault()} - @click=${() => selectSkillMention(command, props, requestUpdate)} - @mouseenter=${() => { - state.skillMenuIndex = index; - requestUpdate(); - }} - > - ${icons.zap} - $${command.name} - ${getSlashCommandDescription(command)} -
- `, - )} -
`} - `; diff --git a/ui/src/pages/chat/components/chat-composer-slash-menu.ts b/ui/src/pages/chat/components/chat-composer-slash-menu.ts index a1648bed766b..f5434c97e5e5 100644 --- a/ui/src/pages/chat/components/chat-composer-slash-menu.ts +++ b/ui/src/pages/chat/components/chat-composer-slash-menu.ts @@ -3,7 +3,6 @@ import { icons, type IconName } from "../../../components/icons.ts"; import { t } from "../../../i18n/index.ts"; import { SLASH_COMMANDS, - getHiddenCommandCount, getSlashCommandCategoryLabel, getSlashCommandCompletions, getSlashCommandDescription, @@ -19,7 +18,6 @@ export function resetSlashMenuState(state: ChatComposerState): void { state.slashMenuCommand = null; state.slashMenuArgItems = []; state.slashMenuItems = []; - state.slashMenuExpanded = false; } function hasVisibleSlashMenuState(state: ChatComposerState): boolean { @@ -28,8 +26,7 @@ function hasVisibleSlashMenuState(state: ChatComposerState): boolean { state.slashMenuMode !== "command" || state.slashMenuCommand !== null || state.slashMenuArgItems.length > 0 || - state.slashMenuItems.length > 0 || - state.slashMenuExpanded + state.slashMenuItems.length > 0 ); } @@ -112,9 +109,7 @@ export function updateSlashMenu( if (!opts.skipSlashIntent) { requestSlashCommandRefresh(value, props, requestUpdate, getCurrentValue); } - const items = getSlashCommandCompletions(match[1] ?? "", { - showAll: state.slashMenuExpanded, - }); + const items = getSlashCommandCompletions(match[1] ?? "", { showAll: true }); state.slashMenuItems = items; state.slashMenuOpen = items.length > 0; state.slashMenuIndex = 0; @@ -274,18 +269,18 @@ export function scrollActiveSlashMenuOptionIntoView( } requestAnimationFrame(() => { const activeOption = document.getElementById(activeId); - const menu = activeOption?.closest(".slash-menu"); - if (!activeOption || !menu) { + const scrollRegion = activeOption?.closest(".slash-menu__scroll"); + if (!activeOption || !scrollRegion) { return; } - const menuBounds = menu.getBoundingClientRect(); + const menuBounds = scrollRegion.getBoundingClientRect(); const optionBounds = activeOption.getBoundingClientRect(); // scrollIntoView also moves the short-landscape composer and page. Keep // keyboard navigation owned by the menu so textarea focus stays stable. if (optionBounds.top < menuBounds.top) { - menu.scrollTop -= menuBounds.top - optionBounds.top; + scrollRegion.scrollTop -= menuBounds.top - optionBounds.top; } else if (optionBounds.bottom > menuBounds.bottom) { - menu.scrollTop += optionBounds.bottom - menuBounds.bottom; + scrollRegion.scrollTop += optionBounds.bottom - menuBounds.bottom; } }); } @@ -321,40 +316,41 @@ export function renderSlashMenu( role="listbox" aria-label=${t("chat.commands.arguments")} > -
-
- /${state.slashMenuCommand.name} ${getSlashCommandDescription(state.slashMenuCommand)} +
+
+
+ /${state.slashMenuCommand.name} ${getSlashCommandDescription(state.slashMenuCommand)} +
+ ${state.slashMenuArgItems.map( + (arg, i) => html` +
selectSlashArg(arg, props, requestUpdate, true)} + @mouseenter=${() => { + state.slashMenuIndex = i; + requestUpdate(); + }} + > + + ${state.slashMenuCommand?.icon + ? renderSlashIcon(state.slashMenuCommand.icon) + : nothing} + ${arg} + + + /${state.slashMenuCommand?.name} ${arg} + +
+ `, + )}
- ${state.slashMenuArgItems.map( - (arg, i) => html` -
selectSlashArg(arg, props, requestUpdate, true)} - @mouseenter=${() => { - state.slashMenuIndex = i; - requestUpdate(); - }} - > - ${state.slashMenuCommand?.icon - ? html`${renderSlashIcon(state.slashMenuCommand.icon)}` - : nothing} - ${arg} - /${state.slashMenuCommand?.name} ${arg} -
- `, - )} -
-
`; @@ -396,21 +392,23 @@ export function renderSlashMenu( requestUpdate(); }} > - ${cmd.icon - ? html`${renderSlashIcon(cmd.icon)}` - : nothing} - /${cmd.name} - ${cmd.args ? html`${cmd.args}` : nothing} - ${getSlashCommandDescription(cmd)} - ${cmd.argOptions?.length - ? html`${t("chat.commands.optionCount", { - count: String(cmd.argOptions.length), - })}` - : cmd.executeLocal && !cmd.args - ? html` ${t("chat.commands.instant")} ` + + ${cmd.icon ? renderSlashIcon(cmd.icon) : nothing} + /${cmd.name} + ${cmd.args ? html`${cmd.args}` : nothing} + + + ${getSlashCommandDescription(cmd)} + ${cmd.argOptions?.length + ? html`${t("chat.commands.optionCount", { + count: String(cmd.argOptions.length), + })}` : nothing} +
`, )} @@ -418,31 +416,9 @@ export function renderSlashMenu( `, ); - const hiddenCount = state.slashMenuExpanded ? 0 : getHiddenCommandCount(); - return html`
- ${sections} - ${hiddenCount > 0 - ? html`` - : nothing} - +
${sections}
`; } diff --git a/ui/src/pages/chat/components/chat-composer-state.ts b/ui/src/pages/chat/components/chat-composer-state.ts index 4843f893f379..04cda91a10f5 100644 --- a/ui/src/pages/chat/components/chat-composer-state.ts +++ b/ui/src/pages/chat/components/chat-composer-state.ts @@ -15,7 +15,6 @@ function createChatComposerState(): ChatComposerState { slashMenuMode: "command", slashMenuCommand: null, slashMenuArgItems: [], - slashMenuExpanded: false, slashCommandRefreshPending: false, skillMenuOpen: false, skillMenuItems: [], diff --git a/ui/src/pages/chat/components/chat-composer-types.ts b/ui/src/pages/chat/components/chat-composer-types.ts index 2c765ba7a90a..06a3b5118450 100644 --- a/ui/src/pages/chat/components/chat-composer-types.ts +++ b/ui/src/pages/chat/components/chat-composer-types.ts @@ -164,7 +164,6 @@ export type ChatComposerState = { slashMenuMode: "command" | "args"; slashMenuCommand: SlashCommandDef | null; slashMenuArgItems: string[]; - slashMenuExpanded: boolean; slashCommandRefreshPending: boolean; skillMenuOpen: boolean; skillMenuItems: SlashCommandDef[]; diff --git a/ui/src/styles/chat/layout.css b/ui/src/styles/chat/layout.css index 13c85c2d8d55..0f59e3c6d447 100644 --- a/ui/src/styles/chat/layout.css +++ b/ui/src/styles/chat/layout.css @@ -3817,64 +3817,89 @@ button.chat-reply-preview--message:disabled { bottom: 100%; left: 0; right: 0; - max-height: 320px; - overflow-y: auto; + max-height: 288px; + overflow: hidden; background: var(--bg-elevated); border: 1px solid var(--border-strong); border-radius: var(--menu-radius); box-shadow: var(--shadow-md); z-index: 30; margin-bottom: 4px; +} + +.slash-menu__scroll { + box-sizing: border-box; + max-height: inherit; + overflow-y: auto; padding: var(--menu-padding); scrollbar-width: thin; + scrollbar-color: color-mix(in srgb, var(--muted) 35%, transparent) transparent; +} + +.slash-menu__scroll::-webkit-scrollbar-track, +.slash-menu__scroll::-webkit-scrollbar-corner { + background: transparent; } .slash-menu-group + .slash-menu-group { - margin-top: 4px; + margin-top: 2px; padding-top: 4px; - border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent); } .slash-menu-group__label { - padding: 4px 10px 2px; - font-size: 0.68rem; - font-weight: 700; + padding: 4px 8px 3px; + font-size: calc(10px * var(--control-ui-text-scale)); + font-weight: 650; text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--accent); - opacity: 0.7; + letter-spacing: 0.08em; + color: var(--muted); } .slash-menu-item { - display: flex; + display: grid; + grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); align-items: center; - gap: 8px; - padding: 7px 10px; - border-radius: var(--radius-sm); - transition: - background var(--duration-fast) ease, - color var(--duration-fast) ease; + column-gap: 16px; + min-height: var(--menu-item-height); + padding: 0 8px; + border-radius: var(--menu-item-radius); + transition: background var(--duration-fast) ease; } .slash-menu-item:hover, .slash-menu-item--active { - background: color-mix(in srgb, var(--accent) 10%, var(--bg-hover)); + background: color-mix(in srgb, var(--bg-hover) 52%, transparent); +} + +.slash-menu-leading, +.slash-menu-trailing { + display: flex; + align-items: center; + min-width: 0; +} + +.slash-menu-leading { + gap: 8px; +} + +.slash-menu-trailing { + justify-content: flex-end; + gap: 8px; } .slash-menu-icon { display: flex; align-items: center; justify-content: center; - width: 20px; - height: 20px; + width: 16px; + height: 16px; flex-shrink: 0; - color: var(--accent); - opacity: 0.7; + color: var(--muted); } .slash-menu-icon svg { - width: 14px; - height: 14px; + width: 13px; + height: 13px; stroke: currentColor; fill: none; stroke-width: 1.5px; @@ -3882,100 +3907,45 @@ button.chat-reply-preview--message:disabled { stroke-linejoin: round; } -.slash-menu-item--active .slash-menu-icon, -.slash-menu-item:hover .slash-menu-icon { - opacity: 1; -} - .slash-menu-name { - font-size: 0.82rem; - font-weight: 600; - font-family: var(--mono); - color: var(--accent); + flex-shrink: 0; + max-width: calc(100% - 24px); + overflow: hidden; + text-overflow: ellipsis; + font-size: var(--control-ui-text-sm); + font-weight: 500; + color: var(--text); white-space: nowrap; } .slash-menu-args { - font-size: 0.75rem; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + font-size: var(--control-ui-text-xs); /* Full-opacity --muted keeps slash-arg hints at WCAG AA on dark surfaces. */ color: var(--muted); - font-family: var(--mono); + white-space: nowrap; } .slash-menu-desc { - flex: 1; + min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; text-align: right; - font-size: 0.75rem; + font-size: var(--control-ui-text-sm); color: var(--muted); } -.slash-menu-item--active .slash-menu-name { - color: var(--accent-hover); -} - -.slash-menu-item--active .slash-menu-desc { - color: var(--text); -} - .slash-menu-badge { - font-size: 0.65rem; - font-weight: 600; - padding: 1px 6px; - border-radius: var(--radius-sm); - background: color-mix(in srgb, var(--accent) 12%, transparent); - color: var(--accent); + font-size: var(--control-ui-text-xs); + font-weight: 500; + color: var(--muted); white-space: nowrap; flex-shrink: 0; } -.slash-menu-show-more { - display: flex; - align-items: center; - justify-content: center; - gap: 4px; - width: 100%; - padding: 8px 10px; - margin-top: 4px; - font-size: 0.75rem; - font-weight: 600; - color: var(--accent); - background: none; - border: none; - border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent); - transition: - background var(--duration-fast) ease, - color var(--duration-fast) ease; -} - -.slash-menu-show-more:hover { - background: color-mix(in srgb, var(--accent) 8%, transparent); - color: var(--accent-hover); -} - -.slash-menu-footer { - display: flex; - gap: 10px; - padding: 6px 10px 4px; - font-size: 0.68rem; - color: var(--muted); - border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent); - margin-top: 4px; -} - -.slash-menu-footer kbd { - display: inline-block; - padding: 1px 4px; - font-size: 0.65rem; - font-family: var(--mono); - border: 1px solid var(--border); - border-radius: 3px; - background: var(--bg); - line-height: 1.3; -} - .chat-attachments-preview { display: flex; gap: 8px;