Browse Source

fix(app): session screen accessibility improvements (#9907)

Nolan Darilek 1 month ago
parent
commit
3435327bc0

+ 1 - 1
packages/app/src/components/dialog-connect-provider.tsx

@@ -143,7 +143,7 @@ export function DialogConnectProvider(props: { provider: string }) {
   }
 
   return (
-    <Dialog title={<IconButton tabIndex={-1} icon="arrow-left" variant="ghost" onClick={goBack} />}>
+    <Dialog title={<IconButton tabIndex={-1} icon="arrow-left" variant="ghost" onClick={goBack} aria-label="Go back" />}>
       <div class="flex flex-col gap-6 px-2.5 pb-3">
         <div class="px-2.5 flex gap-4 items-center">
           <ProviderIcon id={props.provider as IconName} class="size-5 shrink-0 icon-strong-base" />

+ 2 - 0
packages/app/src/components/dialog-edit-project.tsx

@@ -193,6 +193,8 @@ export function DialogEditProject(props: { project: LocalProject }) {
                   {(color) => (
                     <button
                       type="button"
+                      aria-label={`Select ${color} color`}
+                      aria-pressed={store.color === color}
                       classList={{
                         "flex items-center justify-center size-10 p-0.5 rounded-lg overflow-hidden transition-colors cursor-default": true,
                         "bg-transparent border-2 border-icon-strong-base hover:bg-surface-base-hover":

+ 9 - 5
packages/app/src/components/dialog-select-model.tsx

@@ -1,5 +1,5 @@
 import { Popover as Kobalte } from "@kobalte/core/popover"
-import { Component, createMemo, createSignal, JSX, Show } from "solid-js"
+import { Component, ComponentProps, createMemo, createSignal, JSX, Show, ValidComponent } from "solid-js"
 import { useLocal } from "@/context/local"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { popularProviders } from "@/hooks/use-providers"
@@ -86,10 +86,12 @@ const ModelList: Component<{
   )
 }
 
-export const ModelSelectorPopover: Component<{
+export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
   provider?: string
-  children: JSX.Element
-}> = (props) => {
+  children?: JSX.Element
+  triggerAs?: T
+  triggerProps?: ComponentProps<T>
+}) {
   const [open, setOpen] = createSignal(false)
   const dialog = useDialog()
 
@@ -101,7 +103,9 @@ export const ModelSelectorPopover: Component<{
 
   return (
     <Kobalte open={open()} onOpenChange={setOpen} placement="top-start" gutter={8}>
-      <Kobalte.Trigger as="div">{props.children}</Kobalte.Trigger>
+      <Kobalte.Trigger as={props.triggerAs ?? "div"} {...(props.triggerProps as any)}>
+        {props.children}
+      </Kobalte.Trigger>
       <Kobalte.Portal>
         <Kobalte.Content class="w-72 h-80 flex flex-col rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden">
           <Kobalte.Title class="sr-only">{language.t("dialog.model.select.title")}</Kobalte.Title>

+ 1 - 0
packages/app/src/components/dialog-select-server.tsx

@@ -158,6 +158,7 @@ export function DialogSelectServer() {
                   icon="circle-x"
                   variant="ghost"
                   class="bg-transparent transition-opacity shrink-0 hover:scale-110"
+                  aria-label="Remove server"
                   onClick={(e) => {
                     e.stopPropagation()
                     handleRemove(i)

+ 35 - 15
packages/app/src/components/prompt-input.tsx

@@ -1487,6 +1487,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     variant="ghost"
                     class="h-6 w-6"
                     onClick={() => prompt.context.removeActive()}
+                    aria-label="Remove active file from context"
                   />
                 </div>
               )}
@@ -1524,6 +1525,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     variant="ghost"
                     class="h-6 w-6"
                     onClick={() => prompt.context.remove(item.key)}
+                    aria-label="Remove file from context"
                   />
                 </div>
               )}
@@ -1556,6 +1558,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     type="button"
                     onClick={() => removeImageAttachment(attachment.id)}
                     class="absolute -top-1.5 -right-1.5 size-5 rounded-full bg-surface-raised-stronger-non-alpha border border-border-base flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity hover:bg-surface-raised-base-hover"
+                    aria-label="Remove attachment"
                   >
                     <Icon name="close" class="size-3 text-text-weak" />
                   </button>
@@ -1574,6 +1577,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
               editorRef = el
               props.ref?.(el)
             }}
+            role="textbox"
+            aria-multiline="true"
+            aria-label={
+              store.mode === "shell"
+                ? language.t("prompt.placeholder.shell")
+                : language.t("prompt.placeholder.normal", { example: language.t(EXAMPLES[store.placeholder]) })
+            }
             contenteditable="true"
             onInput={handleInput}
             onPaste={handlePaste}
@@ -1638,21 +1648,22 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     </TooltipKeybind>
                   }
                 >
-                  <ModelSelectorPopover>
-                    <TooltipKeybind
-                      placement="top"
-                      title={language.t("command.model.choose")}
-                      keybind={command.keybind("model.choose")}
+                  <TooltipKeybind
+                    placement="top"
+                    title={language.t("command.model.choose")}
+                    keybind={command.keybind("model.choose")}
+                  >
+                    <ModelSelectorPopover
+                      triggerAs={Button}
+                      triggerProps={{ variant: "ghost" }}
                     >
-                      <Button as="div" variant="ghost">
-                        <Show when={local.model.current()?.provider?.id}>
-                          <ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
-                        </Show>
-                        {local.model.current()?.name ?? language.t("dialog.model.select.title")}
-                        <Icon name="chevron-down" size="small" />
-                      </Button>
-                    </TooltipKeybind>
-                  </ModelSelectorPopover>
+                      <Show when={local.model.current()?.provider?.id}>
+                        <ProviderIcon id={local.model.current()!.provider.id as IconName} class="size-4 shrink-0" />
+                      </Show>
+                      {local.model.current()?.name ?? language.t("dialog.model.select.title")}
+                      <Icon name="chevron-down" size="small" />
+                    </ModelSelectorPopover>
+                  </TooltipKeybind>
                 </Show>
                 <Show when={local.model.variant.list().length > 0}>
                   <TooltipKeybind
@@ -1683,6 +1694,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                         "text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory),
                         "hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory),
                       }}
+                      aria-label="Toggle auto-accept permissions"
+                      aria-pressed={permission.isAutoAccepting(params.id!, sdk.directory)}
                     >
                       <Icon
                         name="chevron-double-right"
@@ -1711,7 +1724,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
               <SessionContextUsage />
               <Show when={store.mode === "normal"}>
                 <Tooltip placement="top" value={language.t("prompt.action.attachFile")}>
-                  <Button type="button" variant="ghost" class="size-6" onClick={() => fileInputRef.click()}>
+                  <Button
+                    type="button"
+                    variant="ghost"
+                    class="size-6"
+                    onClick={() => fileInputRef.click()}
+                    aria-label="Attach file"
+                  >
                     <Icon name="photo" class="size-4.5" />
                   </Button>
                 </Tooltip>
@@ -1743,6 +1762,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                 icon={working() ? "stop" : "arrow-up"}
                 variant="primary"
                 class="h-6 w-4.5"
+                aria-label={working() ? "Stop" : "Send message"}
               />
             </Tooltip>
           </div>

+ 1 - 1
packages/app/src/components/session-context-usage.tsx

@@ -96,7 +96,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
         <Switch>
           <Match when={variant() === "indicator"}>{circle()}</Match>
           <Match when={true}>
-            <Button type="button" variant="ghost" class="size-6" onClick={openContext}>
+            <Button type="button" variant="ghost" class="size-6" onClick={openContext} aria-label="View context usage">
               {circle()}
             </Button>
           </Match>

+ 28 - 27
packages/app/src/components/session/session-header.tsx

@@ -135,6 +135,7 @@ export function SessionHeader() {
               type="button"
               class="hidden md:flex w-[320px] p-1 pl-1.5 items-center gap-2 justify-between rounded-md border border-border-weak-base bg-surface-raised-base transition-colors cursor-default hover:bg-surface-raised-base-hover focus:bg-surface-raised-base-hover active:bg-surface-raised-base-active"
               onClick={() => command.trigger("file.open")}
+              aria-label="Search files"
             >
               <div class="flex min-w-0 flex-1 items-center gap-2 overflow-visible">
                 <Icon name="magnifying-glass" size="normal" class="icon-base shrink-0" />
@@ -184,6 +185,10 @@ export function SessionHeader() {
                       variant="ghost"
                       class="group/review-toggle size-6 p-0"
                       onClick={() => view().reviewPanel.toggle()}
+                      aria-label="Toggle review panel"
+                      aria-expanded={view().reviewPanel.opened()}
+                      aria-controls="review-panel"
+                      tabIndex={showReview() ? 0 : -1}
                     >
                       <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
                         <Icon
@@ -214,6 +219,9 @@ export function SessionHeader() {
                     variant="ghost"
                     class="group/terminal-toggle size-6 p-0"
                     onClick={() => view().terminal.toggle()}
+                    aria-label="Toggle terminal"
+                    aria-expanded={view().terminal.opened()}
+                    aria-controls="terminal-panel"
                   >
                     <div class="relative flex items-center justify-center size-4 [&>*]:absolute [&>*]:inset-0">
                       <Icon
@@ -235,32 +243,23 @@ export function SessionHeader() {
                   </Button>
                 </TooltipKeybind>
               </div>
-              <div
-                class="flex items-center"
-                classList={{
-                  "opacity-0 pointer-events-none": !showShare(),
-                }}
-                aria-hidden={!showShare()}
-              >
-                <Popover
-                  title={language.t("session.share.popover.title")}
-                  description={
-                    shareUrl()
-                      ? language.t("session.share.popover.description.shared")
-                      : language.t("session.share.popover.description.unshared")
-                  }
-                  trigger={
-                    <Tooltip class="shrink-0" value={language.t("command.session.share")}>
-                      <Button
-                        variant="secondary"
-                        classList={{ "rounded-r-none": shareUrl() !== undefined }}
-                        style={{ scale: 1 }}
-                      >
-                        {language.t("session.share.action.share")}
-                      </Button>
-                    </Tooltip>
-                  }
-                >
+              <Show when={showShare()}>
+                <div class="flex items-center">
+                  <Popover
+                    title={language.t("session.share.popover.title")}
+                    description={
+                      shareUrl()
+                        ? language.t("session.share.popover.description.shared")
+                        : language.t("session.share.popover.description.unshared")
+                    }
+                    triggerAs={Button}
+                    triggerProps={{
+                      variant: "secondary",
+                      classList: { "rounded-r-none": shareUrl() !== undefined },
+                      style: { scale: 1 },
+                    }}
+                    trigger={language.t("session.share.action.share")}
+                  >
                   <div class="flex flex-col gap-2">
                     <Show
                       when={shareUrl()}
@@ -322,10 +321,12 @@ export function SessionHeader() {
                       class="rounded-l-none"
                       onClick={copyLink}
                       disabled={state.unshare}
+                      aria-label="Copy share link"
                     />
                   </Tooltip>
                 </Show>
-              </div>
+                </div>
+              </Show>
             </div>
           </Portal>
         )}

+ 1 - 1
packages/app/src/components/session/session-sortable-tab.tsx

@@ -37,7 +37,7 @@ export function SortableTab(props: { tab: string; onTabClose: (tab: string) => v
           value={props.tab}
           closeButton={
             <Tooltip value={language.t("common.closeTab")} placement="bottom">
-              <IconButton icon="close" variant="ghost" onClick={() => props.onTabClose(props.tab)} />
+              <IconButton icon="close" variant="ghost" onClick={() => props.onTabClose(props.tab)} aria-label="Close tab" />
             </Tooltip>
           }
           hideCloseButton

+ 1 - 0
packages/app/src/components/session/session-sortable-terminal-tab.tsx

@@ -139,6 +139,7 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
                 e.stopPropagation()
                 close()
               }}
+              aria-label="Close terminal"
             />
           }
         >

+ 7 - 4
packages/app/src/pages/layout.tsx

@@ -1916,6 +1916,7 @@ export default function Layout(props: ParentProps) {
           "bg-surface-base-hover border border-border-weak-base": !selected() && open(),
         }}
         onClick={() => navigateToProject(props.project.worktree)}
+        onBlur={() => setOpen(false)}
       >
         <ProjectIcon project={props.project} notify />
       </button>
@@ -2343,7 +2344,8 @@ export default function Layout(props: ParentProps) {
     <div class="relative bg-background-base flex-1 min-h-0 flex flex-col select-none [&_input]:select-text [&_textarea]:select-text [&_[contenteditable]]:select-text">
       <Titlebar />
       <div class="flex-1 min-h-0 flex">
-        <div
+        <nav
+          aria-label="Projects and sessions"
           classList={{
             "hidden xl:block": true,
             "relative shrink-0": true,
@@ -2364,7 +2366,7 @@ export default function Layout(props: ParentProps) {
               onCollapse={layout.sidebar.close}
             />
           </Show>
-        </div>
+        </nav>
         <div class="xl:hidden">
           <div
             classList={{
@@ -2376,7 +2378,8 @@ export default function Layout(props: ParentProps) {
               if (e.target === e.currentTarget) layout.mobileSidebar.hide()
             }}
           />
-          <div
+          <nav
+            aria-label="Projects and sessions"
             classList={{
               "@container fixed top-10 bottom-0 left-0 z-50 w-72 bg-background-base transition-transform duration-200 ease-out": true,
               "translate-x-0": layout.mobileSidebar.opened(),
@@ -2385,7 +2388,7 @@ export default function Layout(props: ParentProps) {
             onClick={(e) => e.stopPropagation()}
           >
             <SidebarContent mobile />
-          </div>
+          </nav>
         </div>
 
         <main

+ 10 - 5
packages/app/src/pages/session.tsx

@@ -782,7 +782,7 @@ export default function Page() {
     const activeElement = document.activeElement as HTMLElement | undefined
     if (activeElement) {
       const isProtected = activeElement.closest("[data-prevent-autofocus]")
-      const isInput = /^(INPUT|TEXTAREA|SELECT)$/.test(activeElement.tagName) || activeElement.isContentEditable
+      const isInput = /^(INPUT|TEXTAREA|SELECT|BUTTON)$/.test(activeElement.tagName) || activeElement.isContentEditable
       if (isProtected || isInput) return
     }
     if (dialog.active) return
@@ -1404,6 +1404,7 @@ export default function Page() {
 
                         <div
                           ref={autoScroll.contentRef}
+                          role="log"
                           class="flex flex-col gap-32 items-start justify-start pb-[calc(var(--prompt-height,8rem)+64px)] md:pb-[calc(var(--prompt-height,10rem)+64px)] transition-[margin]"
                           classList={{
                             "w-full": true,
@@ -1552,7 +1553,7 @@ export default function Page() {
 
         {/* Desktop tabs panel (Review + Context + Files) - hidden on mobile */}
         <Show when={isDesktop() && showTabs()}>
-          <div class="relative flex-1 min-w-0 h-full border-l border-border-weak-base">
+          <aside id="review-panel" aria-label="Review and files" class="relative flex-1 min-w-0 h-full border-l border-border-weak-base">
             <DragDropProvider
               onDragStart={handleDragStart}
               onDragEnd={handleDragEnd}
@@ -1586,7 +1587,7 @@ export default function Page() {
                         value="context"
                         closeButton={
                           <Tooltip value={language.t("common.closeTab")} placement="bottom">
-                            <IconButton icon="close" variant="ghost" onClick={() => tabs().close("context")} />
+                            <IconButton icon="close" variant="ghost" onClick={() => tabs().close("context")} aria-label="Close context tab" />
                           </Tooltip>
                         }
                         hideCloseButton
@@ -1612,6 +1613,7 @@ export default function Page() {
                           variant="ghost"
                           iconSize="large"
                           onClick={() => dialog.show(() => <DialogSelectFile />)}
+                          aria-label="Open file"
                         />
                       </TooltipKeybind>
                     </div>
@@ -1913,12 +1915,15 @@ export default function Page() {
                 </Show>
               </DragOverlay>
             </DragDropProvider>
-          </div>
+          </aside>
         </Show>
       </div>
 
       <Show when={isDesktop() && view().terminal.opened()}>
         <div
+          id="terminal-panel"
+          role="region"
+          aria-label="Terminal"
           class="relative w-full flex flex-col shrink-0 border-t border-border-weak-base"
           style={{ height: `${layout.terminal.height()}px` }}
         >
@@ -1990,7 +1995,7 @@ export default function Page() {
                         keybind={command.keybind("terminal.new")}
                         class="flex items-center"
                       >
-                        <IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={terminal.new} />
+                        <IconButton icon="plus-small" variant="ghost" iconSize="large" onClick={terminal.new} aria-label="New terminal" />
                       </TooltipKeybind>
                     </div>
                   </Tabs.List>

+ 1 - 1
packages/ui/src/components/dialog.tsx

@@ -40,7 +40,7 @@ export function Dialog(props: DialogProps) {
               <Switch>
                 <Match when={props.action}>{props.action}</Match>
                 <Match when={true}>
-                  <Kobalte.CloseButton data-slot="dialog-close-button" as={IconButton} icon="close" variant="ghost" />
+                  <Kobalte.CloseButton data-slot="dialog-close-button" as={IconButton} icon="close" variant="ghost" aria-label="Close" />
                 </Match>
               </Switch>
             </div>

+ 1 - 1
packages/ui/src/components/image-preview.tsx

@@ -14,7 +14,7 @@ export function ImagePreview(props: ImagePreviewProps) {
       <div data-slot="image-preview-container">
         <Kobalte.Content data-slot="image-preview-content">
           <div data-slot="image-preview-header">
-            <Kobalte.CloseButton data-slot="image-preview-close" as={IconButton} icon="close" variant="ghost" />
+            <Kobalte.CloseButton data-slot="image-preview-close" as={IconButton} icon="close" variant="ghost" aria-label="Close" />
           </div>
           <div data-slot="image-preview-body">
             <img src={props.src} alt={props.alt ?? i18n.t("ui.imagePreview.alt")} data-slot="image-preview-image" />

+ 1 - 1
packages/ui/src/components/list.tsx

@@ -230,7 +230,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
               />
             </div>
             <Show when={internalFilter()}>
-              <IconButton icon="circle-x" variant="ghost" onClick={() => setInternalFilter("")} />
+              <IconButton icon="circle-x" variant="ghost" onClick={() => setInternalFilter("")} aria-label="Clear filter" />
             </Show>
           </div>
           {searchAction()}

+ 1 - 0
packages/ui/src/components/message-part.tsx

@@ -429,6 +429,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
                   event.stopPropagation()
                   handleCopy()
                 }}
+                aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copy")}
               />
             </Tooltip>
           </div>

+ 9 - 7
packages/ui/src/components/popover.tsx

@@ -1,21 +1,23 @@
 import { Popover as Kobalte } from "@kobalte/core/popover"
-import { ComponentProps, JSXElement, ParentProps, Show, splitProps } from "solid-js"
+import { ComponentProps, JSXElement, ParentProps, Show, splitProps, ValidComponent } from "solid-js"
 import { IconButton } from "./icon-button"
 
-export interface PopoverProps extends ParentProps, Omit<ComponentProps<typeof Kobalte>, "children"> {
-  trigger: JSXElement
+export interface PopoverProps<T extends ValidComponent = "div"> extends ParentProps, Omit<ComponentProps<typeof Kobalte>, "children"> {
+  trigger?: JSXElement
+  triggerAs?: T
+  triggerProps?: ComponentProps<T>
   title?: JSXElement
   description?: JSXElement
   class?: ComponentProps<"div">["class"]
   classList?: ComponentProps<"div">["classList"]
 }
 
-export function Popover(props: PopoverProps) {
-  const [local, rest] = splitProps(props, ["trigger", "title", "description", "class", "classList", "children"])
+export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>) {
+  const [local, rest] = splitProps(props, ["trigger", "triggerAs", "triggerProps", "title", "description", "class", "classList", "children"])
 
   return (
     <Kobalte gutter={4} {...rest}>
-      <Kobalte.Trigger as="div" data-slot="popover-trigger">
+      <Kobalte.Trigger as={local.triggerAs ?? "div"} data-slot="popover-trigger" {...(local.triggerProps as any)}>
         {local.trigger}
       </Kobalte.Trigger>
       <Kobalte.Portal>
@@ -30,7 +32,7 @@ export function Popover(props: PopoverProps) {
           <Show when={local.title}>
             <div data-slot="popover-header">
               <Kobalte.Title data-slot="popover-title">{local.title}</Kobalte.Title>
-              <Kobalte.CloseButton data-slot="popover-close-button" as={IconButton} icon="close" variant="ghost" />
+              <Kobalte.CloseButton data-slot="popover-close-button" as={IconButton} icon="close" variant="ghost" aria-label="Close" />
             </div>
           </Show>
           <Show when={local.description}>

+ 9 - 5
packages/ui/src/components/session-turn.tsx

@@ -506,13 +506,13 @@ export function SessionTurn(
                   </Match>
                   <Match when={true}>
                     <Show when={attachmentParts().length > 0}>
-                      <div data-slot="session-turn-attachments">
+                      <div data-slot="session-turn-attachments" aria-live="off">
                         <Message message={msg()} parts={attachmentParts()} />
                       </div>
                     </Show>
                     <div data-slot="session-turn-sticky" ref={setStickyRef}>
                       {/* User Message */}
-                      <div data-slot="session-turn-message-content">
+                      <div data-slot="session-turn-message-content" aria-live="off">
                         <Message message={msg()} parts={stickyParts()} />
                       </div>
 
@@ -525,6 +525,7 @@ export function SessionTurn(
                             variant="ghost"
                             size="small"
                             onClick={props.onStepsExpandedToggle ?? (() => {})}
+                            aria-expanded={props.stepsExpanded}
                           >
                             <Show when={working()}>
                               <Spinner />
@@ -552,8 +553,8 @@ export function SessionTurn(
                               <Match when={props.stepsExpanded}>{i18n.t("ui.sessionTurn.steps.hide")}</Match>
                               <Match when={!props.stepsExpanded}>{i18n.t("ui.sessionTurn.steps.show")}</Match>
                             </Switch>
-                            <span>·</span>
-                            <span>{store.duration}</span>
+                            <span aria-hidden="true">·</span>
+                            <span aria-live="off">{store.duration}</span>
                             <Show when={assistantMessages().length > 0}>
                               <Icon name="chevron-grabber-vertical" size="small" />
                             </Show>
@@ -563,7 +564,7 @@ export function SessionTurn(
                     </div>
                     {/* Response */}
                     <Show when={props.stepsExpanded && assistantMessages().length > 0}>
-                      <div data-slot="session-turn-collapsible-content-inner">
+                      <div data-slot="session-turn-collapsible-content-inner" aria-hidden={working()}>
                         <For each={assistantMessages()}>
                           {(assistantMessage) => (
                             <AssistantMessageItem
@@ -589,6 +590,9 @@ export function SessionTurn(
                       </div>
                     </Show>
                     {/* Response */}
+                    <div class="sr-only" aria-live="polite">
+                      {!working() && response() ? response() : ""}
+                    </div>
                     <Show when={!working() && (response() || hasDiffs())}>
                       <div data-slot="session-turn-summary-section">
                         <div data-slot="session-turn-summary-header">

+ 1 - 0
packages/ui/src/components/text-field.tsx

@@ -103,6 +103,7 @@ export function TextField(props: TextFieldProps) {
               variant="ghost"
               onClick={handleCopy}
               data-slot="input-copy-button"
+              aria-label={copied() ? i18n.t("ui.textField.copied") : i18n.t("ui.textField.copyToClipboard")}
             />
           </Tooltip>
         </Show>

+ 1 - 1
packages/ui/src/components/toast.tsx

@@ -62,7 +62,7 @@ function ToastActions(props: ComponentProps<"div">) {
 }
 
 function ToastCloseButton(props: ToastCloseButtonProps & ComponentProps<"button">) {
-  return <Kobalte.CloseButton data-slot="toast-close-button" as={IconButton} icon="close" variant="ghost" {...props} />
+  return <Kobalte.CloseButton data-slot="toast-close-button" as={IconButton} icon="close" variant="ghost" aria-label="Dismiss" {...props} />
 }
 
 function ToastProgressTrack(props: ComponentProps<typeof Kobalte.ProgressTrack>) {