From 0c61054d07cb1327ff27edd7aad472e9b0418582 Mon Sep 17 00:00:00 2001 From: Vignesh Date: Fri, 15 May 2026 20:34:03 -0700 Subject: [PATCH 01/10] use child snippets for landing and chat message elements --- .../components/app/actions/ActionIcon.svelte | 36 +++-- .../components/app/badges/BadgeInfo.svelte | 8 +- .../ChatMessageStatistics.svelte | 114 ++++++++------ .../ChatMessageStatisticsBadge.svelte | 15 +- .../components/app/models/ModelBadge.svelte | 9 +- .../app/models/ModelsSelectorDropdown.svelte | 142 +++++++++--------- 6 files changed, 177 insertions(+), 147 deletions(-) diff --git a/tools/ui/src/lib/components/app/actions/ActionIcon.svelte b/tools/ui/src/lib/components/app/actions/ActionIcon.svelte index 849b83b19c8..f156df66998 100644 --- a/tools/ui/src/lib/components/app/actions/ActionIcon.svelte +++ b/tools/ui/src/lib/components/app/actions/ActionIcon.svelte @@ -35,23 +35,27 @@ - + onclick?.(e); + }} + class="h-6 w-6 p-0 {className} flex hover:bg-transparent data-[state=open]:bg-transparent!" + aria-label={ariaLabel || tooltip} + > + {#if icon} + {@const IconComponent = icon} + + {/if} + + {/snippet} diff --git a/tools/ui/src/lib/components/app/badges/BadgeInfo.svelte b/tools/ui/src/lib/components/app/badges/BadgeInfo.svelte index 25986082bea..c87c94bc477 100644 --- a/tools/ui/src/lib/components/app/badges/BadgeInfo.svelte +++ b/tools/ui/src/lib/components/app/badges/BadgeInfo.svelte @@ -1,22 +1,22 @@ + Reading + + {/snippet} @@ -140,21 +144,25 @@ {/if} - + + {#snippet child({ props })} + + {/snippet} @@ -169,18 +177,22 @@ {#if hasAgenticStats} - + Tools + + {/snippet} @@ -191,18 +203,22 @@ {#if !hideSummary} - + + {#snippet child({ props })} + + {/snippet} diff --git a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageStatistics/ChatMessageStatisticsBadge.svelte b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageStatistics/ChatMessageStatisticsBadge.svelte index eea7da7b2f1..db7d01690a5 100644 --- a/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageStatistics/ChatMessageStatisticsBadge.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatMessages/ChatMessageStatistics/ChatMessageStatisticsBadge.svelte @@ -21,13 +21,16 @@ {#if tooltipLabel} - - {#snippet icon()} - - {/snippet} + + {#snippet child({ props })} + + {#snippet icon()} + + {/snippet} - {value} - + {value} + + {/snippet}

{tooltipLabel}

diff --git a/tools/ui/src/lib/components/app/models/ModelBadge.svelte b/tools/ui/src/lib/components/app/models/ModelBadge.svelte index cc1d1848e4b..b840687d4ef 100644 --- a/tools/ui/src/lib/components/app/models/ModelBadge.svelte +++ b/tools/ui/src/lib/components/app/models/ModelBadge.svelte @@ -27,8 +27,8 @@ let shouldShow = $derived(model && (modelProp !== undefined || isModelMode)); -{#snippet badgeContent()} - +{#snippet badgeContent(triggerProps?: Record)} + {#snippet icon()} {/snippet} @@ -47,7 +47,10 @@ {#if showTooltip} - {@render badgeContent()} + + {#snippet child({ props })} + {@render badgeContent(props)} + {/snippet} diff --git a/tools/ui/src/lib/components/app/models/ModelsSelectorDropdown.svelte b/tools/ui/src/lib/components/app/models/ModelsSelectorDropdown.svelte index 998a6a0faaa..529a6301299 100644 --- a/tools/ui/src/lib/components/app/models/ModelsSelectorDropdown.svelte +++ b/tools/ui/src/lib/components/app/models/ModelsSelectorDropdown.svelte @@ -118,51 +118,53 @@ {#if ms.isRouter} - - + + + + {#snippet child({ props })} + + - {#if selectedOption} - - - - {#snippet child({ props })} + {#if selectedOption} - {/snippet} - + {:else} + Select model + {/if} - -

{selectedOption.model}

-
-
- {:else} - Select model - {/if} + {#if ms.updating || ms.isLoadingModel} + + {:else} + + {/if} +
+ {/snippet} +
- {#if ms.updating || ms.isLoadingModel} - - {:else} - + {#if selectedOption} + +

{selectedOption.model}

+
{/if} -
+
{:else} - + {/snippet} + - {#if ms.updating} - + {#if selectedOption} + +

{selectedOption.model}

+
{/if} - +
{/if} {/if} From 2683553b30da8fb16edfefb48fb5c12b79d474b5 Mon Sep 17 00:00:00 2001 From: Vignesh Date: Sat, 16 May 2026 11:54:19 -0700 Subject: [PATCH 02/10] make ... icon visible in conversation history menu --- .../SidebarNavigationConversationItem.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte b/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte index dad8d954cbb..9e47f07ec88 100644 --- a/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte +++ b/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte @@ -105,6 +105,7 @@ onclick={handleSelect} onmouseover={handleMouseOver} onmouseleave={handleMouseLeave} + onfocusin={handleMouseOver} >
Date: Sat, 16 May 2026 12:14:18 -0700 Subject: [PATCH 03/10] conversation history forward tab fix --- .../app/navigation/DropdownMenuActions.svelte | 36 ++++++++++--------- .../SidebarNavigationConversationItem.svelte | 5 +++ 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/tools/ui/src/lib/components/app/navigation/DropdownMenuActions.svelte b/tools/ui/src/lib/components/app/navigation/DropdownMenuActions.svelte index 83d856d10ea..951831149fc 100644 --- a/tools/ui/src/lib/components/app/navigation/DropdownMenuActions.svelte +++ b/tools/ui/src/lib/components/app/navigation/DropdownMenuActions.svelte @@ -34,24 +34,28 @@ - e.stopPropagation()} - > - {#if triggerTooltip} - - + + + + {#snippet child({ props })} + e.stopPropagation()} + > {@render iconComponent(triggerIcon, 'h-3 w-3')} - {triggerTooltip} - - -

{triggerTooltip}

-
-
- {:else} - {@render iconComponent(triggerIcon, 'h-3 w-3')} + {#if triggerTooltip} + {triggerTooltip} + {/if} +
+ {/snippet} + + {#if triggerTooltip} + +

{triggerTooltip}

+
{/if} - + {#each actions as action, index (action.label)} diff --git a/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte b/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte index 9e47f07ec88..9dd99a73031 100644 --- a/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte +++ b/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte @@ -106,6 +106,11 @@ onmouseover={handleMouseOver} onmouseleave={handleMouseLeave} onfocusin={handleMouseOver} + onfocusout={(e) => { + if (!e.currentTarget.contains(e.relatedTarget as Node | null)) { + handleMouseLeave(); + } + }} >
Date: Sat, 16 May 2026 12:36:47 -0700 Subject: [PATCH 04/10] add snippet fix for fork icon in conversation history --- .../SidebarNavigationConversationItem.svelte | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte b/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte index 9dd99a73031..e38a937385a 100644 --- a/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte +++ b/tools/ui/src/lib/components/app/navigation/SidebarNavigation/SidebarNavigationConversationItem.svelte @@ -119,12 +119,16 @@ {#if depth > 0} - - - + + {#snippet child({ props })} + + + + {/snippet} From 9de86f601fd84cdfa898b91dcd072b3865d29a3b Mon Sep 17 00:00:00 2001 From: Vignesh Date: Sat, 16 May 2026 13:21:14 -0700 Subject: [PATCH 05/10] focus/keyboard fix for attachment x icon and scroll left/right --- ...hatAttachmentsListItemThumbnailFile.svelte | 4 +++- ...atAttachmentsListItemThumbnailImage.svelte | 2 +- .../app/misc/HorizontalScrollCarousel.svelte | 21 +++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemThumbnailFile.svelte b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemThumbnailFile.svelte index 3eeace42f16..ec150df7406 100644 --- a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemThumbnailFile.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemThumbnailFile.svelte @@ -93,7 +93,9 @@ {/snippet} {#snippet removeButton()} -
+
onRemove?.(id)} />
{/snippet} diff --git a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemThumbnailImage.svelte b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemThumbnailImage.svelte index b78a6591619..dc5e5607b0d 100644 --- a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemThumbnailImage.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemThumbnailImage.svelte @@ -51,7 +51,7 @@ {#if !readonly}
{ - if (scrollContainer) { - setTimeout(() => { - updateScrollButtons(); - }, 0); - } + if (!scrollContainer) return; + + const observer = new ResizeObserver(() => updateScrollButtons()); + observer.observe(scrollContainer); + + return () => observer.disconnect(); });
+ {/snippet} + + + +

{opts.tooltipText}

+
+ +{/snippet} +
{#if hasPromptStats || isLive} - - - - {#snippet child({ props })} - - {/snippet} - - - -

Reading (prompt processing)

-
-
+ {@render viewButton({ + view: ChatMessageStatsView.READING, + icon: BookOpenText, + label: 'Reading', + tooltipText: 'Reading (prompt processing)' + })} {/if} - - - - {#snippet child({ props })} - - {/snippet} - - - -

- {isGenerationDisabled - ? 'Generation (waiting for tokens...)' - : 'Generation (token output)'} -

-
-
+ + {@render viewButton({ + view: ChatMessageStatsView.GENERATION, + icon: Sparkles, + label: 'Generation', + tooltipText: isGenerationDisabled + ? 'Generation (waiting for tokens...)' + : 'Generation (token output)', + disabled: isGenerationDisabled + })} {#if hasAgenticStats} - - - - {#snippet child({ props })} - - {/snippet} - - - -

Tool calls

-
-
+ {@render viewButton({ + view: ChatMessageStatsView.TOOLS, + icon: Wrench, + label: 'Tools', + tooltipText: 'Tool calls' + })} {#if !hideSummary} - - - - {#snippet child({ props })} - - {/snippet} - - - -

Agentic summary

-
-
+ {@render viewButton({ + view: ChatMessageStatsView.SUMMARY, + icon: Layers, + label: 'Summary', + tooltipText: 'Agentic summary' + })} {/if} {/if}
diff --git a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenActionScrollDown.svelte b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenActionScrollDown.svelte index ad169033219..a22c491adac 100644 --- a/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenActionScrollDown.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatScreen/ChatScreenActionScrollDown.svelte @@ -41,17 +41,13 @@ }); -
+
+ {}} /> +
+ diff --git a/tools/ui/tests/stories/a11y/HorizontalScrollCarousel.a11y.stories.svelte b/tools/ui/tests/stories/a11y/HorizontalScrollCarousel.a11y.stories.svelte index ede3741eb73..937d7ab1094 100644 --- a/tools/ui/tests/stories/a11y/HorizontalScrollCarousel.a11y.stories.svelte +++ b/tools/ui/tests/stories/a11y/HorizontalScrollCarousel.a11y.stories.svelte @@ -16,36 +16,54 @@ { + play={async ({ canvas, userEvent }) => { + const before = await canvas.findByRole('button', { name: 'before' }); + const after = await canvas.findByRole('button', { name: 'after' }); const leftArrow = await canvas.findByRole('button', { name: 'Scroll left' }); - const rightArrow = await canvas.findByRole('button', { name: 'Scroll right' }); await waitFor(() => { expect(leftArrow).toBeDisabled(); - expect(rightArrow).toBeDisabled(); }); + + before.focus(); + await userEvent.tab(); + + await expect(after).toHaveFocus(); }} > - -
-
-
+
+ + +
+
+
+ +
{ + play={async ({ canvas, userEvent }) => { + const before = await canvas.findByRole('button', { name: 'before' }); const rightArrow = await canvas.findByRole('button', { name: 'Scroll right' }); await waitFor(() => { expect(rightArrow).not.toBeDisabled(); }); + + before.focus(); + await userEvent.tab(); + + await expect(rightArrow).toHaveFocus(); }} > - - {#each [...Array(20).keys()] as i (i)} -
{i}
- {/each} -
+
+ + + {#each [...Array(20).keys()] as i (i)} +
{i}
+ {/each} +
+
diff --git a/tools/ui/tests/stories/a11y/SidebarNavigationConversationItem.a11y.stories.svelte b/tools/ui/tests/stories/a11y/SidebarNavigationConversationItem.a11y.stories.svelte index ef8dbe4f4e3..1fc42608f72 100644 --- a/tools/ui/tests/stories/a11y/SidebarNavigationConversationItem.a11y.stories.svelte +++ b/tools/ui/tests/stories/a11y/SidebarNavigationConversationItem.a11y.stories.svelte @@ -25,13 +25,12 @@ name="ForkIconSingleTabStop" args={{ conversation: mockForkedConversation, depth: 1 }} play={async ({ canvas, userEvent }) => { + const row = await canvas.findByRole('button', { name: /Forked Conversation/ }); const forkIcon = await canvas.findByRole('link'); - forkIcon.focus(); - await expect(forkIcon).toHaveFocus(); - + row.focus(); await userEvent.tab(); - await expect(forkIcon).not.toHaveFocus(); + await expect(forkIcon).toHaveFocus(); }} />