From 41a479d109bdc0ac74db8bdc7ea994ff147c1765 Mon Sep 17 00:00:00 2001 From: Christopher Gardella <44123552+cmgardella@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:23:36 -0400 Subject: [PATCH 1/2] Polish AI Assistant panel and workspace chooser dropdown styling - Fix skill-selector footer buttons touching with no gap when stacked; size them to content and lay out via flex-wrap - Remove leftover scroll-shadow ("chin") artifact on the LLM select list and restyle its footer actions to match the skill-menu pill buttons - Resize the Past Sessions popover to inset 30px from both panel edges, match its header font to the panel title, and restyle background, border, dividers, and hover colors - Fix a nested double-stroke on the session "more options" dropdown by neutralizing the wormholed BoxelDropdown content wrapper's own border/shadow - Auto-close the "more options" dropdown on mouse-out (hover-intent debounce) instead of requiring a click elsewhere, and clear lingering focus styling - Give the currently-active session row a hover state even while its options menu is open - Restyle the workspace chooser's "View All" sort dropdown (default state matches the chooser background; open state uses a dark platter with a white 25%-opacity stroke, white text, and a teal active-item checkmark) --- .../components/ai-assistant/llm-select.gts | 9 ++ .../ai-assistant/past-session-item.gts | 90 ++++++++++++++--- .../components/ai-assistant/past-sessions.gts | 96 +++++++++++-------- packages/host/app/components/matrix/room.gts | 27 ++++-- .../operator-mode/workspace-chooser/index.gts | 28 +++++- .../host/app/components/pill-menu/index.gts | 3 + 6 files changed, 188 insertions(+), 65 deletions(-) diff --git a/packages/host/app/components/ai-assistant/llm-select.gts b/packages/host/app/components/ai-assistant/llm-select.gts index 5062f9be0fb..85d3f726452 100644 --- a/packages/host/app/components/ai-assistant/llm-select.gts +++ b/packages/host/app/components/ai-assistant/llm-select.gts @@ -107,6 +107,15 @@ export default class LLMSelect extends Component { scroll-timeline: --pill-menu-content-scroll-timeline; } + /* LLMSelect renders its own footer-like row inside .llm-list rather + than forwarding a :footer block to PillMenu, so PillMenu never marks + itself `has-footer` and its default bottom scroll-shadow (meant to be + swapped out when a real footer exists) is left dangling over the + list content instead of framing the true bottom edge. */ + :deep(.menu-content::after) { + display: none; + } + .llm-option { background: var(--boxel-light); border-radius: var(--boxel-border-radius); diff --git a/packages/host/app/components/ai-assistant/past-session-item.gts b/packages/host/app/components/ai-assistant/past-session-item.gts index 15f549cd96b..074fcbc45a1 100644 --- a/packages/host/app/components/ai-assistant/past-session-item.gts +++ b/packages/host/app/components/ai-assistant/past-session-item.gts @@ -9,6 +9,7 @@ import { tracked } from '@glimmer/tracking'; import ExternalLink from '@cardstack/boxel-icons/external-link'; import { format as formatDate, isSameDay, isSameYear } from 'date-fns'; +import { modifier } from 'ember-modifier'; import { BoxelDropdown, @@ -45,6 +46,42 @@ interface Signature { export default class PastSessionItem extends Component { @tracked private preventMenuClose = false; + private closeDropdownAction: (() => void) | null = null; + private closeDropdownTimer: ReturnType | null = null; + private triggerElement: HTMLElement | null = null; + + private registerCloseAction = modifier( + (_element: HTMLElement, [closeFn]: [() => void]) => { + this.closeDropdownAction = closeFn; + return () => { + this.closeDropdownAction = null; + }; + }, + ); + + private registerTriggerElement = modifier((element: HTMLElement) => { + this.triggerElement = element; + return () => { + this.triggerElement = null; + }; + }); + + @action + private scheduleCloseDropdown() { + this.cancelCloseDropdown(); + this.closeDropdownTimer = setTimeout(() => { + this.closeDropdownAction?.(); + this.triggerElement?.blur(); + }, 200); + } + + @action + private cancelCloseDropdown() { + if (this.closeDropdownTimer) { + clearTimeout(this.closeDropdownTimer); + this.closeDropdownTimer = null; + } + }