Răsfoiți Sursa

feat: Transitions, spacing, scroll fade, prompt area update (#11168)

Co-authored-by: Github Action <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: aaroniker <[email protected]>
Aaron Iker 3 săptămâni în urmă
părinte
comite
20619a6a26
70 a modificat fișierele cu 6254 adăugiri și 5225 ștergeri
  1. 4 3
      packages/app/src/components/dialog-select-model.tsx
  2. 93 55
      packages/app/src/components/prompt-input.tsx
  3. 3 3
      packages/app/src/components/session-context-usage.tsx
  4. 24 18
      packages/app/src/components/settings-general.tsx
  5. 8 2
      packages/app/src/components/settings-keybinds.tsx
  6. 1 0
      packages/desktop/src-tauri/src/lib.rs
  7. 94 85
      packages/ui/src/components/accordion.css
  8. 32 11
      packages/ui/src/components/accordion.tsx
  9. 32 32
      packages/ui/src/components/avatar.css
  10. 84 84
      packages/ui/src/components/basic-tool.css
  11. 166 170
      packages/ui/src/components/button.css
  12. 1 1
      packages/ui/src/components/button.tsx
  13. 26 24
      packages/ui/src/components/card.css
  14. 134 119
      packages/ui/src/components/checkbox.css
  15. 2 2
      packages/ui/src/components/code.css
  16. 85 89
      packages/ui/src/components/collapsible.css
  17. 29 13
      packages/ui/src/components/collapsible.tsx
  18. 52 0
      packages/ui/src/components/cycle-label.css
  19. 132 0
      packages/ui/src/components/cycle-label.tsx
  20. 176 161
      packages/ui/src/components/dialog.css
  21. 33 33
      packages/ui/src/components/diff-changes.css
  22. 30 30
      packages/ui/src/components/diff.css
  23. 125 114
      packages/ui/src/components/dropdown-menu.css
  24. 3 3
      packages/ui/src/components/file-icon.css
  25. 52 50
      packages/ui/src/components/hover-card.css
  26. 141 138
      packages/ui/src/components/icon-button.css
  27. 27 27
      packages/ui/src/components/icon.css
  28. 2 2
      packages/ui/src/components/icon.tsx
  29. 55 55
      packages/ui/src/components/image-preview.css
  30. 14 14
      packages/ui/src/components/inline-input.css
  31. 15 15
      packages/ui/src/components/keybind.css
  32. 71 69
      packages/ui/src/components/line-comment.css
  33. 329 323
      packages/ui/src/components/list.css
  34. 9 2
      packages/ui/src/components/list.tsx
  35. 2 2
      packages/ui/src/components/logo.css
  36. 209 207
      packages/ui/src/components/markdown.css
  37. 90 89
      packages/ui/src/components/message-nav.css
  38. 741 741
      packages/ui/src/components/message-part.css
  39. 2 1
      packages/ui/src/components/message-part.tsx
  40. 10 0
      packages/ui/src/components/morph-chevron.css
  41. 73 0
      packages/ui/src/components/morph-chevron.tsx
  42. 127 89
      packages/ui/src/components/popover.css
  43. 7 9
      packages/ui/src/components/progress-circle.css
  44. 16 30
      packages/ui/src/components/progress-circle.tsx
  45. 3 3
      packages/ui/src/components/provider-icon.css
  46. 167 155
      packages/ui/src/components/radio-group.css
  47. 10 0
      packages/ui/src/components/reasoning-icon.css
  48. 32 0
      packages/ui/src/components/reasoning-icon.tsx
  49. 58 56
      packages/ui/src/components/resize-handle.css
  50. 122 0
      packages/ui/src/components/scroll-fade.css
  51. 207 0
      packages/ui/src/components/scroll-fade.tsx
  52. 141 0
      packages/ui/src/components/scroll-reveal.tsx
  53. 156 193
      packages/ui/src/components/select.css
  54. 14 4
      packages/ui/src/components/select.tsx
  55. 214 215
      packages/ui/src/components/session-review.css
  56. 3 2
      packages/ui/src/components/session-review.tsx
  57. 578 570
      packages/ui/src/components/session-turn.css
  58. 1 1
      packages/ui/src/components/session-turn.tsx
  59. 4 4
      packages/ui/src/components/spinner.css
  60. 12 12
      packages/ui/src/components/sticky-accordion-header.css
  61. 131 130
      packages/ui/src/components/switch.css
  62. 452 449
      packages/ui/src/components/tabs.css
  63. 33 30
      packages/ui/src/components/tag.css
  64. 132 132
      packages/ui/src/components/text-field.css
  65. 191 191
      packages/ui/src/components/toast.css
  66. 58 58
      packages/ui/src/components/tooltip.css
  67. 9 9
      packages/ui/src/components/typewriter.css
  68. 24 0
      packages/ui/src/context/dialog.tsx
  69. 2 0
      packages/ui/src/styles/index.css
  70. 139 96
      packages/ui/src/styles/utilities.css

+ 4 - 3
packages/app/src/components/dialog-select-model.tsx

@@ -90,7 +90,7 @@ const ModelList: Component<{
 
 export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
   provider?: string
-  children?: JSX.Element
+  children?: JSX.Element | ((open: boolean) => JSX.Element)
   triggerAs?: T
   triggerProps?: ComponentProps<T>
 }) {
@@ -182,12 +182,13 @@ export function ModelSelectorPopover<T extends ValidComponent = "div">(props: {
         as={props.triggerAs ?? "div"}
         {...(props.triggerProps as any)}
       >
-        {props.children}
+        {typeof props.children === "function" ? props.children(store.open) : 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"
+          data-component="model-popover-content"
           ref={(el) => setStore("content", el)}
-          class="w-72 h-80 flex flex-col p-2 rounded-md border border-border-base bg-surface-raised-stronger-non-alpha shadow-md z-50 outline-none overflow-hidden"
           onEscapeKeyDown={(event) => {
             setStore("dismiss", "escape")
             setStore("open", false)

+ 93 - 55
packages/app/src/components/prompt-input.tsx

@@ -32,7 +32,9 @@ import { useNavigate, useParams } from "@solidjs/router"
 import { useSync } from "@/context/sync"
 import { useComments } from "@/context/comments"
 import { FileIcon } from "@opencode-ai/ui/file-icon"
+import { MorphChevron } from "@opencode-ai/ui/morph-chevron"
 import { Button } from "@opencode-ai/ui/button"
+import { CycleLabel } from "@opencode-ai/ui/cycle-label"
 import { Icon } from "@opencode-ai/ui/icon"
 import { ProviderIcon } from "@opencode-ai/ui/provider-icon"
 import type { IconName } from "@opencode-ai/ui/icons/provider"
@@ -42,6 +44,7 @@ import { Select } from "@opencode-ai/ui/select"
 import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/util/path"
 import { useDialog } from "@opencode-ai/ui/context/dialog"
 import { ImagePreview } from "@opencode-ai/ui/image-preview"
+import { ReasoningIcon } from "@opencode-ai/ui/reasoning-icon"
 import { ModelSelectorPopover } from "@/components/dialog-select-model"
 import { DialogSelectModelUnpaid } from "@/components/dialog-select-model-unpaid"
 import { useProviders } from "@/hooks/use-providers"
@@ -922,7 +925,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       .abort({
         sessionID,
       })
-      .catch(() => {})
+      .catch(() => { })
   }
 
   const addToHistory = (prompt: Prompt, mode: "normal" | "shell") => {
@@ -1252,7 +1255,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       clearInput()
       client.session
         .shell({
-          sessionID: session.id,
+          sessionID: session?.id || "",
           agent,
           model,
           command: text,
@@ -1275,7 +1278,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
         clearInput()
         client.session
           .command({
-            sessionID: session.id,
+            sessionID: session?.id || "",
             command: commandName,
             arguments: args.join(" "),
             agent,
@@ -1348,18 +1351,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
 
     const contextParts: Array<
       | {
-          id: string
-          type: "text"
-          text: string
-          synthetic?: boolean
-        }
+        id: string
+        type: "text"
+        text: string
+        synthetic?: boolean
+      }
       | {
-          id: string
-          type: "file"
-          mime: string
-          url: string
-          filename?: string
-        }
+        id: string
+        type: "file"
+        mime: string
+        url: string
+        filename?: string
+      }
     > = []
 
     const commentNote = (path: string, selection: FileSelection | undefined, comment: string) => {
@@ -1431,13 +1434,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
 
     const optimisticParts = requestParts.map((part) => ({
       ...part,
-      sessionID: session.id,
+      sessionID: session?.id || "",
       messageID,
     })) as unknown as Part[]
 
     const optimisticMessage: Message = {
       id: messageID,
-      sessionID: session.id,
+      sessionID: session?.id || "",
       role: "user",
       time: { created: Date.now() },
       agent,
@@ -1448,9 +1451,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       if (sessionDirectory === projectDirectory) {
         sync.set(
           produce((draft) => {
-            const messages = draft.message[session.id]
+            const messages = draft.message[session?.id || ""]
             if (!messages) {
-              draft.message[session.id] = [optimisticMessage]
+              draft.message[session?.id || ""] = [optimisticMessage]
             } else {
               const result = Binary.search(messages, messageID, (m) => m.id)
               messages.splice(result.index, 0, optimisticMessage)
@@ -1466,9 +1469,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
 
       globalSync.child(sessionDirectory)[1](
         produce((draft) => {
-          const messages = draft.message[session.id]
+          const messages = draft.message[session?.id || ""]
           if (!messages) {
-            draft.message[session.id] = [optimisticMessage]
+            draft.message[session?.id || ""] = [optimisticMessage]
           } else {
             const result = Binary.search(messages, messageID, (m) => m.id)
             messages.splice(result.index, 0, optimisticMessage)
@@ -1485,7 +1488,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       if (sessionDirectory === projectDirectory) {
         sync.set(
           produce((draft) => {
-            const messages = draft.message[session.id]
+            const messages = draft.message[session?.id || ""]
             if (messages) {
               const result = Binary.search(messages, messageID, (m) => m.id)
               if (result.found) messages.splice(result.index, 1)
@@ -1498,7 +1501,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
 
       globalSync.child(sessionDirectory)[1](
         produce((draft) => {
-          const messages = draft.message[session.id]
+          const messages = draft.message[session?.id || ""]
           if (messages) {
             const result = Binary.search(messages, messageID, (m) => m.id)
             if (result.found) messages.splice(result.index, 1)
@@ -1519,15 +1522,15 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       const worktree = WorktreeState.get(sessionDirectory)
       if (!worktree || worktree.status !== "pending") return true
 
-      if (sessionDirectory === projectDirectory) {
-        sync.set("session_status", session.id, { type: "busy" })
+      if (sessionDirectory === projectDirectory && session?.id) {
+        sync.set("session_status", session?.id, { type: "busy" })
       }
 
       const controller = new AbortController()
 
       const cleanup = () => {
-        if (sessionDirectory === projectDirectory) {
-          sync.set("session_status", session.id, { type: "idle" })
+        if (sessionDirectory === projectDirectory && session?.id) {
+          sync.set("session_status", session?.id, { type: "idle" })
         }
         removeOptimisticMessage()
         for (const item of commentItems) {
@@ -1544,7 +1547,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
         restoreInput()
       }
 
-      pending.set(session.id, { abort: controller, cleanup })
+      pending.set(session?.id || "", { abort: controller, cleanup })
 
       const abort = new Promise<Awaited<ReturnType<typeof WorktreeState.wait>>>((resolve) => {
         if (controller.signal.aborted) {
@@ -1572,7 +1575,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
         if (timer.id === undefined) return
         clearTimeout(timer.id)
       })
-      pending.delete(session.id)
+      pending.delete(session?.id || "")
       if (controller.signal.aborted) return false
       if (result.status === "failed") throw new Error(result.message)
       return true
@@ -1582,7 +1585,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
       const ok = await waitForWorktree()
       if (!ok) return
       await client.session.prompt({
-        sessionID: session.id,
+        sessionID: session?.id || "",
         agent,
         model,
         messageID,
@@ -1592,9 +1595,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     }
 
     void send().catch((err) => {
-      pending.delete(session.id)
-      if (sessionDirectory === projectDirectory) {
-        sync.set("session_status", session.id, { type: "idle" })
+      pending.delete(session?.id || "")
+      if (sessionDirectory === projectDirectory && session?.id) {
+        sync.set("session_status", session?.id, { type: "idle" })
       }
       showToast({
         title: language.t("prompt.toast.promptSendFailed.title"),
@@ -1616,6 +1619,28 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
     })
   }
 
+  const currrentModelVariant = createMemo(() => {
+    const modelVariant = local.model.variant.current() ?? ""
+    return modelVariant === "xhigh"
+      ? "xHigh"
+      : modelVariant.length > 0
+        ? modelVariant[0].toUpperCase() + modelVariant.slice(1)
+        : "Default"
+  })
+
+  const reasoningPercentage = createMemo(() => {
+    const variants = local.model.variant.list()
+    const current = local.model.variant.current()
+    const totalEntries = variants.length + 1
+
+    if (totalEntries <= 2 || current === "Default") {
+      return 0
+    }
+
+    const currentIndex = current ? variants.indexOf(current) + 1 : 0
+    return ((currentIndex + 1) / totalEntries) * 100
+  }, [local.model.variant])
+
   return (
     <div class="relative size-full _max-h-[320px] flex flex-col gap-3">
       <Show when={store.popover}>
@@ -1668,7 +1693,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                           </>
                         }
                       >
-                        <Icon name="brain" size="small" class="text-icon-info-active shrink-0" />
+                        <Icon name="brain" size="normal" class="text-icon-info-active shrink-0" />
                         <span class="text-14-regular text-text-strong whitespace-nowrap">
                           @{(item as { type: "agent"; name: string }).name}
                         </span>
@@ -1729,9 +1754,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
         }}
       >
         <Show when={store.dragging}>
-          <div class="absolute inset-0 z-10 flex items-center justify-center bg-surface-raised-stronger-non-alpha/90 pointer-events-none">
+          <div class="absolute inset-0 z-10 flex items-center justify-center bg-surface-raised-stronger-non-alpha/90 mr-1 pointer-events-none">
             <div class="flex flex-col items-center gap-2 text-text-weak">
-              <Icon name="photo" class="size-8" />
+              <Icon name="photo" size={18} class="text-icon-base stroke-1.5" />
               <span class="text-14-regular">{language.t("prompt.dropzone.label")}</span>
             </div>
           </div>
@@ -1770,7 +1795,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                       }}
                     >
                       <div class="flex items-center gap-1.5">
-                        <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-3.5" />
+                        <FileIcon node={{ path: item.path, type: "file" }} class="shrink-0 size-7" />
                         <div class="flex items-center text-11-regular min-w-0 font-medium">
                           <span class="text-text-strong whitespace-nowrap">{getFilenameTruncated(item.path, 14)}</span>
                           <Show when={item.selection}>
@@ -1787,7 +1812,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                           type="button"
                           icon="close-small"
                           variant="ghost"
-                          class="ml-auto h-5 w-5 opacity-0 group-hover:opacity-100 transition-all"
+                          class="ml-auto size-7 opacity-0 group-hover:opacity-100 transition-all"
                           onClick={(e) => {
                             e.stopPropagation()
                             if (item.commentID) comments.remove(item.path, item.commentID)
@@ -1817,7 +1842,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     when={attachment.mime.startsWith("image/")}
                     fallback={
                       <div class="size-16 rounded-md bg-surface-base flex items-center justify-center border border-border-base">
-                        <Icon name="folder" class="size-6 text-text-weak" />
+                        <Icon name="folder" size="normal" class="size-6 text-text-base" />
                       </div>
                     }
                   >
@@ -1891,7 +1916,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
           </Show>
         </div>
         <div class="relative p-3 flex items-center justify-between">
-          <div class="flex items-center justify-start gap-0.5">
+          <div class="flex items-center justify-start gap-2">
             <Switch>
               <Match when={store.mode === "shell"}>
                 <div class="flex items-center gap-2 px-2 h-6">
@@ -1922,12 +1947,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                       title={language.t("command.model.choose")}
                       keybind={command.keybind("model.choose")}
                     >
-                      <Button as="div" variant="ghost" onClick={() => dialog.show(() => <DialogSelectModelUnpaid />)}>
+                      <Button
+                        as="div"
+                        variant="ghost"
+                        class="px-2"
+                        onClick={() => dialog.render(<DialogSelectModelUnpaid />, "select-model")}
+                      >
                         <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" />
+                        <MorphChevron expanded={dialog.isActive("select-model")} />
                       </Button>
                     </TooltipKeybind>
                   }
@@ -1938,11 +1968,15 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     keybind={command.keybind("model.choose")}
                   >
                     <ModelSelectorPopover triggerAs={Button} triggerProps={{ 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" />
+                      {(open) => (
+                        <>
+                          <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")}
+                          <MorphChevron expanded={open} class="text-text-weak" />
+                        </>
+                      )}
                     </ModelSelectorPopover>
                   </TooltipKeybind>
                 </Show>
@@ -1955,10 +1989,13 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                     <Button
                       data-action="model-variant-cycle"
                       variant="ghost"
-                      class="text-text-base _hidden group-hover/prompt-input:inline-block capitalize text-12-regular"
+                      class="text-text-strong text-12-regular"
                       onClick={() => local.model.variant.cycle()}
                     >
-                      {local.model.variant.current() ?? language.t("common.default")}
+                      <Show when={local.model.variant.list().length > 1}>
+                        <ReasoningIcon percentage={reasoningPercentage()} size={16} strokeWidth={1.25} />
+                      </Show>
+                      <CycleLabel value={currrentModelVariant()} />
                     </Button>
                   </TooltipKeybind>
                 </Show>
@@ -1972,7 +2009,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                       variant="ghost"
                       onClick={() => permission.toggleAutoAccept(params.id!, sdk.directory)}
                       classList={{
-                        "_hidden group-hover/prompt-input:flex size-6 items-center justify-center": true,
+                        "_hidden group-hover/prompt-input:flex items-center justify-center": true,
                         "text-text-base": !permission.isAutoAccepting(params.id!, sdk.directory),
                         "hover:bg-surface-success-base": permission.isAutoAccepting(params.id!, sdk.directory),
                       }}
@@ -1994,7 +2031,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
               </Match>
             </Switch>
           </div>
-          <div class="flex items-center gap-3 absolute right-3 bottom-3">
+          <div class="flex items-center gap-1 absolute right-3 bottom-3">
             <input
               ref={fileInputRef}
               type="file"
@@ -2006,18 +2043,19 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                 e.currentTarget.value = ""
               }}
             />
-            <div class="flex items-center gap-2">
+            <div class="flex items-center gap-1.5 mr-1.5">
               <SessionContextUsage />
               <Show when={store.mode === "normal"}>
                 <Tooltip placement="top" value={language.t("prompt.action.attachFile")}>
                   <Button
                     type="button"
                     variant="ghost"
-                    class="size-6"
+                    size="small"
+                    class="px-1"
                     onClick={() => fileInputRef.click()}
                     aria-label={language.t("prompt.action.attachFile")}
                   >
-                    <Icon name="photo" class="size-4.5" />
+                    <Icon name="photo" class="size-6 text-icon-base" />
                   </Button>
                 </Tooltip>
               </Show>
@@ -2036,7 +2074,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                   <Match when={true}>
                     <div class="flex items-center gap-2">
                       <span>{language.t("prompt.action.send")}</span>
-                      <Icon name="enter" size="small" class="text-icon-base" />
+                      <Icon name="enter" size="normal" class="text-icon-base" />
                     </div>
                   </Match>
                 </Switch>
@@ -2047,7 +2085,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                 disabled={!prompt.dirty() && !working()}
                 icon={working() ? "stop" : "arrow-up"}
                 variant="primary"
-                class="h-6 w-4.5"
+                class="h-6 w-5.5"
                 aria-label={working() ? language.t("prompt.action.stop") : language.t("prompt.action.send")}
               />
             </Tooltip>

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

@@ -64,8 +64,8 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
   }
 
   const circle = () => (
-    <div class="p-1">
-      <ProgressCircle size={16} strokeWidth={2} percentage={context()?.percentage ?? 0} />
+    <div class="text-icon-base">
+      <ProgressCircle size={18} percentage={context()?.percentage ?? 0} />
     </div>
   )
 
@@ -101,7 +101,7 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
             <Button
               type="button"
               variant="ghost"
-              class="size-6"
+              class="size-7 text-icon-base"
               onClick={openContext}
               aria-label={language.t("context.usage.view")}
             >

+ 24 - 18
packages/app/src/components/settings-general.tsx

@@ -10,6 +10,7 @@ import { usePlatform } from "@/context/platform"
 import { useSettings, monoFontFamily } from "@/context/settings"
 import { playSound, SOUND_OPTIONS } from "@/utils/sound"
 import { Link } from "./link"
+import { ScrollFade } from "@opencode-ai/ui/scroll-fade"
 
 let demoSoundState = {
   cleanup: undefined as (() => void) | undefined,
@@ -60,24 +61,24 @@ export const SettingsGeneral: Component = () => {
         const actions =
           platform.update && platform.restart
             ? [
-                {
-                  label: language.t("toast.update.action.installRestart"),
-                  onClick: async () => {
-                    await platform.update!()
-                    await platform.restart!()
-                  },
+              {
+                label: language.t("toast.update.action.installRestart"),
+                onClick: async () => {
+                  await platform.update!()
+                  await platform.restart!()
                 },
-                {
-                  label: language.t("toast.update.action.notYet"),
-                  onClick: "dismiss" as const,
-                },
-              ]
+              },
+              {
+                label: language.t("toast.update.action.notYet"),
+                onClick: "dismiss" as const,
+              },
+            ]
             : [
-                {
-                  label: language.t("toast.update.action.notYet"),
-                  onClick: "dismiss" as const,
-                },
-              ]
+              {
+                label: language.t("toast.update.action.notYet"),
+                onClick: "dismiss" as const,
+              },
+            ]
 
         showToast({
           persistent: true,
@@ -130,7 +131,12 @@ export const SettingsGeneral: Component = () => {
   const soundOptions = [...SOUND_OPTIONS]
 
   return (
-    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
+    <ScrollFade
+      direction="vertical"
+      fadeStartSize={0}
+      fadeEndSize={16}
+      class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10"
+    >
       <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
         <div class="flex flex-col gap-1 pt-6 pb-8">
           <h2 class="text-16-medium text-text-strong">{language.t("settings.tab.general")}</h2>
@@ -394,7 +400,7 @@ export const SettingsGeneral: Component = () => {
           </div>
         </div>
       </div>
-    </div>
+    </ScrollFade>
   )
 }
 

+ 8 - 2
packages/app/src/components/settings-keybinds.tsx

@@ -9,6 +9,7 @@ import fuzzysort from "fuzzysort"
 import { formatKeybind, parseKeybind, useCommand } from "@/context/command"
 import { useLanguage } from "@/context/language"
 import { useSettings } from "@/context/settings"
+import { ScrollFade } from "@opencode-ai/ui/scroll-fade"
 
 const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
 const PALETTE_ID = "command.palette"
@@ -352,7 +353,12 @@ export const SettingsKeybinds: Component = () => {
   })
 
   return (
-    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10">
+    <ScrollFade
+      direction="vertical"
+      fadeStartSize={0}
+      fadeEndSize={16}
+      class="flex flex-col h-full overflow-y-auto no-scrollbar px-4 pb-10 sm:px-10 sm:pb-10"
+    >
       <div class="sticky top-0 z-10 bg-[linear-gradient(to_bottom,var(--surface-raised-stronger-non-alpha)_calc(100%_-_24px),transparent)]">
         <div class="flex flex-col gap-4 pt-6 pb-6 max-w-[720px]">
           <div class="flex items-center justify-between gap-4">
@@ -429,6 +435,6 @@ export const SettingsKeybinds: Component = () => {
           </div>
         </Show>
       </div>
-    </div>
+    </ScrollFade>
   )
 }

+ 1 - 0
packages/desktop/src-tauri/src/lib.rs

@@ -345,6 +345,7 @@ pub fn run() {
                 .decorations(false);
 
             let window = window_builder.build().expect("Failed to create window");
+            let _ = window.show();
 
             #[cfg(windows)]
             let _ = window.create_overlay_titlebar();

+ 94 - 85
packages/ui/src/components/accordion.css

@@ -1,98 +1,107 @@
 [data-component="accordion"] {
-  display: flex;
-  flex-direction: column;
-  align-items: flex-start;
-  gap: 8px;
-  align-self: stretch;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	gap: 8px;
+	align-self: stretch;
 
-  [data-slot="accordion-item"] {
-    width: 100%;
-    display: flex;
-    flex-direction: column;
-    align-items: flex-start;
-    align-self: stretch;
-    overflow: clip;
+	[data-slot="accordion-item"] {
+		width: 100%;
+		display: flex;
+		flex-direction: column;
+		align-items: flex-start;
+		align-self: stretch;
+		overflow: clip;
 
-    [data-slot="accordion-header"] {
-      width: 100%;
-      display: flex;
-      align-items: center;
-      margin: 0;
-      padding: 0;
+		[data-slot="accordion-header"] {
+			width: 100%;
+			display: flex;
+			align-items: center;
+			margin: 0;
+			padding: 0;
 
-      [data-slot="accordion-trigger"] {
-        width: 100%;
-        display: flex;
-        height: 32px;
-        padding: 8px 12px;
-        justify-content: space-between;
-        align-items: center;
-        align-self: stretch;
-        cursor: default;
-        user-select: none;
+			[data-slot="accordion-trigger"] {
+				width: 100%;
+				display: flex;
+				height: 32px;
+				padding: 8px 12px;
+				justify-content: space-between;
+				align-items: center;
+				align-self: stretch;
+				cursor: default;
+				user-select: none;
 
-        background-color: var(--surface-base);
-        border: 1px solid var(--border-weak-base);
-        border-radius: var(--radius-md);
-        overflow: clip;
-        color: var(--text-strong);
-        transition: background-color 0.15s ease;
+				background-color: var(--surface-base);
+				border: 1px solid var(--border-weak-base);
+				border-radius: var(--radius-md);
+				overflow: clip;
+				color: var(--text-strong);
+				transition-property: background-color, border-color;
+				transition-duration: var(--transition-duration);
+				transition-timing-function: var(--transition-easing);
 
-        /* text-12-regular */
-        font-family: var(--font-family-sans);
-        font-size: var(--font-size-small);
-        font-style: normal;
-        font-weight: var(--font-weight-regular);
-        line-height: var(--line-height-large); /* 166.667% */
-        letter-spacing: var(--letter-spacing-normal);
+				/* text-12-regular */
+				font-family: var(--font-family-sans);
+				font-size: var(--font-size-small);
+				font-style: normal;
+				font-weight: var(--font-weight-regular);
+				line-height: var(--line-height-large); /* 166.667% */
+				letter-spacing: var(--letter-spacing-normal);
 
-        &:hover {
-          background-color: var(--surface-base);
-        }
-        &:focus-visible {
-          outline: none;
-        }
-        &[data-disabled] {
-          cursor: not-allowed;
-        }
-      }
-    }
+				&:hover {
+					background-color: var(--surface-base);
+				}
+				&:focus-visible {
+					outline: none;
+				}
+				&[data-disabled] {
+					cursor: not-allowed;
+				}
+			}
+		}
 
-    &[data-expanded] {
-      [data-slot="accordion-trigger"] {
-        border-bottom-left-radius: 0;
-        border-bottom-right-radius: 0;
-      }
+		[data-slot="accordion-arrow"] {
+			flex-shrink: 0;
+			width: 16px;
+			height: 16px;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			color: var(--text-weak);
+		}
 
-      [data-slot="accordion-content"] {
-        border: 1px solid var(--border-weak-base);
-        border-top: none;
-        border-bottom-left-radius: var(--radius-md);
-        border-bottom-right-radius: var(--radius-md);
-      }
-    }
+		[data-slot="accordion-content"] {
+			display: grid;
+			grid-template-rows: 0fr;
+			transition-property: grid-template-rows, opacity;
+			transition-duration: var(--transition-duration);
+			transition-timing-function: var(--transition-easing);
+			width: 100%;
 
-    [data-slot="accordion-content"] {
-      overflow: hidden;
-      width: 100%;
-    }
-  }
-}
+			> * {
+				overflow: hidden;
+			}
+		}
 
-@keyframes slideDown {
-  from {
-    height: 0;
-  }
-  to {
-    height: var(--kb-accordion-content-height);
-  }
-}
+		[data-slot="accordion-content"][data-expanded] {
+			grid-template-rows: 1fr;
+		}
+
+		[data-slot="accordion-content"][data-closed] {
+			grid-template-rows: 0fr;
+		}
+
+		&[data-expanded] [data-slot="accordion-trigger"] {
+			border-bottom-left-radius: 0;
+			border-bottom-right-radius: 0;
+		}
 
-@keyframes slideUp {
-  from {
-    height: var(--kb-accordion-content-height);
-  }
-  to {
-    height: 0;
-  }
+		&[data-expanded] [data-slot="accordion-content"] {
+			border: 1px solid var(--border-weak-base);
+			border-top: none;
+			border-bottom-left-radius: var(--radius-md);
+			border-bottom-right-radius: var(--radius-md);
+			height: auto;
+		}
+	}
 }

+ 32 - 11
packages/ui/src/components/accordion.tsx

@@ -1,6 +1,7 @@
 import { Accordion as Kobalte } from "@kobalte/core/accordion"
-import { splitProps } from "solid-js"
+import { Accessor, createContext, splitProps, useContext } from "solid-js"
 import type { ComponentProps, ParentProps } from "solid-js"
+import { MorphChevron } from "./morph-chevron"
 
 export interface AccordionProps extends ComponentProps<typeof Kobalte> {}
 export interface AccordionItemProps extends ComponentProps<typeof Kobalte.Item> {}
@@ -8,6 +9,8 @@ export interface AccordionHeaderProps extends ComponentProps<typeof Kobalte.Head
 export interface AccordionTriggerProps extends ComponentProps<typeof Kobalte.Trigger> {}
 export interface AccordionContentProps extends ComponentProps<typeof Kobalte.Content> {}
 
+const AccordionItemContext = createContext<Accessor<boolean>>()
+
 function AccordionRoot(props: AccordionProps) {
   const [split, rest] = splitProps(props, ["class", "classList"])
   return (
@@ -22,17 +25,19 @@ function AccordionRoot(props: AccordionProps) {
   )
 }
 
-function AccordionItem(props: AccordionItemProps) {
-  const [split, rest] = splitProps(props, ["class", "classList"])
+function AccordionItem(props: AccordionItemProps & { expanded?: boolean }) {
+  const [split, rest] = splitProps(props, ["class", "classList", "expanded"])
   return (
-    <Kobalte.Item
-      {...rest}
-      data-slot="accordion-item"
-      classList={{
-        ...(split.classList ?? {}),
-        [split.class ?? ""]: !!split.class,
-      }}
-    />
+    <AccordionItemContext.Provider value={() => split.expanded ?? false}>
+      <Kobalte.Item
+        {...rest}
+        data-slot="accordion-item"
+        classList={{
+          ...(split.classList ?? {}),
+          [split.class ?? ""]: !!split.class,
+        }}
+      />
+    </AccordionItemContext.Provider>
   )
 }
 
@@ -84,9 +89,25 @@ function AccordionContent(props: ParentProps<AccordionContentProps>) {
   )
 }
 
+export interface AccordionArrowProps extends ComponentProps<"div"> {
+  expanded?: boolean
+}
+
+function AccordionArrow(props: AccordionArrowProps = {}) {
+  const [local, rest] = splitProps(props, ["expanded"])
+  const contextExpanded = useContext(AccordionItemContext)
+  const isExpanded = () => local.expanded ?? contextExpanded?.() ?? false
+  return (
+    <div data-slot="accordion-arrow" {...rest}>
+      <MorphChevron expanded={isExpanded()} />
+    </div>
+  )
+}
+
 export const Accordion = Object.assign(AccordionRoot, {
   Item: AccordionItem,
   Header: AccordionHeader,
   Trigger: AccordionTrigger,
   Content: AccordionContent,
+  Arrow: AccordionArrow,
 })

+ 32 - 32
packages/ui/src/components/avatar.css

@@ -1,49 +1,49 @@
 [data-component="avatar"] {
-  --avatar-bg: var(--color-surface-info-base);
-  --avatar-fg: var(--color-text-base);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-  border-radius: var(--radius-sm);
-  border: 1px solid var(--color-border-weak-base);
-  font-family: var(--font-mono);
-  font-weight: 500;
-  text-transform: uppercase;
-  background-color: var(--avatar-bg);
-  color: var(--avatar-fg);
+	--avatar-bg: var(--color-surface-info-base);
+	--avatar-fg: var(--color-text-base);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex-shrink: 0;
+	border-radius: var(--radius-sm);
+	border: 1px solid var(--color-border-weak-base);
+	font-family: var(--font-mono);
+	font-weight: 500;
+	text-transform: uppercase;
+	background-color: var(--avatar-bg);
+	color: var(--avatar-fg);
 }
 
 [data-component="avatar"][data-has-image] {
-  background-color: transparent;
-  border: none;
+	background-color: transparent;
+	border: none;
 }
 
 [data-component="avatar"][data-size="small"] {
-  width: 1.25rem;
-  height: 1.25rem;
-  font-size: 0.75rem;
-  line-height: 1;
+	width: 1.25rem;
+	height: 1.25rem;
+	font-size: 0.75rem;
+	line-height: 1;
 }
 
 [data-component="avatar"][data-size="normal"] {
-  width: 1.5rem;
-  height: 1.5rem;
-  font-size: 1.125rem;
-  line-height: 1.5rem;
+	width: 1.5rem;
+	height: 1.5rem;
+	font-size: 1.125rem;
+	line-height: 1.5rem;
 }
 
 [data-component="avatar"][data-size="large"] {
-  width: 2rem;
-  height: 2rem;
-  font-size: 1.25rem;
-  line-height: 2rem;
+	width: 2rem;
+	height: 2rem;
+	font-size: 1.25rem;
+	line-height: 2rem;
 }
 
 [data-component="avatar"] [data-slot="avatar-image"] {
-  width: 100%;
-  height: 100%;
-  display: block;
-  object-fit: cover;
-  border-radius: inherit;
+	width: 100%;
+	height: 100%;
+	display: block;
+	object-fit: cover;
+	border-radius: inherit;
 }

+ 84 - 84
packages/ui/src/components/basic-tool.css

@@ -1,97 +1,97 @@
 [data-component="tool-trigger"] {
-  content-visibility: auto;
-  width: 100%;
-  display: flex;
-  align-items: center;
-  align-self: stretch;
-  gap: 20px;
-  justify-content: space-between;
+	content-visibility: auto;
+	width: 100%;
+	display: flex;
+	align-items: center;
+	align-self: stretch;
+	gap: 20px;
+	justify-content: space-between;
 
-  [data-slot="basic-tool-tool-trigger-content"] {
-    width: 100%;
-    display: flex;
-    align-items: center;
-    align-self: stretch;
-    gap: 20px;
-  }
+	[data-slot="basic-tool-tool-trigger-content"] {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		align-self: stretch;
+		gap: 20px;
+	}
 
-  [data-slot="icon-svg"] {
-    flex-shrink: 0;
-  }
+	[data-slot="icon-svg"] {
+		flex-shrink: 0;
+	}
 
-  [data-slot="basic-tool-tool-info"] {
-    flex-grow: 1;
-    min-width: 0;
-  }
+	[data-slot="basic-tool-tool-info"] {
+		flex-grow: 1;
+		min-width: 0;
+	}
 
-  [data-slot="basic-tool-tool-info-structured"] {
-    width: 100%;
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    justify-content: space-between;
-  }
+	[data-slot="basic-tool-tool-info-structured"] {
+		width: 100%;
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		justify-content: space-between;
+	}
 
-  [data-slot="basic-tool-tool-info-main"] {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    min-width: 0;
-    overflow: hidden;
-  }
+	[data-slot="basic-tool-tool-info-main"] {
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		min-width: 0;
+		overflow: hidden;
+	}
 
-  [data-slot="basic-tool-tool-title"] {
-    flex-shrink: 0;
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-    color: var(--text-base);
+	[data-slot="basic-tool-tool-title"] {
+		flex-shrink: 0;
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+		color: var(--text-base);
 
-    &.capitalize {
-      text-transform: capitalize;
-    }
-  }
+		&.capitalize {
+			text-transform: capitalize;
+		}
+	}
 
-  [data-slot="basic-tool-tool-subtitle"] {
-    flex-shrink: 1;
-    min-width: 0;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-    color: var(--text-weak);
+	[data-slot="basic-tool-tool-subtitle"] {
+		flex-shrink: 1;
+		min-width: 0;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+		color: var(--text-weak);
 
-    &.clickable {
-      cursor: pointer;
-      text-decoration: underline;
-      transition: color 0.15s ease;
+		&.clickable {
+			cursor: pointer;
+			text-decoration: underline;
+			transition: color 0.15s ease;
 
-      &:hover {
-        color: var(--text-base);
-      }
-    }
-  }
+			&:hover {
+				color: var(--text-base);
+			}
+		}
+	}
 
-  [data-slot="basic-tool-tool-arg"] {
-    flex-shrink: 1;
-    min-width: 0;
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-    color: var(--text-weak);
-  }
+	[data-slot="basic-tool-tool-arg"] {
+		flex-shrink: 1;
+		min-width: 0;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+		color: var(--text-weak);
+	}
 }

+ 166 - 170
packages/ui/src/components/button.css

@@ -1,172 +1,168 @@
 [data-component="button"] {
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  border-style: solid;
-  border-width: 1px;
-  border-radius: var(--radius-md);
-  text-decoration: none;
-  user-select: none;
-  cursor: default;
-  outline: none;
-  white-space: nowrap;
-
-  &[data-variant="primary"] {
-    background-color: var(--button-primary-base);
-    border-color: var(--border-weak-base);
-    color: var(--icon-invert-base);
-
-    [data-slot="icon-svg"] {
-      color: var(--icon-invert-base);
-    }
-
-    &:hover:not(:disabled) {
-      background-color: var(--icon-strong-hover);
-    }
-    &:focus:not(:disabled) {
-      background-color: var(--icon-strong-focus);
-    }
-    &:active:not(:disabled) {
-      background-color: var(--icon-strong-active);
-    }
-    &:disabled {
-      background-color: var(--icon-strong-disabled);
-
-      [data-slot="icon-svg"] {
-        color: var(--icon-invert-base);
-      }
-    }
-  }
-
-  &[data-variant="ghost"] {
-    border-color: transparent;
-    background-color: transparent;
-    color: var(--text-strong);
-
-    [data-slot="icon-svg"] {
-      color: var(--icon-base);
-    }
-
-    &:hover:not(:disabled) {
-      background-color: var(--surface-raised-base-hover);
-    }
-    &:focus-visible:not(:disabled) {
-      background-color: var(--surface-raised-base-hover);
-    }
-    &:active:not(:disabled) {
-      background-color: var(--surface-raised-base-active);
-    }
-    &:disabled {
-      color: var(--text-weak);
-      cursor: not-allowed;
-
-      [data-slot="icon-svg"] {
-        color: var(--icon-disabled);
-      }
-    }
-    &[data-selected="true"]:not(:disabled) {
-      background-color: var(--surface-raised-base-hover);
-    }
-    &[data-active="true"] {
-      background-color: var(--surface-raised-base-active);
-    }
-  }
-
-  &[data-variant="secondary"] {
-    border: transparent;
-    background-color: var(--button-secondary-base);
-    color: var(--text-strong);
-    box-shadow: var(--shadow-xs-border);
-
-    &:hover:not(:disabled) {
-      background-color: var(--button-secondary-hover);
-    }
-    &:focus:not(:disabled) {
-      background-color: var(--button-secondary-base);
-    }
-    &:focus-visible:not(:active) {
-      background-color: var(--button-secondary-base);
-      box-shadow: var(--shadow-xs-border-focus);
-    }
-    &:focus-visible:active {
-      box-shadow: none;
-    }
-    &:active:not(:disabled) {
-      background-color: var(--button-secondary-base);
-      scale: 0.99;
-      transition: all 150ms ease-out;
-    }
-    &:disabled {
-      border-color: var(--border-disabled);
-      background-color: var(--surface-disabled);
-      color: var(--text-weak);
-      cursor: not-allowed;
-    }
-
-    [data-slot="icon-svg"] {
-      color: var(--icon-strong-base);
-    }
-  }
-
-  &[data-size="small"] {
-    height: 22px;
-    padding: 0 8px;
-    &[data-icon] {
-      padding: 0 12px 0 4px;
-    }
-
-    font-size: var(--font-size-small);
-    line-height: var(--line-height-large);
-    gap: 4px;
-
-    /* text-12-medium */
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large); /* 166.667% */
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  &[data-size="normal"] {
-    height: 24px;
-    line-height: 24px;
-    padding: 0 6px;
-    &[data-icon] {
-      padding: 0 12px 0 4px;
-    }
-
-    font-size: var(--font-size-small);
-    gap: 6px;
-
-    /* text-12-medium */
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  &[data-size="large"] {
-    height: 32px;
-    padding: 6px 12px;
-
-    &[data-icon] {
-      padding: 0 12px 0 8px;
-    }
-
-    gap: 4px;
-
-    /* text-14-medium */
-    font-family: var(--font-family-sans);
-    font-size: 14px;
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large); /* 142.857% */
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  &:focus {
-    outline: none;
-  }
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	border-style: solid;
+	border-width: 1px;
+	border-radius: var(--radius-md);
+	text-decoration: none;
+	user-select: none;
+	cursor: default;
+	padding: 4px 8px;
+	white-space: nowrap;
+	transition-property:
+		background-color, border-color, color, box-shadow, opacity;
+	transition-duration: var(--transition-duration);
+	transition-timing-function: var(--transition-easing);
+	outline: none;
+	line-height: 20px;
+
+	&[data-variant="primary"] {
+		background-color: var(--button-primary-base);
+		border-color: var(--border-weak-base);
+		color: var(--icon-invert-base);
+
+		[data-slot="icon-svg"] {
+			color: var(--icon-invert-base);
+		}
+
+		&:hover:not(:disabled) {
+			background-color: var(--icon-strong-hover);
+		}
+		&:focus:not(:disabled) {
+			background-color: var(--icon-strong-focus);
+		}
+		&:active:not(:disabled) {
+			background-color: var(--icon-strong-active);
+		}
+		&:disabled {
+			background-color: var(--icon-strong-disabled);
+
+			[data-slot="icon-svg"] {
+				color: var(--icon-invert-base);
+			}
+		}
+	}
+
+	&[data-variant="ghost"] {
+		border-color: transparent;
+		background-color: transparent;
+		color: var(--text-strong);
+
+		[data-slot="icon-svg"] {
+			color: var(--icon-base);
+		}
+
+		&:hover:not(:disabled) {
+			background-color: var(--surface-raised-base-hover);
+		}
+		&:focus-visible:not(:disabled) {
+			background-color: var(--surface-raised-base-hover);
+		}
+		&:active:not(:disabled) {
+			background-color: var(--surface-raised-base-active);
+		}
+		&:disabled {
+			color: var(--text-weak);
+			cursor: not-allowed;
+
+			[data-slot="icon-svg"] {
+				color: var(--icon-disabled);
+			}
+		}
+		&[data-selected="true"]:not(:disabled) {
+			background-color: var(--surface-raised-base-hover);
+		}
+		&[data-active="true"] {
+			background-color: var(--surface-raised-base-active);
+		}
+	}
+
+	&[data-variant="secondary"] {
+		border: transparent;
+		background-color: var(--button-secondary-base);
+		color: var(--text-strong);
+		box-shadow: var(--shadow-xs-border);
+
+		&:hover:not(:disabled) {
+			background-color: var(--button-secondary-hover);
+		}
+		&:focus:not(:disabled) {
+			background-color: var(--button-secondary-base);
+		}
+		&:focus-visible:not(:active) {
+			background-color: var(--button-secondary-base);
+			box-shadow: var(--shadow-xs-border-focus);
+		}
+		&:focus-visible:active {
+			box-shadow: none;
+		}
+		&:active:not(:disabled) {
+			background-color: var(--button-secondary-base);
+			scale: 0.99;
+		}
+		&:disabled {
+			border-color: var(--border-disabled);
+			background-color: var(--surface-disabled);
+			color: var(--text-weak);
+			cursor: not-allowed;
+		}
+
+		[data-slot="icon-svg"] {
+			color: var(--icon-strong-base);
+		}
+	}
+
+	&[data-size="small"] {
+		padding: 2px 8px;
+		&[data-icon] {
+			padding: 2px 12px 2px 4px;
+		}
+
+		gap: 4px;
+
+		/* text-12-medium */
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-base);
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	&[data-size="normal"] {
+		padding: 4px 6px;
+		&[data-icon] {
+			padding: 4px 12px 4px 4px;
+		}
+
+		gap: 6px;
+
+		/* text-12-medium */
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	&[data-size="large"] {
+		padding: 6px 12px;
+
+		&[data-icon] {
+			padding: 6px 12px 6px 8px;
+		}
+
+		gap: 4px;
+
+		/* text-14-medium */
+		font-family: var(--font-family-sans);
+		font-size: 14px;
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	&:focus {
+		outline: none;
+	}
 }

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

@@ -4,7 +4,7 @@ import { Icon, IconProps } from "./icon"
 
 export interface ButtonProps
   extends ComponentProps<typeof Kobalte>,
-    Pick<ComponentProps<"button">, "class" | "classList" | "children"> {
+    Pick<ComponentProps<"button">, "class" | "classList" | "children" | "style"> {
   size?: "small" | "normal" | "large"
   variant?: "primary" | "secondary" | "ghost"
   icon?: IconProps["name"]

+ 26 - 24
packages/ui/src/components/card.css

@@ -1,29 +1,31 @@
 [data-component="card"] {
-  width: 100%;
-  display: flex;
-  flex-direction: column;
-  background-color: var(--surface-inset-base);
-  border: 1px solid var(--border-weaker-base);
-  transition: background-color 0.15s ease;
-  border-radius: var(--radius-md);
-  padding: 6px 12px;
-  overflow: clip;
+	width: 100%;
+	display: flex;
+	flex-direction: column;
+	background-color: var(--surface-inset-base);
+	border: 1px solid var(--border-weaker-base);
+	transition-property: background-color, border-color;
+	transition-duration: var(--transition-duration);
+	transition-timing-function: var(--transition-easing);
+	border-radius: var(--radius-md);
+	padding: 6px 12px;
+	overflow: clip;
 
-  &[data-variant="error"] {
-    background-color: var(--surface-critical-weak);
-    border: 1px solid var(--border-critical-base);
-    color: rgba(218, 51, 25, 0.6);
+	&[data-variant="error"] {
+		background-color: var(--surface-critical-weak);
+		border: 1px solid var(--border-critical-base);
+		color: rgba(218, 51, 25, 0.6);
 
-    /* text-12-regular */
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-large); /* 166.667% */
-    letter-spacing: var(--letter-spacing-normal);
+		/* text-12-regular */
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-large); /* 166.667% */
+		letter-spacing: var(--letter-spacing-normal);
 
-    &[data-component="icon"] {
-      color: var(--icon-critical-active);
-    }
-  }
+		&[data-component="icon"] {
+			color: var(--icon-critical-active);
+		}
+	}
 }

+ 134 - 119
packages/ui/src/components/checkbox.css

@@ -1,121 +1,136 @@
 [data-component="checkbox"] {
-  display: flex;
-  align-items: center;
-  gap: 12px;
-  cursor: default;
-
-  [data-slot="checkbox-checkbox-input"] {
-    position: absolute;
-    width: 1px;
-    height: 1px;
-    padding: 0;
-    margin: -1px;
-    overflow: hidden;
-    clip: rect(0, 0, 0, 0);
-    white-space: nowrap;
-    border-width: 0;
-  }
-
-  [data-slot="checkbox-checkbox-control"] {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    width: 16px;
-    height: 16px;
-    padding: 2px;
-    aspect-ratio: 1;
-    flex-shrink: 0;
-    border-radius: var(--radius-sm);
-    border: 1px solid var(--border-weak-base);
-    /* background-color: var(--surface-weak); */
-  }
-
-  [data-slot="checkbox-checkbox-indicator"] {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    width: 100%;
-    height: 100%;
-    color: var(--icon-base);
-    opacity: 0;
-  }
-
-  /* [data-slot="checkbox-checkbox-content"] { */
-  /* } */
-
-  [data-slot="checkbox-checkbox-label"] {
-    user-select: none;
-    color: var(--text-base);
-
-    /* text-12-regular */
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-large); /* 166.667% */
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  [data-slot="checkbox-checkbox-description"] {
-    color: var(--text-base);
-    font-family: var(--font-family-sans);
-    font-size: 12px;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-normal);
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  [data-slot="checkbox-checkbox-error"] {
-    color: var(--text-error);
-    font-family: var(--font-family-sans);
-    font-size: 12px;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-normal);
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  &:hover:not([data-disabled], [data-readonly]) [data-slot="checkbox-checkbox-control"] {
-    border-color: var(--border-hover);
-    background-color: var(--surface-hover);
-  }
-
-  &:focus-within:not([data-readonly]) [data-slot="checkbox-checkbox-control"] {
-    border-color: var(--border-focus);
-    box-shadow: 0 0 0 2px var(--surface-focus);
-  }
-
-  &[data-checked] [data-slot="checkbox-checkbox-control"],
-  &[data-indeterminate] [data-slot="checkbox-checkbox-control"] {
-    border-color: var(--border-base);
-    background-color: var(--surface-weak);
-  }
-
-  &[data-checked]:hover:not([data-disabled], [data-readonly]) [data-slot="checkbox-checkbox-control"],
-  &[data-indeterminate]:hover:not([data-disabled]) [data-slot="checkbox-checkbox-control"] {
-    border-color: var(--border-hover);
-    background-color: var(--surface-hover);
-  }
-
-  &[data-checked] [data-slot="checkbox-checkbox-indicator"],
-  &[data-indeterminate] [data-slot="checkbox-checkbox-indicator"] {
-    opacity: 1;
-  }
-
-  &[data-disabled] {
-    cursor: not-allowed;
-  }
-
-  &[data-disabled] [data-slot="checkbox-checkbox-control"] {
-    border-color: var(--border-disabled);
-    background-color: var(--surface-disabled);
-  }
-
-  &[data-invalid] [data-slot="checkbox-checkbox-control"] {
-    border-color: var(--border-error);
-  }
-
-  &[data-readonly] {
-    cursor: default;
-    pointer-events: none;
-  }
+	display: flex;
+	align-items: center;
+	gap: 12px;
+	cursor: default;
+
+	[data-slot="checkbox-checkbox-control"] {
+		transition-property: border-color, background-color, box-shadow;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+	}
+
+	[data-slot="checkbox-checkbox-indicator"] {
+		transition-property: opacity;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+	}
+
+	[data-slot="checkbox-checkbox-input"] {
+		position: absolute;
+		width: 1px;
+		height: 1px;
+		padding: 0;
+		margin: -1px;
+		overflow: hidden;
+		clip: rect(0, 0, 0, 0);
+		white-space: nowrap;
+		border-width: 0;
+	}
+
+	[data-slot="checkbox-checkbox-control"] {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 16px;
+		height: 16px;
+		padding: 2px;
+		aspect-ratio: 1;
+		flex-shrink: 0;
+		border-radius: var(--radius-sm);
+		border: 1px solid var(--border-weak-base);
+		/* background-color: var(--surface-weak); */
+	}
+
+	[data-slot="checkbox-checkbox-indicator"] {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 100%;
+		height: 100%;
+		color: var(--icon-base);
+		opacity: 0;
+	}
+
+	/* [data-slot="checkbox-checkbox-content"] { */
+	/* } */
+
+	[data-slot="checkbox-checkbox-label"] {
+		user-select: none;
+		color: var(--text-base);
+
+		/* text-12-regular */
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-large); /* 166.667% */
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	[data-slot="checkbox-checkbox-description"] {
+		color: var(--text-base);
+		font-family: var(--font-family-sans);
+		font-size: 12px;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-normal);
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	[data-slot="checkbox-checkbox-error"] {
+		color: var(--text-error);
+		font-family: var(--font-family-sans);
+		font-size: 12px;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-normal);
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	&:hover:not([data-disabled], [data-readonly])
+		[data-slot="checkbox-checkbox-control"] {
+		border-color: var(--border-hover);
+		background-color: var(--surface-hover);
+	}
+
+	&:focus-within:not([data-readonly]) [data-slot="checkbox-checkbox-control"] {
+		border-color: var(--border-focus);
+		box-shadow: 0 0 0 2px var(--surface-focus);
+	}
+
+	&[data-checked] [data-slot="checkbox-checkbox-control"],
+	&[data-indeterminate] [data-slot="checkbox-checkbox-control"] {
+		border-color: var(--border-base);
+		background-color: var(--surface-weak);
+	}
+
+	&[data-checked]:hover:not([data-disabled], [data-readonly])
+		[data-slot="checkbox-checkbox-control"],
+	&[data-indeterminate]:hover:not([data-disabled])
+		[data-slot="checkbox-checkbox-control"] {
+		border-color: var(--border-hover);
+		background-color: var(--surface-hover);
+	}
+
+	&[data-checked] [data-slot="checkbox-checkbox-indicator"],
+	&[data-indeterminate] [data-slot="checkbox-checkbox-indicator"] {
+		opacity: 1;
+	}
+
+	&[data-disabled] {
+		cursor: not-allowed;
+	}
+
+	&[data-disabled] [data-slot="checkbox-checkbox-control"] {
+		border-color: var(--border-disabled);
+		background-color: var(--surface-disabled);
+	}
+
+	&[data-invalid] [data-slot="checkbox-checkbox-control"] {
+		border-color: var(--border-error);
+	}
+
+	&[data-readonly] {
+		cursor: default;
+		pointer-events: none;
+	}
 }

+ 2 - 2
packages/ui/src/components/code.css

@@ -1,4 +1,4 @@
 [data-component="code"] {
-  content-visibility: auto;
-  overflow: hidden;
+	content-visibility: auto;
+	overflow: hidden;
 }

+ 85 - 89
packages/ui/src/components/collapsible.css

@@ -1,103 +1,99 @@
 [data-component="collapsible"] {
-  width: 100%;
-  display: flex;
-  flex-direction: column;
-  background-color: var(--surface-inset-base);
-  border: 1px solid var(--border-weaker-base);
-  transition: background-color 0.15s ease;
-  border-radius: var(--radius-md);
-  overflow: clip;
+	width: 100%;
+	display: flex;
+	flex-direction: column;
+	background-color: var(--surface-inset-base);
+	border: 1px solid var(--border-weaker-base);
+	transition-property: background-color, border-color;
+	transition-duration: var(--transition-duration);
+	transition-timing-function: var(--transition-easing);
+	border-radius: var(--radius-md);
+	overflow: clip;
 
-  [data-slot="collapsible-trigger"] {
-    width: 100%;
-    display: flex;
-    height: 32px;
-    padding: 6px 8px 6px 12px;
-    align-items: center;
-    align-self: stretch;
-    cursor: default;
-    user-select: none;
-    color: var(--text-base);
+	[data-slot="collapsible-trigger"] {
+		width: 100%;
+		display: flex;
+		height: 32px;
+		padding: 6px 8px 6px 12px;
+		align-items: center;
+		align-self: stretch;
+		cursor: default;
+		user-select: none;
+		color: var(--text-base);
 
-    /* text-12-medium */
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large); /* 166.667% */
-    letter-spacing: var(--letter-spacing-normal);
+		/* text-12-medium */
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large); /* 166.667% */
+		letter-spacing: var(--letter-spacing-normal);
 
-    /* &:hover { */
-    /*   background-color: var(--surface-base); */
-    /* } */
-    &:focus-visible {
-      outline: none;
-    }
-    &[data-disabled] {
-      cursor: not-allowed;
-    }
+		/* &:hover { */
+		/*   background-color: var(--surface-base); */
+		/* } */
+		&:focus-visible {
+			outline: none;
+		}
+		&[data-disabled] {
+			cursor: not-allowed;
+		}
 
-    [data-slot="collapsible-arrow"] {
-      flex-shrink: 0;
-      width: 24px;
-      height: 24px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-  }
+		[data-slot="collapsible-arrow"] {
+			flex-shrink: 0;
+			width: 24px;
+			height: 24px;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			color: var(--text-weak);
+		}
+	}
 
-  [data-slot="collapsible-content"] {
-    overflow: hidden;
-    /* animation: slideUp 250ms ease-out; */
+	[data-slot="collapsible-content"] {
+		display: grid;
+		grid-template-rows: 0fr;
+		transition-property: grid-template-rows, opacity;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
 
-    /* &[data-expanded] { */
-    /*   animation: slideDown 250ms ease-out; */
-    /* } */
-  }
+		> * {
+			overflow: hidden;
+		}
 
-  &[data-variant="ghost"] {
-    background-color: transparent;
-    border: none;
+		&[data-expanded] {
+			grid-template-rows: 1fr;
+		}
 
-    > [data-slot="collapsible-trigger"] {
-      background-color: transparent;
-      border: none;
-      padding: 0;
+		&[data-closed] {
+			grid-template-rows: 0fr;
+		}
+	}
 
-      /* &:hover { */
-      /*   color: var(--text-strong); */
-      /* } */
-      &:focus-visible {
-        outline: none;
-      }
-      &[data-disabled] {
-        cursor: not-allowed;
-      }
-    }
-  }
+	&[data-variant="ghost"] {
+		background-color: transparent;
+		border: none;
 
-  &[data-variant="ghost"][data-scope="filetree"] {
-    > [data-slot="collapsible-trigger"] {
-      height: 24px;
-    }
-  }
-}
+		> [data-slot="collapsible-trigger"] {
+			background-color: transparent;
+			border: none;
+			padding: 0;
 
-@keyframes slideDown {
-  from {
-    height: 0;
-  }
-  to {
-    height: var(--kb-collapsible-content-height);
-  }
-}
+			/* &:hover { */
+			/*   color: var(--text-strong); */
+			/* } */
+			&:focus-visible {
+				outline: none;
+			}
+			&[data-disabled] {
+				cursor: not-allowed;
+			}
+		}
+	}
 
-@keyframes slideUp {
-  from {
-    height: var(--kb-collapsible-content-height);
-  }
-  to {
-    height: 0;
-  }
+	&[data-variant="ghost"][data-scope="filetree"] {
+		> [data-slot="collapsible-trigger"] {
+			height: 24px;
+		}
+	}
 }

+ 29 - 13
packages/ui/src/components/collapsible.tsx

@@ -1,6 +1,8 @@
 import { Collapsible as Kobalte, CollapsibleRootProps } from "@kobalte/core/collapsible"
-import { ComponentProps, ParentProps, splitProps } from "solid-js"
-import { Icon } from "./icon"
+import { Accessor, ComponentProps, createContext, createSignal, ParentProps, splitProps, useContext } from "solid-js"
+import { MorphChevron } from "./morph-chevron"
+
+const CollapsibleContext = createContext<Accessor<boolean>>()
 
 export interface CollapsibleProps extends ParentProps<CollapsibleRootProps> {
   class?: string
@@ -9,17 +11,30 @@ export interface CollapsibleProps extends ParentProps<CollapsibleRootProps> {
 }
 
 function CollapsibleRoot(props: CollapsibleProps) {
-  const [local, others] = splitProps(props, ["class", "classList", "variant"])
+  const [local, others] = splitProps(props, ["class", "classList", "variant", "open", "onOpenChange", "children"])
+  const [internalOpen, setInternalOpen] = createSignal(local.open ?? false)
+
+  const handleOpenChange = (open: boolean) => {
+    setInternalOpen(open)
+    local.onOpenChange?.(open)
+  }
+
   return (
-    <Kobalte
-      data-component="collapsible"
-      data-variant={local.variant || "normal"}
-      classList={{
-        ...(local.classList ?? {}),
-        [local.class ?? ""]: !!local.class,
-      }}
-      {...others}
-    />
+    <CollapsibleContext.Provider value={internalOpen}>
+      <Kobalte
+        data-component="collapsible"
+        data-variant={local.variant || "normal"}
+        open={local.open}
+        onOpenChange={handleOpenChange}
+        classList={{
+          ...(local.classList ?? {}),
+          [local.class ?? ""]: !!local.class,
+        }}
+        {...others}
+      >
+        {local.children}
+      </Kobalte>
+    </CollapsibleContext.Provider>
   )
 }
 
@@ -32,9 +47,10 @@ function CollapsibleContent(props: ComponentProps<typeof Kobalte.Content>) {
 }
 
 function CollapsibleArrow(props?: ComponentProps<"div">) {
+  const isOpen = useContext(CollapsibleContext)
   return (
     <div data-slot="collapsible-arrow" {...(props || {})}>
-      <Icon name="chevron-grabber-vertical" size="small" />
+      <MorphChevron expanded={isOpen?.() ?? false} />
     </div>
   )
 }

+ 52 - 0
packages/ui/src/components/cycle-label.css

@@ -0,0 +1,52 @@
+.cycle-label {
+	--c-dur: 200ms;
+	--c-stag: 30ms;
+	--c-ease: cubic-bezier(0.25, 0, 0.5, 1);
+	--c-opacity-start: 0;
+	--c-opacity-end: 1;
+	--c-blur-start: 0px;
+	--c-blur-end: 0px;
+	--c-skew: 10deg;
+
+	display: inline-flex;
+	position: relative;
+
+	transform-style: preserve-3d;
+	perspective: 500px;
+	transition: width 200ms var(--c-ease);
+	will-change: width;
+	overflow: hidden;
+
+	.cycle-char {
+		display: inline-block;
+		transform-style: preserve-3d;
+		min-width: 0.25em;
+		backface-visibility: hidden;
+
+		transition:
+			transform var(--c-dur) var(--c-ease),
+			opacity var(--c-dur) var(--c-ease),
+			filter var(--c-dur) var(--c-ease);
+		transition-delay: calc(var(--i, 0) * var(--c-stag));
+
+		&.enter {
+			opacity: var(--c-opacity-end);
+			filter: blur(var(--c-blur-end));
+			transform: translateY(0) rotateX(0) skewX(0);
+		}
+
+		&.exit {
+			opacity: var(--c-opacity-start);
+			filter: blur(var(--c-blur-start));
+			transform: translateY(50%) rotateX(90deg) skewX(var(--c-skew));
+		}
+
+		&.pre {
+			opacity: var(--c-opacity-start);
+			filter: blur(var(--c-blur-start));
+			transition: none;
+			transform: translateY(-50%) rotateX(-90deg)
+				skewX(calc(var(--c-skew) * -1));
+		}
+	}
+}

+ 132 - 0
packages/ui/src/components/cycle-label.tsx

@@ -0,0 +1,132 @@
+import "./cycle-label.css"
+import { createEffect, createSignal, JSX, on } from "solid-js"
+
+export interface CycleLabelProps extends JSX.HTMLAttributes<HTMLSpanElement> {
+  value: string
+  onValueChange?: (value: string) => void
+  duration?: number | ((value: string) => number)
+  stagger?: number
+  opacity?: [number, number]
+  blur?: [number, number]
+  skewX?: number
+  onAnimationStart?: () => void
+  onAnimationEnd?: () => void
+}
+
+const segmenter =
+  typeof Intl !== "undefined" && Intl.Segmenter ? new Intl.Segmenter("en", { granularity: "grapheme" }) : null
+
+const getChars = (text: string): string[] =>
+  segmenter ? Array.from(segmenter.segment(text), (s) => s.segment) : text.split("")
+
+const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
+
+export function CycleLabel(props: CycleLabelProps) {
+  const getDuration = (text: string) => {
+    const d = props?.duration ?? 200
+    return typeof d === "function" ? d(text) : d
+  }
+  const stagger = () => props?.stagger ?? 20
+  const opacity = () => props?.opacity ?? [0, 1]
+  const blur = () => props?.blur ?? [0, 0]
+  const skewX = () => props?.skewX ?? 10
+
+  let containerRef: HTMLSpanElement | undefined
+  let isAnimating = false
+  const [currentText, setCurrentText] = createSignal(props.value)
+
+  const setChars = (el: HTMLElement, text: string, state: "enter" | "exit" | "pre" = "enter") => {
+    el.innerHTML = ""
+    const chars = getChars(text)
+    chars.forEach((char, i) => {
+      const span = document.createElement("span")
+      span.textContent = char === " " ? "\u00A0" : char
+      span.className = `cycle-char ${state}`
+      span.style.setProperty("--i", String(i))
+      el.appendChild(span)
+    })
+  }
+
+  const animateToText = async (newText: string) => {
+    if (!containerRef || isAnimating) return
+    if (newText === currentText()) return
+
+    isAnimating = true
+    props.onAnimationStart?.()
+
+    const dur = getDuration(newText)
+    const stag = stagger()
+
+    containerRef.style.width = containerRef.offsetWidth + "px"
+
+    const oldChars = containerRef.querySelectorAll(".cycle-char")
+    oldChars.forEach((c) => c.classList.replace("enter", "exit"))
+
+    const clone = containerRef.cloneNode(false) as HTMLElement
+    Object.assign(clone.style, {
+      position: "absolute",
+      visibility: "hidden",
+      width: "auto",
+      transition: "none",
+    })
+    setChars(clone, newText)
+    document.body.appendChild(clone)
+    const nextWidth = clone.offsetWidth
+    clone.remove()
+
+    const exitTime = oldChars.length * stag + dur
+    await wait(exitTime * 0.3)
+
+    containerRef.style.width = nextWidth + "px"
+
+    const widthDur = 200
+    await wait(widthDur * 0.3)
+
+    setChars(containerRef, newText, "pre")
+    containerRef.offsetWidth
+
+    Array.from(containerRef.children).forEach((c) => (c.className = "cycle-char enter"))
+    setCurrentText(newText)
+    props.onValueChange?.(newText)
+
+    const enterTime = getChars(newText).length * stag + dur
+    await wait(enterTime)
+
+    containerRef.style.width = ""
+    isAnimating = false
+    props.onAnimationEnd?.()
+  }
+
+  createEffect(
+    on(
+      () => props.value,
+      (newValue) => {
+        if (newValue !== currentText()) {
+          animateToText(newValue)
+        }
+      },
+    ),
+  )
+
+  const initRef = (el: HTMLSpanElement) => {
+    containerRef = el
+    setChars(el, props.value)
+  }
+
+  return (
+    <span
+      ref={initRef}
+      class={`cycle-label ${props.class ?? ""}`}
+      style={{
+        "--c-dur": `${getDuration(currentText())}ms`,
+        "--c-stag": `${stagger()}ms`,
+        "--c-opacity-start": opacity()[0],
+        "--c-opacity-end": opacity()[1],
+        "--c-blur-start": `${blur()[0]}px`,
+        "--c-blur-end": `${blur()[1]}px`,
+        "--c-skew": `${skewX()}deg`,
+        ...(typeof props.style === "object" ? props.style : {}),
+      }}
+    />
+  )
+}

+ 176 - 161
packages/ui/src/components/dialog.css

@@ -1,181 +1,196 @@
 /* [data-component="dialog-trigger"] { } */
 
 [data-component="dialog-overlay"] {
-  position: fixed;
-  inset: 0;
-  z-index: 50;
-  background-color: hsl(from var(--background-base) h s l / 0.2);
+	position: fixed;
+	inset: 0;
+	z-index: 50;
+	background-color: hsl(from var(--background-base) h s l / 0.2);
+
+	animation: overlayHide var(--transition-duration) var(--transition-easing)
+		forwards;
+
+	&[data-expanded] {
+		animation: overlayShow var(--transition-duration) var(--transition-easing)
+			forwards;
+	}
+
+	@starting-style {
+		animation: none;
+	}
 }
 
 [data-component="dialog"] {
-  position: fixed;
-  inset: 0;
-  z-index: 50;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  pointer-events: none;
-
-  [data-slot="dialog-container"] {
-    position: relative;
-    z-index: 50;
-    width: min(calc(100vw - 16px), 640px);
-    height: min(calc(100vh - 16px), 512px);
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-items: start;
-    overflow: visible;
-
-    [data-slot="dialog-content"] {
-      display: flex;
-      flex-direction: column;
-      align-items: flex-start;
-      align-self: stretch;
-      width: 100%;
-      max-height: 100%;
-      min-height: 280px;
-      overflow: auto;
-      pointer-events: auto;
-
-      /* Hide scrollbar */
-      scrollbar-width: none;
-      -ms-overflow-style: none;
-      &::-webkit-scrollbar {
-        display: none;
-      }
-
-      /* padding: 8px; */
-      /* padding: 8px 8px 0 8px; */
-      border-radius: var(--radius-xl);
-      background: var(--surface-raised-stronger-non-alpha);
-      background-clip: padding-box;
-      box-shadow: var(--shadow-lg-border-base);
-
-      [data-slot="dialog-header"] {
-        display: flex;
-        padding: 20px;
-        justify-content: space-between;
-        align-items: center;
-        flex-shrink: 0;
-        align-self: stretch;
-
-        [data-slot="dialog-title"] {
-          color: var(--text-strong);
-
-          /* text-16-medium */
-          font-family: var(--font-family-sans);
-          font-size: var(--font-size-large);
-          font-style: normal;
-          font-weight: var(--font-weight-medium);
-          line-height: var(--line-height-x-large); /* 150% */
-          letter-spacing: var(--letter-spacing-tight);
-        }
-        /* [data-slot="dialog-close-button"] {} */
-      }
-
-      [data-slot="dialog-description"] {
-        display: flex;
-        padding: 16px;
-        padding-left: 24px;
-        padding-top: 0;
-        margin-top: -8px;
-        justify-content: space-between;
-        align-items: center;
-        flex-shrink: 0;
-        align-self: stretch;
-
-        color: var(--text-base);
-
-        /* text-14-regular */
-        font-family: var(--font-family-sans);
-        font-size: 14px;
-        font-style: normal;
-        font-weight: var(--font-weight-regular);
-        line-height: var(--line-height-large); /* 142.857% */
-        letter-spacing: var(--letter-spacing-normal);
-      }
-
-      [data-slot="dialog-body"] {
-        width: 100%;
-        position: relative;
-        display: flex;
-        flex-direction: column;
-        flex: 1;
-        overflow: hidden;
-
-        &:focus-visible {
-          outline: none;
-        }
-      }
-      &:focus-visible {
-        outline: none;
-      }
-    }
-  }
-
-  &[data-fit] {
-    [data-slot="dialog-container"] {
-      height: auto;
-
-      [data-slot="dialog-content"] {
-        min-height: 0;
-      }
-    }
-  }
-
-  &[data-size="large"] [data-slot="dialog-container"] {
-    width: min(calc(100vw - 32px), 800px);
-    height: min(calc(100vh - 32px), 600px);
-  }
-
-  &[data-size="x-large"] [data-slot="dialog-container"] {
-    width: min(calc(100vw - 32px), 960px);
-    height: min(calc(100vh - 32px), 600px);
-  }
+	position: fixed;
+	inset: 0;
+	z-index: 50;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	pointer-events: none;
+
+	[data-slot="dialog-container"] {
+		position: relative;
+		z-index: 50;
+		width: min(calc(100vw - 16px), 640px);
+		height: min(calc(100vh - 16px), 512px);
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-items: start;
+
+		[data-slot="dialog-content"] {
+			display: flex;
+			flex-direction: column;
+			align-items: flex-start;
+			align-self: stretch;
+			width: 100%;
+			max-height: 100%;
+			min-height: 280px;
+			pointer-events: auto;
+
+			/* padding: 8px; */
+			/* padding: 8px 8px 0 8px; */
+			border-radius: var(--radius-xl);
+			background: var(--surface-raised-stronger-non-alpha);
+			background-clip: padding-box;
+			box-shadow: var(--shadow-lg-border-base);
+
+			animation: contentHide var(--transition-duration) var(--transition-easing)
+				forwards;
+
+			&[data-expanded] {
+				animation: contentShow var(--transition-duration)
+					var(--transition-easing) forwards;
+			}
+
+			@starting-style {
+				animation: none;
+			}
+
+			[data-slot="dialog-header"] {
+				display: flex;
+				padding: 20px;
+				justify-content: space-between;
+				align-items: center;
+				flex-shrink: 0;
+				align-self: stretch;
+
+				[data-slot="dialog-title"] {
+					color: var(--text-strong);
+
+					/* text-16-medium */
+					font-family: var(--font-family-sans);
+					font-size: var(--font-size-large);
+					font-style: normal;
+					font-weight: var(--font-weight-medium);
+					line-height: var(--line-height-x-large); /* 150% */
+					letter-spacing: var(--letter-spacing-tight);
+				}
+				/* [data-slot="dialog-close-button"] {} */
+			}
+
+			[data-slot="dialog-description"] {
+				display: flex;
+				padding: 16px;
+				padding-left: 24px;
+				padding-top: 0;
+				margin-top: -8px;
+				justify-content: space-between;
+				align-items: center;
+				flex-shrink: 0;
+				align-self: stretch;
+
+				color: var(--text-base);
+
+				/* text-14-regular */
+				font-family: var(--font-family-sans);
+				font-size: 14px;
+				font-style: normal;
+				font-weight: var(--font-weight-regular);
+				line-height: var(--line-height-large); /* 142.857% */
+				letter-spacing: var(--letter-spacing-normal);
+			}
+
+			[data-slot="dialog-body"] {
+				width: 100%;
+				position: relative;
+				display: flex;
+				flex-direction: column;
+				flex: 1;
+				overflow: hidden;
+
+				&:focus-visible {
+					outline: none;
+				}
+			}
+			&:focus-visible {
+				outline: none;
+			}
+		}
+	}
+
+	&[data-fit] {
+		[data-slot="dialog-container"] {
+			height: auto;
+
+			[data-slot="dialog-content"] {
+				min-height: 0;
+			}
+		}
+	}
+
+	&[data-size="large"] [data-slot="dialog-container"] {
+		width: min(calc(100vw - 32px), 800px);
+		height: min(calc(100vh - 32px), 600px);
+	}
+
+	&[data-size="x-large"] [data-slot="dialog-container"] {
+		width: min(calc(100vw - 32px), 960px);
+		height: min(calc(100vh - 32px), 600px);
+	}
 }
 
 [data-component="dialog"][data-transition] [data-slot="dialog-content"] {
-  animation: contentHide 100ms ease-in forwards;
+	animation: contentHide 100ms ease-in forwards;
 
-  &[data-expanded] {
-    animation: contentShow 150ms ease-out;
-  }
+	&[data-expanded] {
+		animation: contentShow 150ms ease-out;
+	}
 }
 
 @keyframes overlayShow {
-  from {
-    opacity: 0;
-  }
-  to {
-    opacity: 1;
-  }
+	from {
+		opacity: 0;
+	}
+	to {
+		opacity: 1;
+	}
 }
 @keyframes overlayHide {
-  from {
-    opacity: 1;
-  }
-  to {
-    opacity: 0;
-  }
+	from {
+		opacity: 1;
+	}
+	to {
+		opacity: 0;
+	}
 }
 @keyframes contentShow {
-  from {
-    opacity: 0;
-    transform: scale(0.98);
-  }
-  to {
-    opacity: 1;
-    transform: scale(1);
-  }
+	from {
+		opacity: 0;
+		transform: translateY(2.5%) scale(0.975);
+	}
+	to {
+		opacity: 1;
+		transform: scale(1);
+	}
 }
 @keyframes contentHide {
-  from {
-    opacity: 1;
-    transform: scale(1);
-  }
-  to {
-    opacity: 0;
-    transform: scale(0.98);
-  }
+	from {
+		opacity: 1;
+		transform: scale(1);
+	}
+	to {
+		opacity: 0;
+		transform: translateY(-2.5%) scale(0.975);
+	}
 }

+ 33 - 33
packages/ui/src/components/diff-changes.css

@@ -1,41 +1,41 @@
 [data-component="diff-changes"] {
-  display: flex;
-  gap: 8px;
-  justify-content: flex-end;
-  align-items: center;
+	display: flex;
+	gap: 8px;
+	justify-content: flex-end;
+	align-items: center;
 
-  [data-slot="diff-changes-additions"] {
-    font-family: var(--font-family-mono);
-    font-feature-settings: var(--font-family-mono--font-feature-settings);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-    text-align: right;
-    color: var(--text-diff-add-base);
-  }
+	[data-slot="diff-changes-additions"] {
+		font-family: var(--font-family-mono);
+		font-feature-settings: var(--font-family-mono--font-feature-settings);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+		text-align: right;
+		color: var(--text-diff-add-base);
+	}
 
-  [data-slot="diff-changes-deletions"] {
-    font-family: var(--font-family-mono);
-    font-feature-settings: var(--font-family-mono--font-feature-settings);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-    text-align: right;
-    color: var(--text-diff-delete-base);
-  }
+	[data-slot="diff-changes-deletions"] {
+		font-family: var(--font-family-mono);
+		font-feature-settings: var(--font-family-mono--font-feature-settings);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+		text-align: right;
+		color: var(--text-diff-delete-base);
+	}
 }
 
 [data-component="diff-changes"][data-variant="bars"] {
-  width: 18px;
-  flex-shrink: 0;
+	width: 18px;
+	flex-shrink: 0;
 
-  svg {
-    display: block;
-    width: 100%;
-    height: auto;
-  }
+	svg {
+		display: block;
+		width: 100%;
+		height: auto;
+	}
 }

+ 30 - 30
packages/ui/src/components/diff.css

@@ -1,35 +1,35 @@
 [data-component="diff"] {
-  content-visibility: auto;
+	content-visibility: auto;
 
-  [data-slot="diff-hunk-separator-line-number"] {
-    position: sticky;
-    left: 0;
-    background-color: var(--surface-diff-hidden-strong);
-    z-index: 2;
-    display: flex;
-    align-items: center;
-    justify-content: center;
+	[data-slot="diff-hunk-separator-line-number"] {
+		position: sticky;
+		left: 0;
+		background-color: var(--surface-diff-hidden-strong);
+		z-index: 2;
+		display: flex;
+		align-items: center;
+		justify-content: center;
 
-    [data-slot="diff-hunk-separator-line-number-icon"] {
-      aspect-ratio: 1;
-      width: 24px;
-      height: 24px;
-      color: var(--icon-strong-base);
-    }
-  }
-  [data-slot="diff-hunk-separator-content"] {
-    position: sticky;
-    background-color: var(--surface-diff-hidden-base);
-    color: var(--text-base);
-    width: var(--diffs-column-content-width);
-    left: var(--diffs-column-number-width);
-    padding-left: 8px;
-    user-select: none;
-    cursor: default;
-    text-align: left;
+		[data-slot="diff-hunk-separator-line-number-icon"] {
+			aspect-ratio: 1;
+			width: 24px;
+			height: 24px;
+			color: var(--icon-strong-base);
+		}
+	}
+	[data-slot="diff-hunk-separator-content"] {
+		position: sticky;
+		background-color: var(--surface-diff-hidden-base);
+		color: var(--text-base);
+		width: var(--diffs-column-content-width);
+		left: var(--diffs-column-number-width);
+		padding-left: 8px;
+		user-select: none;
+		cursor: default;
+		text-align: left;
 
-    [data-slot="diff-hunk-separator-content-span"] {
-      mix-blend-mode: var(--text-mix-blend-mode);
-    }
-  }
+		[data-slot="diff-hunk-separator-content-span"] {
+			mix-blend-mode: var(--text-mix-blend-mode);
+		}
+	}
 }

+ 125 - 114
packages/ui/src/components/dropdown-menu.css

@@ -1,125 +1,136 @@
 [data-component="dropdown-menu-content"],
 [data-component="dropdown-menu-sub-content"] {
-  min-width: 8rem;
-  overflow: hidden;
-  border-radius: var(--radius-md);
-  border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
-  background-clip: padding-box;
-  background-color: var(--surface-raised-stronger-non-alpha);
-  padding: 4px;
-  box-shadow: var(--shadow-md);
-  z-index: 50;
-  transform-origin: var(--kb-menu-content-transform-origin);
-
-  &:focus,
-  &:focus-visible {
-    outline: none;
-  }
-
-  &[data-closed] {
-    animation: dropdown-menu-close 0.15s ease-out;
-  }
-
-  &[data-expanded] {
-    animation: dropdown-menu-open 0.15s ease-out;
-  }
+	min-width: 8rem;
+	overflow: hidden;
+	border: none;
+	border-radius: var(--radius-md);
+	box-shadow: var(--shadow-xs-border);
+	background-clip: padding-box;
+	background-color: var(--surface-raised-stronger-non-alpha);
+	padding: 4px;
+	z-index: 100;
+	transform-origin: var(--kb-menu-content-transform-origin);
+
+	&:focus-within,
+	&:focus {
+		outline: none;
+	}
+
+	animation: dropdownMenuContentHide var(--transition-duration)
+		var(--transition-easing) forwards;
+
+	@starting-style {
+		animation: none;
+	}
+
+	&[data-expanded] {
+		pointer-events: auto;
+		animation: dropdownMenuContentShow var(--transition-duration)
+			var(--transition-easing) forwards;
+	}
 }
 
 [data-component="dropdown-menu-content"],
 [data-component="dropdown-menu-sub-content"] {
-  [data-slot="dropdown-menu-item"],
-  [data-slot="dropdown-menu-checkbox-item"],
-  [data-slot="dropdown-menu-radio-item"],
-  [data-slot="dropdown-menu-sub-trigger"] {
-    position: relative;
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    padding: 4px 8px;
-    border-radius: var(--radius-sm);
-    cursor: default;
-    user-select: none;
-    outline: none;
-
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-    color: var(--text-strong);
-
-    &[data-highlighted] {
-      background: var(--surface-raised-base-hover);
-    }
-
-    &[data-disabled] {
-      color: var(--text-weak);
-      pointer-events: none;
-    }
-  }
-
-  [data-slot="dropdown-menu-sub-trigger"] {
-    &[data-expanded] {
-      background: var(--surface-raised-base-hover);
-    }
-  }
-
-  [data-slot="dropdown-menu-item-indicator"] {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    width: 16px;
-    height: 16px;
-  }
-
-  [data-slot="dropdown-menu-item-label"] {
-    flex: 1;
-  }
-
-  [data-slot="dropdown-menu-item-description"] {
-    font-size: var(--font-size-x-small);
-    color: var(--text-weak);
-  }
-
-  [data-slot="dropdown-menu-separator"] {
-    height: 1px;
-    margin: 4px -4px;
-    border-top-color: var(--border-weak-base);
-  }
-
-  [data-slot="dropdown-menu-group-label"] {
-    padding: 4px 8px;
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-x-small);
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-    color: var(--text-weak);
-  }
-
-  [data-slot="dropdown-menu-arrow"] {
-    fill: var(--surface-raised-stronger-non-alpha);
-  }
+	[data-slot="dropdown-menu-item"],
+	[data-slot="dropdown-menu-checkbox-item"],
+	[data-slot="dropdown-menu-radio-item"],
+	[data-slot="dropdown-menu-sub-trigger"] {
+		position: relative;
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		padding: 4px 8px;
+		border-radius: var(--radius-sm);
+		cursor: default;
+		outline: none;
+
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-base);
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+		color: var(--text-strong);
+
+		transition-property: background-color, color;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+		user-select: none;
+
+		&:hover {
+			background-color: var(--surface-raised-base-hover);
+		}
+
+		&[data-disabled] {
+			color: var(--text-weak);
+			pointer-events: none;
+		}
+	}
+
+	[data-slot="dropdown-menu-sub-trigger"] {
+		&[data-expanded] {
+			background: var(--surface-raised-base-hover);
+			outline: none;
+			border: none;
+		}
+	}
+
+	[data-slot="dropdown-menu-item-indicator"] {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		width: 16px;
+		height: 16px;
+	}
+
+	[data-slot="dropdown-menu-item-label"] {
+		flex: 1;
+	}
+
+	[data-slot="dropdown-menu-item-description"] {
+		font-size: var(--font-size-x-small);
+		color: var(--text-weak);
+	}
+
+	[data-slot="dropdown-menu-separator"] {
+		height: 1px;
+		margin: 4px -4px;
+		border-top-color: var(--border-weak-base);
+	}
+
+	[data-slot="dropdown-menu-group-label"] {
+		padding: 4px 8px;
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-x-small);
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+		color: var(--text-weak);
+	}
+
+	[data-slot="dropdown-menu-arrow"] {
+		fill: var(--surface-raised-stronger-non-alpha);
+	}
 }
 
-@keyframes dropdown-menu-open {
-  from {
-    opacity: 0;
-    transform: scale(0.96);
-  }
-  to {
-    opacity: 1;
-    transform: scale(1);
-  }
+@keyframes dropdownMenuContentShow {
+	from {
+		opacity: 0;
+		transform: scaleY(0.95);
+	}
+	to {
+		opacity: 1;
+		transform: scaleY(1);
+	}
 }
 
-@keyframes dropdown-menu-close {
-  from {
-    opacity: 1;
-    transform: scale(1);
-  }
-  to {
-    opacity: 0;
-    transform: scale(0.96);
-  }
+@keyframes dropdownMenuContentHide {
+	from {
+		opacity: 1;
+		transform: scaleY(1);
+	}
+	to {
+		opacity: 0;
+		transform: scaleY(0.95);
+	}
 }

+ 3 - 3
packages/ui/src/components/file-icon.css

@@ -1,5 +1,5 @@
 [data-component="file-icon"] {
-  flex-shrink: 0;
-  width: 16px;
-  height: 16px;
+	flex-shrink: 0;
+	width: 16px;
+	height: 16px;
 }

+ 52 - 50
packages/ui/src/components/hover-card.css

@@ -1,61 +1,63 @@
 [data-slot="hover-card-trigger"] {
-  display: flex;
-  width: 100%;
-  min-width: 0;
+	display: flex;
+	width: 100%;
+	min-width: 0;
 }
 
 [data-component="hover-card-content"] {
-  z-index: 50;
-  min-width: 200px;
-  max-width: 320px;
-  max-height: calc(100vh - 1rem);
-  border-radius: 8px;
-  background-color: var(--surface-raised-stronger-non-alpha);
-  pointer-events: auto;
-
-  border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
-  background-clip: padding-box;
-  box-shadow: var(--shadow-md);
-
-  transform-origin: var(--kb-hovercard-content-transform-origin);
-
-  &:focus-within {
-    outline: none;
-  }
-
-  &[data-closed] {
-    animation: hover-card-close 0.15s ease-out;
-  }
-
-  &[data-expanded] {
-    animation: hover-card-open 0.15s ease-out;
-  }
-
-  [data-slot="hover-card-body"] {
-    padding: 4px;
-    max-height: inherit;
-    overflow: hidden;
-  }
+	z-index: 50;
+	min-width: 200px;
+	max-width: 320px;
+	max-height: calc(100vh - 1rem);
+	border-radius: 8px;
+	background-color: var(--surface-raised-stronger-non-alpha);
+	pointer-events: auto;
+
+	border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
+	background-clip: padding-box;
+	box-shadow: var(--shadow-md);
+
+	transform-origin: var(--kb-hovercard-content-transform-origin);
+
+	&:focus-within {
+		outline: none;
+	}
+
+	&[data-closed] {
+		animation: hover-card-close var(--transition-duration)
+			var(--transition-easing);
+	}
+
+	&[data-expanded] {
+		animation: hover-card-open var(--transition-duration)
+			var(--transition-easing);
+	}
+
+	[data-slot="hover-card-body"] {
+		padding: 4px;
+		max-height: inherit;
+		overflow: hidden;
+	}
 }
 
 @keyframes hover-card-open {
-  from {
-    opacity: 0;
-    transform: scale(0.96);
-  }
-  to {
-    opacity: 1;
-    transform: scale(1);
-  }
+	from {
+		opacity: 0;
+		transform: scale(0.96);
+	}
+	to {
+		opacity: 1;
+		transform: scale(1);
+	}
 }
 
 @keyframes hover-card-close {
-  from {
-    opacity: 1;
-    transform: scale(1);
-  }
-  to {
-    opacity: 0;
-    transform: scale(0.96);
-  }
+	from {
+		opacity: 1;
+		transform: scale(1);
+	}
+	to {
+		opacity: 0;
+		transform: scale(0.96);
+	}
 }

+ 141 - 138
packages/ui/src/components/icon-button.css

@@ -1,140 +1,143 @@
 [data-component="icon-button"] {
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  border-radius: var(--radius-sm);
-  text-decoration: none;
-  user-select: none;
-  aspect-ratio: 1;
-  flex-shrink: 0;
-
-  &[data-variant="primary"] {
-    background-color: var(--icon-strong-base);
-
-    [data-slot="icon-svg"] {
-      /* color: var(--icon-weak-base); */
-      color: var(--icon-invert-base);
-
-      /* &:hover:not(:disabled) { */
-      /*   color: var(--icon-weak-hover); */
-      /* } */
-      /* &:active:not(:disabled) { */
-      /*   color: var(--icon-strong-active); */
-      /* } */
-    }
-
-    &:hover:not(:disabled) {
-      background-color: var(--icon-strong-hover);
-    }
-    &:focus:not(:disabled) {
-      background-color: var(--icon-strong-focus);
-    }
-    &:active:not(:disabled) {
-      background-color: var(--icon-strong-active);
-    }
-    &:disabled {
-      background-color: var(--icon-strong-disabled);
-
-      [data-slot="icon-svg"] {
-        color: var(--icon-invert-base);
-      }
-    }
-  }
-
-  &[data-variant="secondary"] {
-    border: transparent;
-    background-color: var(--button-secondary-base);
-    color: var(--text-strong);
-    box-shadow: var(--shadow-xs-border);
-
-    &:hover:not(:disabled) {
-      background-color: var(--button-secondary-hover);
-    }
-    &:focus:not(:disabled) {
-      background-color: var(--button-secondary-base);
-    }
-    &:focus-visible:not(:active) {
-      background-color: var(--button-secondary-base);
-      box-shadow: var(--shadow-xs-border-focus);
-    }
-    &:focus-visible:active {
-      box-shadow: none;
-    }
-    &:active:not(:disabled) {
-      background-color: var(--button-secondary-base);
-    }
-
-    [data-slot="icon-svg"] {
-      color: var(--icon-strong-base);
-    }
-
-    &:disabled {
-      background-color: var(--icon-strong-disabled);
-      color: var(--icon-invert-base);
-      cursor: not-allowed;
-    }
-  }
-
-  &[data-variant="ghost"] {
-    background-color: transparent;
-    /* color: var(--icon-base); */
-
-    [data-slot="icon-svg"] {
-      color: var(--icon-base);
-    }
-
-    &:hover:not(:disabled) {
-      background-color: var(--surface-raised-base-hover);
-
-      /* [data-slot="icon-svg"] { */
-      /*   color: var(--icon-hover); */
-      /* } */
-    }
-    &:focus-visible:not(:disabled) {
-      background-color: var(--surface-raised-base-hover);
-    }
-    &:active:not(:disabled) {
-      background-color: var(--surface-raised-base-active);
-      /* [data-slot="icon-svg"] { */
-      /*   color: var(--icon-active); */
-      /* } */
-    }
-    &:selected:not(:disabled) {
-      background-color: var(--surface-raised-base-active);
-      /* [data-slot="icon-svg"] { */
-      /*   color: var(--icon-selected); */
-      /* } */
-    }
-    &:disabled {
-      color: var(--icon-invert-base);
-      cursor: not-allowed;
-    }
-  }
-
-  &[data-size="normal"] {
-    width: 24px;
-    height: 24px;
-
-    font-size: var(--font-size-small);
-    line-height: var(--line-height-large);
-    gap: calc(var(--spacing) * 0.5);
-  }
-
-  &[data-size="large"] {
-    height: 32px;
-    /* padding: 0 8px 0 6px; */
-    gap: 8px;
-
-    /* text-12-medium */
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large); /* 166.667% */
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  &:focus {
-    outline: none;
-  }
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	border-radius: var(--radius-sm);
+	text-decoration: none;
+	user-select: none;
+	aspect-ratio: 1;
+	flex-shrink: 0;
+	transition-property: background-color, color, opacity, box-shadow;
+	transition-duration: var(--transition-duration);
+	transition-timing-function: var(--transition-easing);
+
+	&[data-variant="primary"] {
+		background-color: var(--icon-strong-base);
+
+		[data-slot="icon-svg"] {
+			/* color: var(--icon-weak-base); */
+			color: var(--icon-invert-base);
+
+			/* &:hover:not(:disabled) { */
+			/*   color: var(--icon-weak-hover); */
+			/* } */
+			/* &:active:not(:disabled) { */
+			/*   color: var(--icon-strong-active); */
+			/* } */
+		}
+
+		&:hover:not(:disabled) {
+			background-color: var(--icon-strong-hover);
+		}
+		&:focus:not(:disabled) {
+			background-color: var(--icon-strong-focus);
+		}
+		&:active:not(:disabled) {
+			background-color: var(--icon-strong-active);
+		}
+		&:disabled {
+			background-color: var(--icon-strong-disabled);
+
+			[data-slot="icon-svg"] {
+				color: var(--icon-invert-base);
+			}
+		}
+	}
+
+	&[data-variant="secondary"] {
+		border: transparent;
+		background-color: var(--button-secondary-base);
+		color: var(--text-strong);
+		box-shadow: var(--shadow-xs-border);
+
+		&:hover:not(:disabled) {
+			background-color: var(--button-secondary-hover);
+		}
+		&:focus:not(:disabled) {
+			background-color: var(--button-secondary-base);
+		}
+		&:focus-visible:not(:active) {
+			background-color: var(--button-secondary-base);
+			box-shadow: var(--shadow-xs-border-focus);
+		}
+		&:focus-visible:active {
+			box-shadow: none;
+		}
+		&:active:not(:disabled) {
+			background-color: var(--button-secondary-base);
+		}
+
+		[data-slot="icon-svg"] {
+			color: var(--icon-strong-base);
+		}
+
+		&:disabled {
+			background-color: var(--icon-strong-disabled);
+			color: var(--icon-invert-base);
+			cursor: not-allowed;
+		}
+	}
+
+	&[data-variant="ghost"] {
+		background-color: transparent;
+		/* color: var(--icon-base); */
+
+		[data-slot="icon-svg"] {
+			color: var(--icon-base);
+		}
+
+		&:hover:not(:disabled) {
+			background-color: var(--surface-raised-base-hover);
+
+			/* [data-slot="icon-svg"] { */
+			/*   color: var(--icon-hover); */
+			/* } */
+		}
+		&:focus-visible:not(:disabled) {
+			background-color: var(--surface-raised-base-hover);
+		}
+		&:active:not(:disabled) {
+			background-color: var(--surface-raised-base-active);
+			/* [data-slot="icon-svg"] { */
+			/*   color: var(--icon-active); */
+			/* } */
+		}
+		&[data-selected]:not(:disabled) {
+			background-color: var(--surface-raised-base-active);
+			/* [data-slot="icon-svg"] { */
+			/*   color: var(--icon-selected); */
+			/* } */
+		}
+		&:disabled {
+			color: var(--icon-invert-base);
+			cursor: not-allowed;
+		}
+	}
+
+	&[data-size="normal"] {
+		width: 24px;
+		height: 24px;
+
+		font-size: var(--font-size-small);
+		line-height: var(--line-height-large);
+		gap: calc(var(--spacing) * 0.5);
+	}
+
+	&[data-size="large"] {
+		height: 32px;
+		/* padding: 0 8px 0 6px; */
+		gap: 8px;
+
+		/* text-12-medium */
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large); /* 166.667% */
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	&:focus {
+		outline: none;
+	}
 }

+ 27 - 27
packages/ui/src/components/icon.css

@@ -1,34 +1,34 @@
 [data-component="icon"] {
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-  /* resize: both; */
-  aspect-ratio: 1/1;
-  color: var(--icon-base);
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	flex-shrink: 0;
+	/* resize: both; */
+	aspect-ratio: 1 / 1;
+	color: var(--icon-base);
 
-  &[data-size="small"] {
-    width: 16px;
-    height: 16px;
-  }
+	&[data-size="small"] {
+		width: 16px;
+		height: 16px;
+	}
 
-  &[data-size="normal"] {
-    width: 20px;
-    height: 20px;
-  }
+	&[data-size="normal"] {
+		width: 20px;
+		height: 20px;
+	}
 
-  &[data-size="medium"] {
-    width: 24px;
-    height: 24px;
-  }
+	&[data-size="medium"] {
+		width: 24px;
+		height: 24px;
+	}
 
-  &[data-size="large"] {
-    width: 24px;
-    height: 24px;
-  }
+	&[data-size="large"] {
+		width: 24px;
+		height: 24px;
+	}
 
-  [data-slot="icon-svg"] {
-    width: 100%;
-    height: auto;
-  }
+	[data-slot="icon-svg"] {
+		width: 100%;
+		height: auto;
+	}
 }

+ 2 - 2
packages/ui/src/components/icon.tsx

@@ -80,13 +80,13 @@ const icons = {
 
 export interface IconProps extends ComponentProps<"svg"> {
   name: keyof typeof icons
-  size?: "small" | "normal" | "medium" | "large"
+  size?: "small" | "normal" | "medium" | "large" | number
 }
 
 export function Icon(props: IconProps) {
   const [local, others] = splitProps(props, ["name", "size", "class", "classList"])
   return (
-    <div data-component="icon" data-size={local.size || "normal"}>
+    <div data-component="icon" data-size={typeof local.size !== 'number' ? local.size || "normal" : `size-[${local.size}px]`}>
       <svg
         data-slot="icon-svg"
         classList={{

+ 55 - 55
packages/ui/src/components/image-preview.css

@@ -1,63 +1,63 @@
 [data-component="image-preview"] {
-  position: fixed;
-  inset: 0;
-  z-index: 50;
-  display: flex;
-  align-items: center;
-  justify-content: center;
+	position: fixed;
+	inset: 0;
+	z-index: 50;
+	display: flex;
+	align-items: center;
+	justify-content: center;
 
-  [data-slot="image-preview-container"] {
-    position: relative;
-    z-index: 50;
-    width: min(calc(100vw - 32px), 90vw);
-    max-width: 1200px;
-    height: min(calc(100vh - 32px), 90vh);
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
+	[data-slot="image-preview-container"] {
+		position: relative;
+		z-index: 50;
+		width: min(calc(100vw - 32px), 90vw);
+		max-width: 1200px;
+		height: min(calc(100vh - 32px), 90vh);
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
 
-    [data-slot="image-preview-content"] {
-      display: flex;
-      flex-direction: column;
-      align-items: center;
-      width: 100%;
-      max-height: 100%;
-      border-radius: var(--radius-lg);
-      background: var(--surface-raised-stronger-non-alpha);
-      box-shadow:
-        0 15px 45px 0 rgba(19, 16, 16, 0.35),
-        0 3.35px 10.051px 0 rgba(19, 16, 16, 0.25),
-        0 0.998px 2.993px 0 rgba(19, 16, 16, 0.2);
-      overflow: hidden;
+		[data-slot="image-preview-content"] {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			width: 100%;
+			max-height: 100%;
+			border-radius: var(--radius-lg);
+			background: var(--surface-raised-stronger-non-alpha);
+			box-shadow:
+				0 15px 45px 0 rgba(19, 16, 16, 0.35),
+				0 3.35px 10.051px 0 rgba(19, 16, 16, 0.25),
+				0 0.998px 2.993px 0 rgba(19, 16, 16, 0.2);
+			overflow: hidden;
 
-      &:focus-visible {
-        outline: none;
-      }
+			&:focus-visible {
+				outline: none;
+			}
 
-      [data-slot="image-preview-header"] {
-        display: flex;
-        padding: 8px 8px 0;
-        justify-content: flex-end;
-        align-items: center;
-        align-self: stretch;
-      }
+			[data-slot="image-preview-header"] {
+				display: flex;
+				padding: 8px 8px 0;
+				justify-content: flex-end;
+				align-items: center;
+				align-self: stretch;
+			}
 
-      [data-slot="image-preview-body"] {
-        width: 100%;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        padding: 16px;
-        overflow: auto;
-      }
+			[data-slot="image-preview-body"] {
+				width: 100%;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				padding: 16px;
+				overflow: auto;
+			}
 
-      [data-slot="image-preview-image"] {
-        max-width: 100%;
-        max-height: calc(90vh - 100px);
-        object-fit: contain;
-        border-radius: var(--radius-md);
-      }
-    }
-  }
+			[data-slot="image-preview-image"] {
+				max-width: 100%;
+				max-height: calc(90vh - 100px);
+				object-fit: contain;
+				border-radius: var(--radius-md);
+			}
+		}
+	}
 }

+ 14 - 14
packages/ui/src/components/inline-input.css

@@ -1,17 +1,17 @@
 [data-component="inline-input"] {
-  color: inherit;
-  background: transparent;
-  border: 0;
-  border-radius: var(--radius-md);
-  padding: 0;
-  min-width: 0;
-  font: inherit;
-  letter-spacing: inherit;
-  line-height: inherit;
-  box-sizing: border-box;
+	color: inherit;
+	background: transparent;
+	border: 0;
+	border-radius: var(--radius-md);
+	padding: 0;
+	min-width: 0;
+	font: inherit;
+	letter-spacing: inherit;
+	line-height: inherit;
+	box-sizing: border-box;
 
-  &:focus {
-    outline: none;
-    box-shadow: 0 0 0 1px var(--border-interactive-focus);
-  }
+	&:focus {
+		outline: none;
+		box-shadow: 0 0 0 1px var(--border-interactive-focus);
+	}
 }

+ 15 - 15
packages/ui/src/components/keybind.css

@@ -1,18 +1,18 @@
 [data-component="keybind"] {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-  height: 20px;
-  padding: 0 8px;
-  border-radius: 2px;
-  background: var(--surface-base);
-  box-shadow: var(--shadow-xxs-border);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex-shrink: 0;
+	height: 20px;
+	padding: 0 8px;
+	border-radius: 2px;
+	background: var(--surface-base);
+	box-shadow: var(--shadow-xxs-border);
 
-  /* text-12-medium */
-  font-family: var(--font-family-sans);
-  font-size: 12px;
-  font-weight: var(--font-weight-medium);
-  line-height: 1;
-  color: var(--text-weak);
+	/* text-12-medium */
+	font-family: var(--font-family-sans);
+	font-size: 12px;
+	font-weight: var(--font-weight-medium);
+	line-height: 1;
+	color: var(--text-weak);
 }

+ 71 - 69
packages/ui/src/components/line-comment.css

@@ -1,115 +1,117 @@
 [data-component="line-comment"] {
-  position: absolute;
-  right: 24px;
-  z-index: var(--line-comment-z, 30);
+	position: absolute;
+	right: 24px;
+	z-index: var(--line-comment-z, 30);
 }
 
 [data-component="line-comment"][data-open] {
-  z-index: var(--line-comment-open-z, 100);
+	z-index: var(--line-comment-open-z, 100);
 }
 
 [data-component="line-comment"] [data-slot="line-comment-button"] {
-  width: 20px;
-  height: 20px;
-  border-radius: var(--radius-md);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: var(--icon-interactive-base);
-  box-shadow: var(--shadow-xs);
-  cursor: default;
-  border: none;
+	width: 20px;
+	height: 20px;
+	border-radius: var(--radius-md);
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	background: var(--icon-interactive-base);
+	box-shadow: var(--shadow-xs);
+	cursor: default;
+	border: none;
 }
 
 [data-component="line-comment"] [data-component="icon"] {
-  color: var(--white);
+	color: var(--white);
 }
 
 [data-component="line-comment"] [data-slot="line-comment-button"]:focus {
-  outline: none;
+	outline: none;
 }
 
-[data-component="line-comment"] [data-slot="line-comment-button"]:focus-visible {
-  box-shadow: var(--shadow-xs-border-focus);
+[data-component="line-comment"]
+	[data-slot="line-comment-button"]:focus-visible {
+	box-shadow: var(--shadow-xs-border-focus);
 }
 
 [data-component="line-comment"] [data-slot="line-comment-popover"] {
-  position: absolute;
-  top: calc(100% + 4px);
-  right: -8px;
-  z-index: var(--line-comment-popover-z, 40);
-  min-width: 200px;
-  max-width: min(320px, calc(100vw - 48px));
-  border-radius: 8px;
-  background: var(--surface-raised-stronger-non-alpha);
-  box-shadow: var(--shadow-lg-border-base);
-  padding: 12px;
+	position: absolute;
+	top: calc(100% + 4px);
+	right: -8px;
+	z-index: var(--line-comment-popover-z, 40);
+	min-width: 200px;
+	max-width: min(320px, calc(100vw - 48px));
+	border-radius: 8px;
+	background: var(--surface-raised-stronger-non-alpha);
+	box-shadow: var(--shadow-lg-border-base);
+	padding: 12px;
 }
 
-[data-component="line-comment"][data-variant="editor"] [data-slot="line-comment-popover"] {
-  width: 380px;
-  max-width: min(380px, calc(100vw - 48px));
-  padding: 8px;
-  border-radius: 14px;
+[data-component="line-comment"][data-variant="editor"]
+	[data-slot="line-comment-popover"] {
+	width: 380px;
+	max-width: min(380px, calc(100vw - 48px));
+	padding: 8px;
+	border-radius: 14px;
 }
 
 [data-component="line-comment"] [data-slot="line-comment-content"] {
-  display: flex;
-  flex-direction: column;
-  gap: 6px;
+	display: flex;
+	flex-direction: column;
+	gap: 6px;
 }
 
 [data-component="line-comment"] [data-slot="line-comment-text"] {
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-base);
-  font-weight: var(--font-weight-regular);
-  line-height: var(--line-height-x-large);
-  letter-spacing: var(--letter-spacing-normal);
-  color: var(--text-strong);
-  white-space: pre-wrap;
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-base);
+	font-weight: var(--font-weight-regular);
+	line-height: var(--line-height-x-large);
+	letter-spacing: var(--letter-spacing-normal);
+	color: var(--text-strong);
+	white-space: pre-wrap;
 }
 
 [data-component="line-comment"] [data-slot="line-comment-label"],
 [data-component="line-comment"] [data-slot="line-comment-editor-label"] {
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-small);
-  font-weight: var(--font-weight-medium);
-  line-height: var(--line-height-large);
-  letter-spacing: var(--letter-spacing-normal);
-  color: var(--text-weak);
-  white-space: nowrap;
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-small);
+	font-weight: var(--font-weight-medium);
+	line-height: var(--line-height-large);
+	letter-spacing: var(--letter-spacing-normal);
+	color: var(--text-weak);
+	white-space: nowrap;
 }
 
 [data-component="line-comment"] [data-slot="line-comment-editor"] {
-  display: flex;
-  flex-direction: column;
-  gap: 8px;
+	display: flex;
+	flex-direction: column;
+	gap: 8px;
 }
 
 [data-component="line-comment"] [data-slot="line-comment-textarea"] {
-  width: 100%;
-  resize: vertical;
-  padding: 8px;
-  border-radius: var(--radius-md);
-  background: var(--surface-base);
-  border: 1px solid var(--border-base);
-  color: var(--text-strong);
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-small);
-  line-height: var(--line-height-large);
+	width: 100%;
+	resize: vertical;
+	padding: 8px;
+	border-radius: var(--radius-md);
+	background: var(--surface-base);
+	border: 1px solid var(--border-base);
+	color: var(--text-strong);
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-small);
+	line-height: var(--line-height-large);
 }
 
 [data-component="line-comment"] [data-slot="line-comment-textarea"]:focus {
-  outline: none;
-  box-shadow: var(--shadow-xs-border-select);
+	outline: none;
+	box-shadow: var(--shadow-xs-border-select);
 }
 
 [data-component="line-comment"] [data-slot="line-comment-actions"] {
-  display: flex;
-  align-items: center;
-  gap: 8px;
+	display: flex;
+	align-items: center;
+	gap: 8px;
 }
 
 [data-component="line-comment"] [data-slot="line-comment-editor-label"] {
-  margin-right: auto;
+	margin-right: auto;
 }

+ 329 - 323
packages/ui/src/components/list.css

@@ -1,331 +1,337 @@
 @property --bottom-fade {
-  syntax: "<length>";
-  inherits: false;
-  initial-value: 0px;
+	syntax: "<length>";
+	inherits: false;
+	initial-value: 0px;
 }
 
 @keyframes scroll {
-  0% {
-    --bottom-fade: 20px;
-  }
-  90% {
-    --bottom-fade: 20px;
-  }
-  100% {
-    --bottom-fade: 0;
-  }
+	0% {
+		--bottom-fade: 20px;
+	}
+	90% {
+		--bottom-fade: 20px;
+	}
+	100% {
+		--bottom-fade: 0;
+	}
 }
 
 [data-component="list"] {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-  overflow: hidden;
-  padding: 0 12px;
-
-  [data-slot="list-search-wrapper"] {
-    display: flex;
-    flex-shrink: 0;
-    align-items: center;
-    gap: 8px;
-    align-self: stretch;
-    margin-bottom: 4px;
-
-    > [data-component="icon-button"] {
-      width: 24px;
-      height: 24px;
-      flex-shrink: 0;
-      background-color: transparent;
-      opacity: 0.5;
-      transition: opacity 0.15s ease;
-
-      &:hover:not(:disabled),
-      &:focus-visible:not(:disabled),
-      &:active:not(:disabled) {
-        background-color: transparent;
-        opacity: 0.7;
-      }
-
-      &:hover:not(:disabled) [data-slot="icon-svg"] {
-        color: var(--icon-hover);
-      }
-
-      &:active:not(:disabled) [data-slot="icon-svg"] {
-        color: var(--icon-active);
-      }
-    }
-  }
-
-  [data-slot="list-search"] {
-    display: flex;
-    flex: 1;
-    padding: 8px;
-    align-items: center;
-    gap: 12px;
-
-    border-radius: var(--radius-md);
-    background: var(--surface-base);
-
-    [data-slot="list-search-container"] {
-      display: flex;
-      align-items: center;
-      gap: 8px;
-      flex: 1 0 0;
-      max-height: 20px;
-
-      [data-slot="list-search-input"] {
-        width: 100%;
-
-        &[data-slot="input-input"] {
-          line-height: 20px;
-          max-height: 20px;
-        }
-      }
-    }
-
-    > [data-component="icon-button"] {
-      width: 20px;
-      height: 20px;
-      background-color: transparent;
-      opacity: 0.5;
-      transition: opacity 0.15s ease;
-
-      &:hover:not(:disabled),
-      &:focus-visible:not(:disabled),
-      &:active:not(:disabled) {
-        background-color: transparent;
-        opacity: 0.7;
-      }
-
-      &:hover:not(:disabled) [data-slot="icon-svg"] {
-        color: var(--icon-hover);
-      }
-
-      &:active:not(:disabled) [data-slot="icon-svg"] {
-        color: var(--icon-active);
-      }
-    }
-
-    > [data-component="icon-button"] {
-      background-color: transparent;
-
-      &:hover:not(:disabled),
-      &:focus:not(:disabled),
-      &:active:not(:disabled) {
-        background-color: transparent;
-      }
-
-      &:hover:not(:disabled) [data-slot="icon-svg"] {
-        color: var(--icon-hover);
-      }
-
-      &:active:not(:disabled) [data-slot="icon-svg"] {
-        color: var(--icon-active);
-      }
-    }
-  }
-
-  [data-slot="list-scroll"] {
-    display: flex;
-    flex-direction: column;
-    gap: 12px;
-    overflow-y: auto;
-    overscroll-behavior: contain;
-    mask: linear-gradient(to bottom, #ffff calc(100% - var(--bottom-fade)), #0000);
-    animation: scroll;
-    animation-timeline: --scroll;
-    scroll-timeline: --scroll y;
-    scrollbar-width: none;
-    -ms-overflow-style: none;
-    &::-webkit-scrollbar {
-      display: none;
-    }
-
-    [data-slot="list-empty-state"] {
-      display: flex;
-      padding: 32px 48px;
-      flex-direction: column;
-      justify-content: center;
-      align-items: center;
-      gap: 8px;
-      align-self: stretch;
-
-      [data-slot="list-message"] {
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        gap: 2px;
-        max-width: 100%;
-        color: var(--text-weak);
-        white-space: nowrap;
-
-        /* text-14-regular */
-        font-family: var(--font-family-sans);
-        font-size: 14px;
-        font-style: normal;
-        font-weight: var(--font-weight-regular);
-        line-height: var(--line-height-large); /* 142.857% */
-        letter-spacing: var(--letter-spacing-normal);
-      }
-
-      [data-slot="list-filter"] {
-        color: var(--text-strong);
-        overflow: hidden;
-        text-overflow: ellipsis;
-      }
-    }
-
-    [data-slot="list-group"] {
-      position: relative;
-      display: flex;
-      flex-direction: column;
-
-      &:last-child {
-        padding-bottom: 12px;
-      }
-
-      [data-slot="list-header"] {
-        display: flex;
-        z-index: 10;
-        padding: 8px 12px 8px 8px;
-        justify-content: space-between;
-        align-items: center;
-        align-self: stretch;
-        background: var(--surface-raised-stronger-non-alpha);
-        position: sticky;
-        top: 0;
-
-        color: var(--text-weak);
-
-        /* text-14-medium */
-        font-family: var(--font-family-sans);
-        font-size: 14px;
-        font-style: normal;
-        font-weight: var(--font-weight-medium);
-        line-height: var(--line-height-large); /* 142.857% */
-        letter-spacing: var(--letter-spacing-normal);
-
-        &::after {
-          content: "";
-          position: absolute;
-          top: 100%;
-          left: 0;
-          right: 0;
-          height: 16px;
-          background: linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha), transparent);
-          pointer-events: none;
-          opacity: 0;
-          transition: opacity 0.15s ease;
-        }
-
-        &[data-stuck="true"]::after {
-          opacity: 1;
-        }
-      }
-
-      [data-slot="list-items"] {
-        display: flex;
-        flex-direction: column;
-        align-items: flex-start;
-        align-self: stretch;
-
-        [data-slot="list-item"] {
-          display: flex;
-          position: relative;
-          width: 100%;
-          padding: 6px 8px 6px 8px;
-          align-items: center;
-          color: var(--text-strong);
-          scroll-margin-top: 28px;
-
-          /* text-14-medium */
-          font-family: var(--font-family-sans);
-          font-size: 14px;
-          font-style: normal;
-          font-weight: var(--font-weight-medium);
-          line-height: var(--line-height-large); /* 142.857% */
-          letter-spacing: var(--letter-spacing-normal);
-
-          [data-slot="list-item-selected-icon"] {
-            display: inline-flex;
-            align-items: center;
-            justify-content: center;
-            flex-shrink: 0;
-            aspect-ratio: 1/1;
-            [data-component="icon"] {
-              color: var(--icon-strong-base);
-            }
-          }
-          [data-slot="list-item-active-icon"] {
-            display: none;
-            align-items: center;
-            justify-content: center;
-            flex-shrink: 0;
-            aspect-ratio: 1/1;
-            [data-component="icon"] {
-              color: var(--icon-strong-base);
-            }
-          }
-
-          [data-slot="list-item-extra-icon"] {
-            color: var(--icon-base);
-            margin-left: -4px;
-          }
-
-          [data-slot="list-item-divider"] {
-            position: absolute;
-            bottom: 0;
-            left: var(--list-divider-inset, 16px);
-            right: var(--list-divider-inset, 16px);
-            height: 1px;
-            background: var(--border-weak-base);
-            pointer-events: none;
-          }
-
-          [data-slot="list-item"]:last-child [data-slot="list-item-divider"] {
-            display: none;
-          }
-
-          &[data-active="true"] {
-            border-radius: var(--radius-md);
-            background: var(--surface-raised-base-hover);
-            [data-slot="list-item-active-icon"] {
-              display: inline-flex;
-            }
-            [data-slot="list-item-extra-icon"] {
-              display: block !important;
-              color: var(--icon-strong-base) !important;
-            }
-          }
-          &:active {
-            background: var(--surface-raised-base-active);
-          }
-          &:focus-visible {
-            outline: none;
-          }
-        }
-
-        [data-slot="list-item-add"] {
-          display: flex;
-          position: relative;
-          width: 100%;
-          padding: 6px 8px 6px 8px;
-          align-items: center;
-          color: var(--text-strong);
-
-          /* text-14-medium */
-          font-family: var(--font-family-sans);
-          font-size: 14px;
-          font-style: normal;
-          font-weight: var(--font-weight-medium);
-          line-height: var(--line-height-large); /* 142.857% */
-          letter-spacing: var(--letter-spacing-normal);
-
-          [data-component="input"] {
-            width: 100%;
-          }
-        }
-      }
-    }
-  }
+	display: flex;
+	flex-direction: column;
+	gap: 8px;
+	overflow: hidden;
+	padding: 0 12px;
+
+	[data-slot="list-search-wrapper"] {
+		display: flex;
+		flex-shrink: 0;
+		align-items: center;
+		gap: 8px;
+		align-self: stretch;
+		margin-bottom: 4px;
+
+		> [data-component="icon-button"] {
+			width: 24px;
+			height: 24px;
+			flex-shrink: 0;
+			background-color: transparent;
+			opacity: 0.5;
+			transition-property: opacity;
+			transition-duration: var(--transition-duration);
+			transition-timing-function: var(--transition-easing);
+
+			&:hover:not(:disabled),
+			&:focus-visible:not(:disabled),
+			&:active:not(:disabled) {
+				background-color: transparent;
+				opacity: 0.7;
+			}
+
+			&:hover:not(:disabled) [data-slot="icon-svg"] {
+				color: var(--icon-hover);
+			}
+
+			&:active:not(:disabled) [data-slot="icon-svg"] {
+				color: var(--icon-active);
+			}
+		}
+	}
+
+	[data-slot="list-search"] {
+		display: flex;
+		flex: 1;
+		padding: 8px;
+		align-items: center;
+		gap: 12px;
+
+		border-radius: var(--radius-md);
+		background: var(--surface-base);
+
+		[data-slot="list-search-container"] {
+			display: flex;
+			align-items: center;
+			gap: 8px;
+			flex: 1 0 0;
+			max-height: 20px;
+
+			[data-slot="list-search-input"] {
+				width: 100%;
+
+				&[data-slot="input-input"] {
+					line-height: 20px;
+					max-height: 20px;
+				}
+			}
+		}
+
+		> [data-component="icon-button"] {
+			width: 20px;
+			height: 20px;
+			background-color: transparent;
+			opacity: 0.5;
+			transition-property: opacity;
+			transition-duration: var(--transition-duration);
+			transition-timing-function: var(--transition-easing);
+
+			&:hover:not(:disabled),
+			&:focus-visible:not(:disabled),
+			&:active:not(:disabled) {
+				background-color: transparent;
+				opacity: 0.7;
+			}
+
+			&:hover:not(:disabled) [data-slot="icon-svg"] {
+				color: var(--icon-hover);
+			}
+
+			&:active:not(:disabled) [data-slot="icon-svg"] {
+				color: var(--icon-active);
+			}
+		}
+
+		> [data-component="icon-button"] {
+			background-color: transparent;
+
+			&:hover:not(:disabled),
+			&:focus:not(:disabled),
+			&:active:not(:disabled) {
+				background-color: transparent;
+			}
+
+			&:hover:not(:disabled) [data-slot="icon-svg"] {
+				color: var(--icon-hover);
+			}
+
+			&:active:not(:disabled) [data-slot="icon-svg"] {
+				color: var(--icon-active);
+			}
+		}
+	}
+
+	[data-slot="list-scroll"] {
+		display: flex;
+		flex-direction: column;
+		gap: 12px;
+		overflow-y: auto;
+		overscroll-behavior: contain;
+
+		[data-slot="list-empty-state"] {
+			display: flex;
+			padding: 32px 48px;
+			flex-direction: column;
+			justify-content: center;
+			align-items: center;
+			gap: 8px;
+			align-self: stretch;
+
+			[data-slot="list-message"] {
+				display: flex;
+				justify-content: center;
+				align-items: center;
+				gap: 2px;
+				max-width: 100%;
+				color: var(--text-weak);
+				white-space: nowrap;
+
+				/* text-14-regular */
+				font-family: var(--font-family-sans);
+				font-size: 14px;
+				font-style: normal;
+				font-weight: var(--font-weight-regular);
+				line-height: var(--line-height-large); /* 142.857% */
+				letter-spacing: var(--letter-spacing-normal);
+			}
+
+			[data-slot="list-filter"] {
+				color: var(--text-strong);
+				overflow: hidden;
+				text-overflow: ellipsis;
+			}
+		}
+
+		[data-slot="list-group"] {
+			position: relative;
+			display: flex;
+			flex-direction: column;
+
+			&:last-child {
+				padding-bottom: 12px;
+			}
+
+			[data-slot="list-header"] {
+				display: flex;
+				z-index: 10;
+				padding: 8px 12px 8px 8px;
+				justify-content: space-between;
+				align-items: center;
+				align-self: stretch;
+				background: var(--surface-raised-stronger-non-alpha);
+				position: sticky;
+				top: 0;
+
+				color: var(--text-weak);
+
+				/* text-14-medium */
+				font-family: var(--font-family-sans);
+				font-size: 14px;
+				font-style: normal;
+				font-weight: var(--font-weight-medium);
+				line-height: var(--line-height-large); /* 142.857% */
+				letter-spacing: var(--letter-spacing-normal);
+
+				&::after {
+					content: "";
+					position: absolute;
+					top: 100%;
+					left: 0;
+					right: 0;
+					height: 16px;
+					background: linear-gradient(
+						to bottom,
+						var(--surface-raised-stronger-non-alpha),
+						transparent
+					);
+					pointer-events: none;
+					opacity: 0;
+					transition-property: opacity;
+					transition-duration: var(--transition-duration);
+					transition-timing-function: var(--transition-easing);
+				}
+
+				&[data-stuck="true"]::after {
+					opacity: 1;
+				}
+			}
+
+			[data-slot="list-items"] {
+				display: flex;
+				flex-direction: column;
+				align-items: flex-start;
+				align-self: stretch;
+
+				[data-slot="list-item"] {
+					display: flex;
+					position: relative;
+					width: 100%;
+					padding: 6px 8px 6px 8px;
+					align-items: center;
+					color: var(--text-strong);
+					scroll-margin-top: 28px;
+
+					/* text-14-medium */
+					font-family: var(--font-family-sans);
+					font-size: 14px;
+					font-style: normal;
+					font-weight: var(--font-weight-medium);
+					line-height: var(--line-height-large); /* 142.857% */
+					letter-spacing: var(--letter-spacing-normal);
+
+					[data-slot="list-item-selected-icon"] {
+						display: inline-flex;
+						align-items: center;
+						justify-content: center;
+						flex-shrink: 0;
+						aspect-ratio: 1 / 1;
+						[data-component="icon"] {
+							color: var(--icon-strong-base);
+						}
+					}
+
+					[name="check"] {
+						color: var(--icon-strong-base);
+					}
+
+					[data-slot="list-item-active-icon"] {
+						display: none;
+						align-items: center;
+						justify-content: center;
+						flex-shrink: 0;
+						aspect-ratio: 1 / 1;
+						[data-component="icon"] {
+							color: var(--icon-strong-base);
+						}
+					}
+
+					[data-slot="list-item-extra-icon"] {
+						color: var(--icon-base);
+						margin-left: -4px;
+					}
+
+					[data-slot="list-item-divider"] {
+						position: absolute;
+						bottom: 0;
+						left: var(--list-divider-inset, 16px);
+						right: var(--list-divider-inset, 16px);
+						height: 1px;
+						background: var(--border-weak-base);
+						pointer-events: none;
+					}
+
+					[data-slot="list-item"]:last-child [data-slot="list-item-divider"] {
+						display: none;
+					}
+
+					&[data-active="true"] {
+						border-radius: var(--radius-md);
+						background: var(--surface-raised-base-hover);
+						[data-slot="list-item-active-icon"] {
+							display: inline-flex;
+						}
+						[data-slot="list-item-extra-icon"] {
+							display: block !important;
+							color: var(--icon-strong-base) !important;
+						}
+					}
+					&:active {
+						background: var(--surface-raised-base-active);
+					}
+					&:focus-visible {
+						outline: none;
+					}
+				}
+
+				[data-slot="list-item-add"] {
+					display: flex;
+					position: relative;
+					width: 100%;
+					padding: 6px 8px 6px 8px;
+					align-items: center;
+					color: var(--text-strong);
+
+					/* text-14-medium */
+					font-family: var(--font-family-sans);
+					font-size: 14px;
+					font-style: normal;
+					font-weight: var(--font-weight-medium);
+					line-height: var(--line-height-large); /* 142.857% */
+					letter-spacing: var(--letter-spacing-normal);
+
+					[data-component="input"] {
+						width: 100%;
+					}
+				}
+			}
+		}
+	}
 }

+ 9 - 2
packages/ui/src/components/list.tsx

@@ -5,6 +5,7 @@ import { useI18n } from "../context/i18n"
 import { Icon, type IconProps } from "./icon"
 import { IconButton } from "./icon-button"
 import { TextField } from "./text-field"
+import { ScrollFade } from "./scroll-fade"
 
 function findByKey(container: HTMLElement, key: string) {
   const nodes = container.querySelectorAll<HTMLElement>('[data-slot="list-item"][data-key]')
@@ -267,7 +268,13 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
           {searchAction()}
         </div>
       </Show>
-      <div ref={setScrollRef} data-slot="list-scroll">
+      <ScrollFade
+        ref={setScrollRef}
+        direction="vertical"
+        fadeStartSize={0}
+        fadeEndSize={20}
+        data-slot="list-scroll"
+      >
         <Show
           when={flat().length > 0 || showAdd()}
           fallback={
@@ -339,7 +346,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
             </div>
           </Show>
         </Show>
-      </div>
+      </ScrollFade>
     </div>
   )
 }

+ 2 - 2
packages/ui/src/components/logo.css

@@ -1,4 +1,4 @@
 [data-component="logo-mark"] {
-  width: 16px;
-  aspect-ratio: 4/5;
+	width: 16px;
+	aspect-ratio: 4 / 5;
 }

+ 209 - 207
packages/ui/src/components/markdown.css

@@ -1,209 +1,211 @@
 [data-component="markdown"] {
-  /* Reset & Base Typography */
-  min-width: 0;
-  max-width: 100%;
-  overflow-wrap: break-word;
-  color: var(--text-base);
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-base); /* 14px */
-  line-height: var(--line-height-x-large);
-
-  /* Spacing for flow */
-  > *:first-child {
-    margin-top: 0;
-  }
-  > *:last-child {
-    margin-bottom: 0;
-  }
-
-  /* Headings: Same size, distinguished by color and spacing */
-  h1,
-  h2,
-  h3,
-  h4,
-  h5,
-  h6 {
-    font-size: var(--font-size-base);
-    color: var(--text-strong);
-    font-weight: var(--font-weight-medium);
-    margin-top: 2rem;
-    margin-bottom: 0.75rem;
-    line-height: var(--line-height-large);
-  }
-
-  /* Emphasis & Strong: Neutral strong color */
-  strong,
-  b {
-    color: var(--text-strong);
-    font-weight: var(--font-weight-medium);
-  }
-
-  /* Paragraphs */
-  p {
-    margin-bottom: 1rem;
-  }
-
-  /* Links */
-  a {
-    color: var(--text-interactive-base);
-    text-decoration: none;
-    font-weight: inherit;
-  }
-
-  a:hover {
-    text-decoration: underline;
-    text-underline-offset: 2px;
-  }
-
-  /* Lists */
-  ul,
-  ol {
-    margin-top: 0.5rem;
-    margin-bottom: 1rem;
-    padding-left: 1.5rem;
-    list-style-position: outside;
-  }
-
-  ul {
-    list-style-type: disc;
-  }
-
-  ol {
-    list-style-type: decimal;
-  }
-
-  li {
-    margin-bottom: 0.5rem;
-  }
-
-  li > p:first-child {
-    display: inline;
-    margin: 0;
-  }
-
-  li > p + p {
-    display: block;
-    margin-top: 0.5rem;
-  }
-
-  li::marker {
-    color: var(--text-weak);
-  }
-
-  /* Nested lists spacing */
-  li > ul,
-  li > ol {
-    margin-top: 0.25rem;
-    margin-bottom: 0.25rem;
-    padding-left: 1rem; /* Minimal indent for nesting only */
-  }
-
-  /* Blockquotes */
-  blockquote {
-    border-left: 2px solid var(--border-weak-base);
-    margin: 1.5rem 0;
-    padding-left: 0.5rem;
-    color: var(--text-weak);
-    font-style: normal;
-  }
-
-  /* Horizontal Rule - Invisible spacing only */
-  hr {
-    border: none;
-    height: 0;
-    margin: 2.5rem 0;
-  }
-
-  .shiki {
-    font-size: 13px;
-    padding: 8px 12px;
-    border-radius: 4px;
-    border: 0.5px solid var(--border-weak-base);
-  }
-
-  [data-component="markdown-code"] {
-    position: relative;
-  }
-
-  [data-slot="markdown-copy-button"] {
-    position: absolute;
-    top: 8px;
-    right: 8px;
-    opacity: 0;
-    transition: opacity 0.15s ease;
-    z-index: 1;
-  }
-
-  [data-component="markdown-code"]:hover [data-slot="markdown-copy-button"] {
-    opacity: 1;
-  }
-
-  [data-slot="markdown-copy-button"] [data-slot="check-icon"] {
-    display: none;
-  }
-
-  [data-slot="markdown-copy-button"][data-copied="true"] [data-slot="copy-icon"] {
-    display: none;
-  }
-
-  [data-slot="markdown-copy-button"][data-copied="true"] [data-slot="check-icon"] {
-    display: inline-flex;
-  }
-
-  pre {
-    margin-top: 2rem;
-    margin-bottom: 2rem;
-    overflow: auto;
-
-    scrollbar-width: none;
-    &::-webkit-scrollbar {
-      display: none;
-    }
-  }
-
-  :not(pre) > code {
-    font-family: var(--font-family-mono);
-    font-feature-settings: var(--font-family-mono--font-feature-settings);
-    color: var(--syntax-string);
-    font-weight: var(--font-weight-medium);
-    /* font-size: 13px; */
-
-    /* padding: 2px 2px; */
-    /* margin: 0 1.5px; */
-    /* border-radius: 2px; */
-    /* background: var(--surface-base); */
-    /* box-shadow: 0 0 0 0.5px var(--border-weak-base); */
-  }
-
-  /* Tables */
-  table {
-    width: 100%;
-    border-collapse: collapse;
-    margin: 1.5rem 0;
-    font-size: var(--font-size-base);
-  }
-
-  th,
-  td {
-    /* Minimal borders for structure, matching TUI "lines" roughly but keeping it web-clean */
-    border-bottom: 1px solid var(--border-weaker-base);
-    padding: 0.75rem 0.5rem;
-    text-align: left;
-    vertical-align: top;
-  }
-
-  th {
-    color: var(--text-strong);
-    font-weight: var(--font-weight-medium);
-    border-bottom: 1px solid var(--border-weak-base);
-  }
-
-  /* Images */
-  img {
-    max-width: 100%;
-    height: auto;
-    border-radius: 4px;
-    margin: 1.5rem 0;
-    display: block;
-  }
+	/* Reset & Base Typography */
+	min-width: 0;
+	max-width: 100%;
+	overflow-wrap: break-word;
+	color: var(--text-base);
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-base); /* 14px */
+	line-height: var(--line-height-x-large);
+
+	/* Spacing for flow */
+	> *:first-child {
+		margin-top: 0;
+	}
+	> *:last-child {
+		margin-bottom: 0;
+	}
+
+	/* Headings: Same size, distinguished by color and spacing */
+	h1,
+	h2,
+	h3,
+	h4,
+	h5,
+	h6 {
+		font-size: var(--font-size-base);
+		color: var(--text-strong);
+		font-weight: var(--font-weight-medium);
+		margin-top: 2rem;
+		margin-bottom: 0.75rem;
+		line-height: var(--line-height-large);
+	}
+
+	/* Emphasis & Strong: Neutral strong color */
+	strong,
+	b {
+		color: var(--text-strong);
+		font-weight: var(--font-weight-medium);
+	}
+
+	/* Paragraphs */
+	p {
+		margin-bottom: 1rem;
+	}
+
+	/* Links */
+	a {
+		color: var(--text-interactive-base);
+		text-decoration: none;
+		font-weight: inherit;
+	}
+
+	a:hover {
+		text-decoration: underline;
+		text-underline-offset: 2px;
+	}
+
+	/* Lists */
+	ul,
+	ol {
+		margin-top: 0.5rem;
+		margin-bottom: 1rem;
+		padding-left: 1.5rem;
+		list-style-position: outside;
+	}
+
+	ul {
+		list-style-type: disc;
+	}
+
+	ol {
+		list-style-type: decimal;
+	}
+
+	li {
+		margin-bottom: 0.5rem;
+	}
+
+	li > p:first-child {
+		display: inline;
+		margin: 0;
+	}
+
+	li > p + p {
+		display: block;
+		margin-top: 0.5rem;
+	}
+
+	li::marker {
+		color: var(--text-weak);
+	}
+
+	/* Nested lists spacing */
+	li > ul,
+	li > ol {
+		margin-top: 0.25rem;
+		margin-bottom: 0.25rem;
+		padding-left: 1rem; /* Minimal indent for nesting only */
+	}
+
+	/* Blockquotes */
+	blockquote {
+		border-left: 2px solid var(--border-weak-base);
+		margin: 1.5rem 0;
+		padding-left: 0.5rem;
+		color: var(--text-weak);
+		font-style: normal;
+	}
+
+	/* Horizontal Rule - Invisible spacing only */
+	hr {
+		border: none;
+		height: 0;
+		margin: 2.5rem 0;
+	}
+
+	.shiki {
+		font-size: 13px;
+		padding: 8px 12px;
+		border-radius: 4px;
+		border: 0.5px solid var(--border-weak-base);
+	}
+
+	[data-component="markdown-code"] {
+		position: relative;
+	}
+
+	[data-slot="markdown-copy-button"] {
+		position: absolute;
+		top: 8px;
+		right: 8px;
+		opacity: 0;
+		transition: opacity 0.15s ease;
+		z-index: 1;
+	}
+
+	[data-component="markdown-code"]:hover [data-slot="markdown-copy-button"] {
+		opacity: 1;
+	}
+
+	[data-slot="markdown-copy-button"] [data-slot="check-icon"] {
+		display: none;
+	}
+
+	[data-slot="markdown-copy-button"][data-copied="true"]
+		[data-slot="copy-icon"] {
+		display: none;
+	}
+
+	[data-slot="markdown-copy-button"][data-copied="true"]
+		[data-slot="check-icon"] {
+		display: inline-flex;
+	}
+
+	pre {
+		margin-top: 2rem;
+		margin-bottom: 2rem;
+		overflow: auto;
+
+		scrollbar-width: none;
+		&::-webkit-scrollbar {
+			display: none;
+		}
+	}
+
+	:not(pre) > code {
+		font-family: var(--font-family-mono);
+		font-feature-settings: var(--font-family-mono--font-feature-settings);
+		color: var(--syntax-string);
+		font-weight: var(--font-weight-medium);
+		/* font-size: 13px; */
+
+		/* padding: 2px 2px; */
+		/* margin: 0 1.5px; */
+		/* border-radius: 2px; */
+		/* background: var(--surface-base); */
+		/* box-shadow: 0 0 0 0.5px var(--border-weak-base); */
+	}
+
+	/* Tables */
+	table {
+		width: 100%;
+		border-collapse: collapse;
+		margin: 1.5rem 0;
+		font-size: var(--font-size-base);
+	}
+
+	th,
+	td {
+		/* Minimal borders for structure, matching TUI "lines" roughly but keeping it web-clean */
+		border-bottom: 1px solid var(--border-weaker-base);
+		padding: 0.75rem 0.5rem;
+		text-align: left;
+		vertical-align: top;
+	}
+
+	th {
+		color: var(--text-strong);
+		font-weight: var(--font-weight-medium);
+		border-bottom: 1px solid var(--border-weak-base);
+	}
+
+	/* Images */
+	img {
+		max-width: 100%;
+		height: auto;
+		border-radius: 4px;
+		margin: 1.5rem 0;
+		display: block;
+	}
 }

+ 90 - 89
packages/ui/src/components/message-nav.css

@@ -1,122 +1,123 @@
 [data-component="message-nav"] {
-  flex-shrink: 0;
-  display: flex;
-  flex-direction: column;
-  align-items: flex-start;
-  padding-left: 0;
-  list-style: none;
-
-  &[data-size="normal"] {
-    width: 240px;
-    gap: 4px;
-  }
-
-  &[data-size="compact"] {
-    width: 24px;
-  }
+	flex-shrink: 0;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	padding-left: 0;
+	list-style: none;
+
+	&[data-size="normal"] {
+		width: 240px;
+		gap: 4px;
+	}
+
+	&[data-size="compact"] {
+		width: 24px;
+	}
 }
 
 [data-slot="message-nav-item"] {
-  display: flex;
-  align-items: center;
-  align-self: stretch;
-  justify-content: flex-end;
-
-  [data-component="message-nav"][data-size="normal"] & {
-    justify-content: flex-start;
-  }
+	display: flex;
+	align-items: center;
+	align-self: stretch;
+	justify-content: flex-end;
+
+	[data-component="message-nav"][data-size="normal"] & {
+		justify-content: flex-start;
+	}
 }
 
 [data-slot="message-nav-tick-button"] {
-  display: flex;
-  align-items: center;
-  justify-content: flex-start;
-  height: 12px;
-  width: 24px;
-  border: none;
-  background: none;
-  padding: 0;
-
-  &[data-active] [data-slot="message-nav-tick-line"] {
-    background-color: var(--icon-strong-base);
-    width: 100%;
-  }
+	display: flex;
+	align-items: center;
+	justify-content: flex-start;
+	height: 12px;
+	width: 24px;
+	border: none;
+	background: none;
+	padding: 0;
+
+	&[data-active] [data-slot="message-nav-tick-line"] {
+		background-color: var(--icon-strong-base);
+		width: 100%;
+	}
 }
 
 [data-slot="message-nav-tick-line"] {
-  height: 1px;
-  width: 16px;
-  background-color: var(--icon-base);
-  transition:
-    width 0.2s,
-    background-color 0.2s;
+	height: 1px;
+	width: 16px;
+	background-color: var(--icon-base);
+	transition:
+		width 0.2s,
+		background-color 0.2s;
 }
 
-[data-slot="message-nav-tick-button"]:hover [data-slot="message-nav-tick-line"] {
-  width: 100%;
-  background-color: var(--icon-strong-base);
+[data-slot="message-nav-tick-button"]:hover
+	[data-slot="message-nav-tick-line"] {
+	width: 100%;
+	background-color: var(--icon-strong-base);
 }
 
 [data-slot="message-nav-message-button"] {
-  display: flex;
-  align-items: center;
-  align-self: stretch;
-  width: 100%;
-  column-gap: 12px;
-  cursor: default;
-  border: none;
-  background: none;
-  padding: 4px 12px;
-  border-radius: var(--radius-sm);
+	display: flex;
+	align-items: center;
+	align-self: stretch;
+	width: 100%;
+	column-gap: 12px;
+	cursor: default;
+	border: none;
+	background: none;
+	padding: 4px 12px;
+	border-radius: var(--radius-sm);
 }
 
 [data-slot="message-nav-title-preview"] {
-  font-size: 14px; /* text-14-regular */
-  color: var(--text-base);
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  min-width: 0;
-  text-align: left;
-
-  &[data-active] {
-    color: var(--text-strong);
-  }
+	font-size: 14px; /* text-14-regular */
+	color: var(--text-base);
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	min-width: 0;
+	text-align: left;
+
+	&[data-active] {
+		color: var(--text-strong);
+	}
 }
 
 [data-slot="message-nav-item"]:hover [data-slot="message-nav-message-button"] {
-  background-color: var(--surface-base);
+	background-color: var(--surface-base);
 }
 [data-slot="message-nav-item"]:active [data-slot="message-nav-message-button"] {
-  background-color: var(--surface-base-active);
+	background-color: var(--surface-base-active);
 }
 
 [data-slot="message-nav-item"]:active [data-slot="message-nav-title-preview"] {
-  color: var(--text-base);
+	color: var(--text-base);
 }
 
 [data-slot="message-nav-tooltip"] {
-  z-index: 1000;
+	z-index: 1000;
 }
 
 [data-slot="message-nav-tooltip-content"] {
-  display: flex;
-  padding: 4px 4px 6px 4px;
-  justify-content: center;
-  align-items: center;
-  border-radius: var(--radius-md);
-  background: var(--surface-raised-stronger-non-alpha);
-  max-height: calc(100vh - 6rem);
-  overflow-y: auto;
-
-  /* border/shadow-xs/base */
-  box-shadow:
-    0 0 0 1px var(--border-weak-base, rgba(17, 0, 0, 0.12)),
-    0 1px 2px -1px rgba(19, 16, 16, 0.04),
-    0 1px 2px 0 rgba(19, 16, 16, 0.06),
-    0 1px 3px 0 rgba(19, 16, 16, 0.08);
-
-  * {
-    margin: 0 !important;
-  }
+	display: flex;
+	padding: 4px 4px 6px 4px;
+	justify-content: center;
+	align-items: center;
+	border-radius: var(--radius-md);
+	background: var(--surface-raised-stronger-non-alpha);
+	max-height: calc(100vh - 6rem);
+	overflow-y: auto;
+
+	/* border/shadow-xs/base */
+	box-shadow:
+		0 0 0 1px var(--border-weak-base, rgba(17, 0, 0, 0.12)),
+		0 1px 2px -1px rgba(19, 16, 16, 0.04),
+		0 1px 2px 0 rgba(19, 16, 16, 0.06),
+		0 1px 3px 0 rgba(19, 16, 16, 0.08);
+
+	* {
+		margin: 0 !important;
+	}
 }

+ 741 - 741
packages/ui/src/components/message-part.css

@@ -1,439 +1,439 @@
 [data-component="assistant-message"] {
-  content-visibility: auto;
-  width: 100%;
-  display: flex;
-  flex-direction: column;
-  align-items: flex-start;
-  gap: 12px;
+	content-visibility: auto;
+	width: 100%;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	gap: 12px;
 }
 
 [data-component="user-message"] {
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-base);
-  font-style: normal;
-  font-weight: var(--font-weight-regular);
-  line-height: var(--line-height-large);
-  letter-spacing: var(--letter-spacing-normal);
-  color: var(--text-base);
-  display: flex;
-  flex-direction: column;
-  gap: 8px;
-
-  [data-slot="user-message-attachments"] {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 8px;
-  }
-
-  [data-slot="user-message-attachment"] {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    border-radius: 6px;
-    overflow: hidden;
-    background: var(--surface-weak);
-    border: 1px solid var(--border-weak-base);
-    transition: border-color 0.15s ease;
-
-    &:hover {
-      border-color: var(--border-strong-base);
-    }
-
-    &[data-type="image"] {
-      width: 48px;
-      height: 48px;
-    }
-
-    &[data-type="file"] {
-      width: 48px;
-      height: 48px;
-    }
-  }
-
-  [data-slot="user-message-attachment-image"] {
-    width: 100%;
-    height: 100%;
-    object-fit: cover;
-  }
-
-  [data-slot="user-message-attachment-icon"] {
-    width: 100%;
-    height: 100%;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    color: var(--icon-weak);
-
-    [data-component="icon"] {
-      width: 20px;
-      height: 20px;
-    }
-  }
-
-  [data-slot="user-message-text"] {
-    position: relative;
-    white-space: pre-wrap;
-    word-break: break-word;
-    overflow: hidden;
-    background: var(--surface-weak);
-    border: 1px solid var(--border-weak-base);
-    padding: 8px 12px;
-    border-radius: 4px;
-
-    [data-highlight="file"] {
-      color: var(--syntax-property);
-    }
-
-    [data-highlight="agent"] {
-      color: var(--syntax-type);
-    }
-
-    [data-slot="user-message-copy-wrapper"] {
-      position: absolute;
-      top: 7px;
-      right: 7px;
-      opacity: 0;
-      transition: opacity 0.15s ease;
-    }
-
-    &:hover [data-slot="user-message-copy-wrapper"] {
-      opacity: 1;
-    }
-  }
-
-  .text-text-strong {
-    color: var(--text-strong);
-  }
-
-  .font-medium {
-    font-weight: var(--font-weight-medium);
-  }
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-base);
+	font-style: normal;
+	font-weight: var(--font-weight-regular);
+	line-height: var(--line-height-large);
+	letter-spacing: var(--letter-spacing-normal);
+	color: var(--text-base);
+	display: flex;
+	flex-direction: column;
+	gap: 8px;
+
+	[data-slot="user-message-attachments"] {
+		display: flex;
+		flex-wrap: wrap;
+		gap: 8px;
+	}
+
+	[data-slot="user-message-attachment"] {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		border-radius: 6px;
+		overflow: hidden;
+		background: var(--surface-weak);
+		border: 1px solid var(--border-weak-base);
+		transition: border-color 0.15s ease;
+
+		&:hover {
+			border-color: var(--border-strong-base);
+		}
+
+		&[data-type="image"] {
+			width: 48px;
+			height: 48px;
+		}
+
+		&[data-type="file"] {
+			width: 48px;
+			height: 48px;
+		}
+	}
+
+	[data-slot="user-message-attachment-image"] {
+		width: 100%;
+		height: 100%;
+		object-fit: cover;
+	}
+
+	[data-slot="user-message-attachment-icon"] {
+		width: 100%;
+		height: 100%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		color: var(--icon-weak);
+
+		[data-component="icon"] {
+			width: 20px;
+			height: 20px;
+		}
+	}
+
+	[data-slot="user-message-text"] {
+		position: relative;
+		white-space: pre-wrap;
+		word-break: break-word;
+		overflow: hidden;
+		background: var(--surface-weak);
+		border: 1px solid var(--border-weak-base);
+		padding: 8px 12px;
+		border-radius: 4px;
+
+		[data-highlight="file"] {
+			color: var(--syntax-property);
+		}
+
+		[data-highlight="agent"] {
+			color: var(--syntax-type);
+		}
+
+		[data-slot="user-message-copy-wrapper"] {
+			position: absolute;
+			top: 7px;
+			right: 7px;
+			opacity: 0;
+			transition: opacity 0.15s ease;
+		}
+
+		&:hover [data-slot="user-message-copy-wrapper"] {
+			opacity: 1;
+		}
+	}
+
+	.text-text-strong {
+		color: var(--text-strong);
+	}
+
+	.font-medium {
+		font-weight: var(--font-weight-medium);
+	}
 }
 
 [data-component="text-part"] {
-  width: 100%;
-
-  [data-slot="text-part-body"] {
-    position: relative;
-    margin-top: 32px;
-  }
-
-  [data-slot="text-part-copy-wrapper"] {
-    position: absolute;
-    top: 8px;
-    right: 8px;
-    opacity: 0;
-    transition: opacity 0.15s ease;
-    z-index: 1;
-  }
-
-  [data-slot="text-part-body"]:hover [data-slot="text-part-copy-wrapper"] {
-    opacity: 1;
-  }
-
-  [data-component="markdown"] {
-    margin-top: 0;
-    font-size: var(--font-size-base);
-  }
+	width: 100%;
+
+	[data-slot="text-part-body"] {
+		position: relative;
+		margin-top: 32px;
+	}
+
+	[data-slot="text-part-copy-wrapper"] {
+		position: absolute;
+		top: 8px;
+		right: 8px;
+		opacity: 0;
+		transition: opacity 0.15s ease;
+		z-index: 1;
+	}
+
+	[data-slot="text-part-body"]:hover [data-slot="text-part-copy-wrapper"] {
+		opacity: 1;
+	}
+
+	[data-component="markdown"] {
+		margin-top: 0;
+		font-size: var(--font-size-base);
+	}
 }
 
 [data-component="reasoning-part"] {
-  width: 100%;
-  color: var(--text-base);
-  opacity: 0.8;
-  line-height: var(--line-height-large);
-
-  [data-component="markdown"] {
-    margin-top: 24px;
-    font-style: italic !important;
-
-    p:has(strong) {
-      margin-top: 24px;
-      margin-bottom: 0;
-
-      &:first-child {
-        margin-top: 0;
-      }
-    }
-  }
+	width: 100%;
+	color: var(--text-base);
+	opacity: 0.8;
+	line-height: var(--line-height-large);
+
+	[data-component="markdown"] {
+		margin-top: 24px;
+		font-style: italic !important;
+
+		p:has(strong) {
+			margin-top: 24px;
+			margin-bottom: 0;
+
+			&:first-child {
+				margin-top: 0;
+			}
+		}
+	}
 }
 
 [data-component="tool-error"] {
-  display: flex;
-  align-items: start;
-  gap: 8px;
-
-  [data-slot="icon-svg"] {
-    color: var(--icon-critical-base);
-    margin-top: 4px;
-  }
-
-  [data-slot="message-part-tool-error-content"] {
-    display: flex;
-    align-items: start;
-    gap: 8px;
-  }
-
-  [data-slot="message-part-tool-error-title"] {
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-base);
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-    color: var(--text-on-critical-base);
-    white-space: nowrap;
-  }
-
-  [data-slot="message-part-tool-error-message"] {
-    color: var(--text-on-critical-weak);
-    max-height: 240px;
-    overflow-y: auto;
-    word-break: break-word;
-  }
+	display: flex;
+	align-items: start;
+	gap: 8px;
+
+	[data-slot="icon-svg"] {
+		color: var(--icon-critical-base);
+		margin-top: 4px;
+	}
+
+	[data-slot="message-part-tool-error-content"] {
+		display: flex;
+		align-items: start;
+		gap: 8px;
+	}
+
+	[data-slot="message-part-tool-error-title"] {
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-base);
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+		color: var(--text-on-critical-base);
+		white-space: nowrap;
+	}
+
+	[data-slot="message-part-tool-error-message"] {
+		color: var(--text-on-critical-weak);
+		max-height: 240px;
+		overflow-y: auto;
+		word-break: break-word;
+	}
 }
 
 [data-component="tool-output"] {
-  white-space: pre;
-  padding: 8px 12px;
-  height: fit-content;
-  display: flex;
-  flex-direction: column;
-  align-items: flex-start;
-  justify-content: flex-start;
-
-  [data-component="markdown"] {
-    width: 100%;
-    min-width: 0;
-
-    pre {
-      margin: 0;
-      padding: 0;
-      background-color: transparent !important;
-      border: none !important;
-    }
-  }
-
-  pre {
-    margin: 0;
-    padding: 0;
-    background: none;
-  }
-
-  &[data-scrollable] {
-    height: auto;
-    max-height: 240px;
-    overflow-y: auto;
-    scrollbar-width: none;
-    -ms-overflow-style: none;
-
-    &::-webkit-scrollbar {
-      display: none;
-    }
-
-    [data-component="markdown"] {
-      overflow: visible;
-    }
-  }
+	white-space: pre;
+	padding: 8px 12px;
+	height: fit-content;
+	display: flex;
+	flex-direction: column;
+	align-items: flex-start;
+	justify-content: flex-start;
+
+	[data-component="markdown"] {
+		width: 100%;
+		min-width: 0;
+
+		pre {
+			margin: 0;
+			padding: 0;
+			background-color: transparent !important;
+			border: none !important;
+		}
+	}
+
+	pre {
+		margin: 0;
+		padding: 0;
+		background: none;
+	}
+
+	&[data-scrollable] {
+		height: auto;
+		max-height: 240px;
+		overflow-y: auto;
+		scrollbar-width: none;
+		-ms-overflow-style: none;
+
+		&::-webkit-scrollbar {
+			display: none;
+		}
+
+		[data-component="markdown"] {
+			overflow: visible;
+		}
+	}
 }
 
 [data-component="edit-trigger"],
 [data-component="write-trigger"] {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  gap: 8px;
-  width: 100%;
-
-  [data-slot="message-part-title-area"] {
-    flex-grow: 1;
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    min-width: 0;
-  }
-
-  [data-slot="message-part-title"] {
-    flex-shrink: 0;
-    display: flex;
-    align-items: center;
-    gap: 4px;
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-base);
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-    color: var(--text-base);
-  }
-
-  [data-slot="message-part-title-text"] {
-    text-transform: capitalize;
-  }
-
-  [data-slot="message-part-title-filename"] {
-    /* No text-transform - preserve original filename casing */
-  }
-
-  [data-slot="message-part-path"] {
-    display: flex;
-    flex-grow: 1;
-    min-width: 0;
-  }
-
-  [data-slot="message-part-directory"] {
-    color: var(--text-weak);
-    text-overflow: ellipsis;
-    overflow: hidden;
-    white-space: nowrap;
-    direction: rtl;
-    text-align: left;
-  }
-
-  [data-slot="message-part-filename"] {
-    color: var(--text-strong);
-    flex-shrink: 0;
-  }
-
-  [data-slot="message-part-actions"] {
-    display: flex;
-    gap: 16px;
-    align-items: center;
-    justify-content: flex-end;
-  }
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	gap: 8px;
+	width: 100%;
+
+	[data-slot="message-part-title-area"] {
+		flex-grow: 1;
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		min-width: 0;
+	}
+
+	[data-slot="message-part-title"] {
+		flex-shrink: 0;
+		display: flex;
+		align-items: center;
+		gap: 4px;
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-base);
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+		color: var(--text-base);
+	}
+
+	[data-slot="message-part-title-text"] {
+		text-transform: capitalize;
+	}
+
+	[data-slot="message-part-title-filename"] {
+		/* No text-transform - preserve original filename casing */
+	}
+
+	[data-slot="message-part-path"] {
+		display: flex;
+		flex-grow: 1;
+		min-width: 0;
+	}
+
+	[data-slot="message-part-directory"] {
+		color: var(--text-weak);
+		text-overflow: ellipsis;
+		overflow: hidden;
+		white-space: nowrap;
+		direction: rtl;
+		text-align: left;
+	}
+
+	[data-slot="message-part-filename"] {
+		color: var(--text-strong);
+		flex-shrink: 0;
+	}
+
+	[data-slot="message-part-actions"] {
+		display: flex;
+		gap: 16px;
+		align-items: center;
+		justify-content: flex-end;
+	}
 }
 
 [data-component="edit-content"] {
-  border-top: 1px solid var(--border-weaker-base);
-  max-height: 420px;
-  overflow-y: auto;
+	border-top: 1px solid var(--border-weaker-base);
+	max-height: 420px;
+	overflow-y: auto;
 
-  scrollbar-width: none;
-  -ms-overflow-style: none;
+	scrollbar-width: none;
+	-ms-overflow-style: none;
 
-  &::-webkit-scrollbar {
-    display: none;
-  }
+	&::-webkit-scrollbar {
+		display: none;
+	}
 }
 
 [data-component="write-content"] {
-  border-top: 1px solid var(--border-weaker-base);
-  max-height: 240px;
-  overflow-y: auto;
+	border-top: 1px solid var(--border-weaker-base);
+	max-height: 240px;
+	overflow-y: auto;
 
-  [data-component="code"] {
-    padding-bottom: 0px !important;
-  }
+	[data-component="code"] {
+		padding-bottom: 0px !important;
+	}
 
-  /* Hide scrollbar */
-  scrollbar-width: none;
-  -ms-overflow-style: none;
+	/* Hide scrollbar */
+	scrollbar-width: none;
+	-ms-overflow-style: none;
 
-  &::-webkit-scrollbar {
-    display: none;
-  }
+	&::-webkit-scrollbar {
+		display: none;
+	}
 }
 
 [data-component="tool-action"] {
-  width: 24px;
-  height: 24px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
+	width: 24px;
+	height: 24px;
+	display: flex;
+	align-items: center;
+	justify-content: center;
 }
 
 [data-component="todos"] {
-  padding: 10px 12px 24px 48px;
-  display: flex;
-  flex-direction: column;
-  gap: 8px;
-
-  [data-slot="message-part-todo-content"] {
-    &[data-completed="completed"] {
-      text-decoration: line-through;
-      color: var(--text-weaker);
-    }
-  }
+	padding: 10px 12px 24px 48px;
+	display: flex;
+	flex-direction: column;
+	gap: 8px;
+
+	[data-slot="message-part-todo-content"] {
+		&[data-completed="completed"] {
+			text-decoration: line-through;
+			color: var(--text-weaker);
+		}
+	}
 }
 
 [data-component="task-tools"] {
-  padding: 8px 12px;
-  display: flex;
-  flex-direction: column;
-  gap: 6px;
-
-  [data-slot="task-tool-item"] {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    color: var(--text-weak);
-
-    [data-slot="icon-svg"] {
-      flex-shrink: 0;
-      color: var(--icon-weak);
-    }
-  }
-
-  [data-slot="task-tool-title"] {
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large);
-    color: var(--text-weak);
-  }
-
-  [data-slot="task-tool-subtitle"] {
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-large);
-    color: var(--text-weaker);
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-  }
+	padding: 8px 12px;
+	display: flex;
+	flex-direction: column;
+	gap: 6px;
+
+	[data-slot="task-tool-item"] {
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		color: var(--text-weak);
+
+		[data-slot="icon-svg"] {
+			flex-shrink: 0;
+			color: var(--icon-weak);
+		}
+	}
+
+	[data-slot="task-tool-title"] {
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large);
+		color: var(--text-weak);
+	}
+
+	[data-slot="task-tool-subtitle"] {
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-large);
+		color: var(--text-weaker);
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+	}
 }
 
 [data-component="diagnostics"] {
-  display: flex;
-  flex-direction: column;
-  gap: 4px;
-  padding: 8px 12px;
-  background-color: var(--surface-critical-weak);
-  border-top: 1px solid var(--border-critical-base);
-
-  [data-slot="diagnostic"] {
-    display: flex;
-    align-items: baseline;
-    gap: 6px;
-    font-family: var(--font-family-mono);
-    font-size: var(--font-size-small);
-    line-height: var(--line-height-large);
-  }
-
-  [data-slot="diagnostic-label"] {
-    color: var(--text-on-critical-base);
-    font-weight: var(--font-weight-medium);
-    text-transform: uppercase;
-    letter-spacing: -0.5px;
-    flex-shrink: 0;
-  }
-
-  [data-slot="diagnostic-location"] {
-    color: var(--text-on-critical-weak);
-    flex-shrink: 0;
-  }
-
-  [data-slot="diagnostic-message"] {
-    color: var(--text-on-critical-base);
-    word-break: break-word;
-    display: -webkit-box;
-    -webkit-box-orient: vertical;
-    -webkit-line-clamp: 3;
-    line-clamp: 3;
-    overflow: hidden;
-  }
+	display: flex;
+	flex-direction: column;
+	gap: 4px;
+	padding: 8px 12px;
+	background-color: var(--surface-critical-weak);
+	border-top: 1px solid var(--border-critical-base);
+
+	[data-slot="diagnostic"] {
+		display: flex;
+		align-items: baseline;
+		gap: 6px;
+		font-family: var(--font-family-mono);
+		font-size: var(--font-size-small);
+		line-height: var(--line-height-large);
+	}
+
+	[data-slot="diagnostic-label"] {
+		color: var(--text-on-critical-base);
+		font-weight: var(--font-weight-medium);
+		text-transform: uppercase;
+		letter-spacing: -0.5px;
+		flex-shrink: 0;
+	}
+
+	[data-slot="diagnostic-location"] {
+		color: var(--text-on-critical-weak);
+		flex-shrink: 0;
+	}
+
+	[data-slot="diagnostic-message"] {
+		color: var(--text-on-critical-base);
+		word-break: break-word;
+		display: -webkit-box;
+		-webkit-box-orient: vertical;
+		-webkit-line-clamp: 3;
+		line-clamp: 3;
+		overflow: hidden;
+	}
 }
 
 [data-component="user-message"] [data-slot="user-message-text"],
@@ -446,383 +446,383 @@
 [data-component="todos"],
 [data-component="diagnostics"],
 .error-card {
-  -webkit-user-select: text;
-  user-select: text;
+	-webkit-user-select: text;
+	user-select: text;
 }
 
 [data-component="tool-part-wrapper"] {
-  width: 100%;
-
-  &[data-permission="true"],
-  &[data-question="true"] {
-    position: sticky;
-    top: calc(2px + var(--sticky-header-height, 40px));
-    bottom: 0px;
-    z-index: 20;
-    border-radius: 6px;
-    border: none;
-    box-shadow: var(--shadow-xs-border-base);
-    background-color: var(--surface-raised-base);
-    overflow: visible;
-    overflow-anchor: none;
-
-    & > *:first-child {
-      border-top-left-radius: 6px;
-      border-top-right-radius: 6px;
-      overflow: hidden;
-    }
-
-    & > *:last-child {
-      border-bottom-left-radius: 6px;
-      border-bottom-right-radius: 6px;
-      overflow: hidden;
-    }
-
-    [data-component="collapsible"] {
-      border: none;
-    }
-
-    [data-component="card"] {
-      border: none;
-    }
-  }
-
-  &[data-permission="true"] {
-    &::before {
-      content: "";
-      position: absolute;
-      inset: -1.5px;
-      top: -5px;
-      border-radius: 7.5px;
-      border: 1.5px solid transparent;
-      background:
-        linear-gradient(var(--background-base) 0 0) padding-box,
-        conic-gradient(
-            from var(--border-angle),
-            transparent 0deg,
-            transparent 0deg,
-            var(--border-warning-strong, var(--border-warning-selected)) 300deg,
-            var(--border-warning-base) 360deg
-          )
-          border-box;
-      animation: chase-border 2.5s linear infinite;
-      pointer-events: none;
-      z-index: -1;
-    }
-  }
-
-  &[data-question="true"] {
-    background: var(--background-base);
-    border: 1px solid var(--border-weak-base);
-  }
+	width: 100%;
+
+	&[data-permission="true"],
+	&[data-question="true"] {
+		position: sticky;
+		top: calc(2px + var(--sticky-header-height, 40px));
+		bottom: 0px;
+		z-index: 20;
+		border-radius: 6px;
+		border: none;
+		box-shadow: var(--shadow-xs-border-base);
+		background-color: var(--surface-raised-base);
+		overflow: visible;
+		overflow-anchor: none;
+
+		& > *:first-child {
+			border-top-left-radius: 6px;
+			border-top-right-radius: 6px;
+			overflow: hidden;
+		}
+
+		& > *:last-child {
+			border-bottom-left-radius: 6px;
+			border-bottom-right-radius: 6px;
+			overflow: hidden;
+		}
+
+		[data-component="collapsible"] {
+			border: none;
+		}
+
+		[data-component="card"] {
+			border: none;
+		}
+	}
+
+	&[data-permission="true"] {
+		&::before {
+			content: "";
+			position: absolute;
+			inset: -1.5px;
+			top: -5px;
+			border-radius: 7.5px;
+			border: 1.5px solid transparent;
+			background:
+				linear-gradient(var(--background-base) 0 0) padding-box,
+				conic-gradient(
+					from var(--border-angle),
+					transparent 0deg,
+					transparent 0deg,
+					var(--border-warning-strong, var(--border-warning-selected)) 300deg,
+					var(--border-warning-base) 360deg
+				)
+				border-box;
+			animation: chase-border 2.5s linear infinite;
+			pointer-events: none;
+			z-index: -1;
+		}
+	}
+
+	&[data-question="true"] {
+		background: var(--background-base);
+		border: 1px solid var(--border-weak-base);
+	}
 }
 
 @property --border-angle {
-  syntax: "<angle>";
-  initial-value: 0deg;
-  inherits: false;
+	syntax: "<angle>";
+	initial-value: 0deg;
+	inherits: false;
 }
 
 @keyframes chase-border {
-  from {
-    --border-angle: 0deg;
-  }
+	from {
+		--border-angle: 0deg;
+	}
 
-  to {
-    --border-angle: 360deg;
-  }
+	to {
+		--border-angle: 360deg;
+	}
 }
 
 [data-component="permission-prompt"] {
-  display: flex;
-  flex-direction: column;
-  padding: 8px 12px;
-  background-color: var(--surface-raised-strong);
-  border-radius: 0 0 6px 6px;
-
-  [data-slot="permission-actions"] {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    justify-content: flex-end;
-  }
+	display: flex;
+	flex-direction: column;
+	padding: 8px 12px;
+	background-color: var(--surface-raised-strong);
+	border-radius: 0 0 6px 6px;
+
+	[data-slot="permission-actions"] {
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		justify-content: flex-end;
+	}
 }
 
 [data-component="question-prompt"] {
-  display: flex;
-  flex-direction: column;
-  padding: 12px;
-  background-color: var(--surface-inset-base);
-  border-radius: 0 0 6px 6px;
-  gap: 12px;
-
-  [data-slot="question-tabs"] {
-    display: flex;
-    gap: 4px;
-    flex-wrap: wrap;
-
-    [data-slot="question-tab"] {
-      padding: 4px 12px;
-      font-size: 13px;
-      border-radius: 4px;
-      background-color: var(--surface-base);
-      color: var(--text-base);
-      border: none;
-      cursor: pointer;
-      transition:
-        color 0.15s,
-        background-color 0.15s;
-
-      &:hover {
-        background-color: var(--surface-base-hover);
-      }
-
-      &[data-active="true"] {
-        background-color: var(--surface-raised-base);
-      }
-
-      &[data-answered="true"] {
-        color: var(--text-strong);
-      }
-    }
-  }
-
-  [data-slot="question-content"] {
-    display: flex;
-    flex-direction: column;
-    gap: 8px;
-
-    [data-slot="question-text"] {
-      font-size: 14px;
-      color: var(--text-base);
-      line-height: 1.5;
-    }
-  }
-
-  [data-slot="question-options"] {
-    display: flex;
-    flex-direction: column;
-    gap: 4px;
-
-    [data-slot="question-option"] {
-      display: flex;
-      flex-direction: column;
-      align-items: flex-start;
-      gap: 2px;
-      padding: 8px 12px;
-      background-color: var(--surface-base);
-      border: 1px solid var(--border-weaker-base);
-      border-radius: 6px;
-      cursor: pointer;
-      text-align: left;
-      width: 100%;
-      transition:
-        background-color 0.15s,
-        border-color 0.15s;
-      position: relative;
-
-      &:hover {
-        background-color: var(--surface-base-hover);
-        border-color: var(--border-default);
-      }
-
-      &[data-picked="true"] {
-        [data-component="icon"] {
-          position: absolute;
-          right: 12px;
-          top: 50%;
-          transform: translateY(-50%);
-          color: var(--text-strong);
-        }
-      }
-
-      [data-slot="option-label"] {
-        font-size: 14px;
-        color: var(--text-base);
-        font-weight: 500;
-      }
-
-      [data-slot="option-description"] {
-        font-size: 12px;
-        color: var(--text-weak);
-      }
-    }
-
-    [data-slot="custom-input-form"] {
-      display: flex;
-      gap: 8px;
-      padding: 8px 0;
-      align-items: stretch;
-
-      [data-slot="custom-input"] {
-        flex: 1;
-        padding: 8px 12px;
-        font-size: 14px;
-        border: 1px solid var(--border-default);
-        border-radius: 6px;
-        background-color: var(--surface-base);
-        color: var(--text-base);
-        outline: none;
-
-        &:focus {
-          border-color: var(--border-focus);
-        }
-
-        &::placeholder {
-          color: var(--text-weak);
-        }
-      }
-
-      [data-component="button"] {
-        height: auto;
-      }
-    }
-  }
-
-  [data-slot="question-review"] {
-    display: flex;
-    flex-direction: column;
-    gap: 12px;
-
-    [data-slot="review-title"] {
-      display: none;
-    }
-
-    [data-slot="review-item"] {
-      display: flex;
-      flex-direction: column;
-      gap: 2px;
-      font-size: 13px;
-
-      [data-slot="review-label"] {
-        color: var(--text-weak);
-      }
-
-      [data-slot="review-value"] {
-        color: var(--text-strong);
-
-        &[data-answered="false"] {
-          color: var(--text-weak);
-        }
-      }
-    }
-  }
-
-  [data-slot="question-actions"] {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    justify-content: flex-end;
-  }
+	display: flex;
+	flex-direction: column;
+	padding: 12px;
+	background-color: var(--surface-inset-base);
+	border-radius: 0 0 6px 6px;
+	gap: 12px;
+
+	[data-slot="question-tabs"] {
+		display: flex;
+		gap: 4px;
+		flex-wrap: wrap;
+
+		[data-slot="question-tab"] {
+			padding: 4px 12px;
+			font-size: 13px;
+			border-radius: 4px;
+			background-color: var(--surface-base);
+			color: var(--text-base);
+			border: none;
+			cursor: pointer;
+			transition:
+				color 0.15s,
+				background-color 0.15s;
+
+			&:hover {
+				background-color: var(--surface-base-hover);
+			}
+
+			&[data-active="true"] {
+				background-color: var(--surface-raised-base);
+			}
+
+			&[data-answered="true"] {
+				color: var(--text-strong);
+			}
+		}
+	}
+
+	[data-slot="question-content"] {
+		display: flex;
+		flex-direction: column;
+		gap: 8px;
+
+		[data-slot="question-text"] {
+			font-size: 14px;
+			color: var(--text-base);
+			line-height: 1.5;
+		}
+	}
+
+	[data-slot="question-options"] {
+		display: flex;
+		flex-direction: column;
+		gap: 4px;
+
+		[data-slot="question-option"] {
+			display: flex;
+			flex-direction: column;
+			align-items: flex-start;
+			gap: 2px;
+			padding: 8px 12px;
+			background-color: var(--surface-base);
+			border: 1px solid var(--border-weaker-base);
+			border-radius: 6px;
+			cursor: pointer;
+			text-align: left;
+			width: 100%;
+			transition:
+				background-color 0.15s,
+				border-color 0.15s;
+			position: relative;
+
+			&:hover {
+				background-color: var(--surface-base-hover);
+				border-color: var(--border-default);
+			}
+
+			&[data-picked="true"] {
+				[data-component="icon"] {
+					position: absolute;
+					right: 12px;
+					top: 50%;
+					transform: translateY(-50%);
+					color: var(--text-strong);
+				}
+			}
+
+			[data-slot="option-label"] {
+				font-size: 14px;
+				color: var(--text-base);
+				font-weight: 500;
+			}
+
+			[data-slot="option-description"] {
+				font-size: 12px;
+				color: var(--text-weak);
+			}
+		}
+
+		[data-slot="custom-input-form"] {
+			display: flex;
+			gap: 8px;
+			padding: 8px 0;
+			align-items: stretch;
+
+			[data-slot="custom-input"] {
+				flex: 1;
+				padding: 8px 12px;
+				font-size: 14px;
+				border: 1px solid var(--border-default);
+				border-radius: 6px;
+				background-color: var(--surface-base);
+				color: var(--text-base);
+				outline: none;
+
+				&:focus {
+					border-color: var(--border-focus);
+				}
+
+				&::placeholder {
+					color: var(--text-weak);
+				}
+			}
+
+			[data-component="button"] {
+				height: auto;
+			}
+		}
+	}
+
+	[data-slot="question-review"] {
+		display: flex;
+		flex-direction: column;
+		gap: 12px;
+
+		[data-slot="review-title"] {
+			display: none;
+		}
+
+		[data-slot="review-item"] {
+			display: flex;
+			flex-direction: column;
+			gap: 2px;
+			font-size: 13px;
+
+			[data-slot="review-label"] {
+				color: var(--text-weak);
+			}
+
+			[data-slot="review-value"] {
+				color: var(--text-strong);
+
+				&[data-answered="false"] {
+					color: var(--text-weak);
+				}
+			}
+		}
+	}
+
+	[data-slot="question-actions"] {
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		justify-content: flex-end;
+	}
 }
 
 [data-component="question-answers"] {
-  display: flex;
-  flex-direction: column;
-  gap: 12px;
-  padding: 8px 12px;
-
-  [data-slot="question-answer-item"] {
-    display: flex;
-    flex-direction: column;
-    gap: 2px;
-    font-size: 13px;
-
-    [data-slot="question-text"] {
-      color: var(--text-weak);
-    }
-
-    [data-slot="answer-text"] {
-      color: var(--text-strong);
-    }
-  }
+	display: flex;
+	flex-direction: column;
+	gap: 12px;
+	padding: 8px 12px;
+
+	[data-slot="question-answer-item"] {
+		display: flex;
+		flex-direction: column;
+		gap: 2px;
+		font-size: 13px;
+
+		[data-slot="question-text"] {
+			color: var(--text-weak);
+		}
+
+		[data-slot="answer-text"] {
+			color: var(--text-strong);
+		}
+	}
 }
 
 [data-component="apply-patch-files"] {
-  display: flex;
-  flex-direction: column;
+	display: flex;
+	flex-direction: column;
 }
 
 [data-component="apply-patch-file"] {
-  display: flex;
-  flex-direction: column;
-  border-top: 1px solid var(--border-weaker-base);
-
-  &:first-child {
-    border-top: 1px solid var(--border-weaker-base);
-  }
-
-  [data-slot="apply-patch-file-header"] {
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    padding: 8px 12px;
-    background-color: var(--surface-inset-base);
-  }
-
-  [data-slot="apply-patch-file-action"] {
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large);
-    color: var(--text-base);
-    flex-shrink: 0;
-
-    &[data-type="delete"] {
-      color: var(--text-critical-base);
-    }
-
-    &[data-type="add"] {
-      color: var(--text-success-base);
-    }
-
-    &[data-type="move"] {
-      color: var(--text-warning-base);
-    }
-  }
-
-  [data-slot="apply-patch-file-path"] {
-    font-family: var(--font-family-mono);
-    font-size: var(--font-size-small);
-    color: var(--text-weak);
-    overflow: hidden;
-    text-overflow: ellipsis;
-    white-space: nowrap;
-    flex-grow: 1;
-  }
-
-  [data-slot="apply-patch-deletion-count"] {
-    font-family: var(--font-family-mono);
-    font-size: var(--font-size-small);
-    color: var(--text-critical-base);
-    flex-shrink: 0;
-  }
+	display: flex;
+	flex-direction: column;
+	border-top: 1px solid var(--border-weaker-base);
+
+	&:first-child {
+		border-top: 1px solid var(--border-weaker-base);
+	}
+
+	[data-slot="apply-patch-file-header"] {
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		padding: 8px 12px;
+		background-color: var(--surface-inset-base);
+	}
+
+	[data-slot="apply-patch-file-action"] {
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large);
+		color: var(--text-base);
+		flex-shrink: 0;
+
+		&[data-type="delete"] {
+			color: var(--text-critical-base);
+		}
+
+		&[data-type="add"] {
+			color: var(--text-success-base);
+		}
+
+		&[data-type="move"] {
+			color: var(--text-warning-base);
+		}
+	}
+
+	[data-slot="apply-patch-file-path"] {
+		font-family: var(--font-family-mono);
+		font-size: var(--font-size-small);
+		color: var(--text-weak);
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+		flex-grow: 1;
+	}
+
+	[data-slot="apply-patch-deletion-count"] {
+		font-family: var(--font-family-mono);
+		font-size: var(--font-size-small);
+		color: var(--text-critical-base);
+		flex-shrink: 0;
+	}
 }
 
 [data-component="apply-patch-file-diff"] {
-  max-height: 420px;
-  overflow-y: auto;
-  scrollbar-width: none;
-  -ms-overflow-style: none;
-
-  &::-webkit-scrollbar {
-    display: none;
-  }
+	max-height: 420px;
+	overflow-y: auto;
+	scrollbar-width: none;
+	-ms-overflow-style: none;
+
+	&::-webkit-scrollbar {
+		display: none;
+	}
 }
 
 [data-component="tool-loaded-file"] {
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  padding: 4px 0 4px 28px;
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-small);
-  font-weight: var(--font-weight-regular);
-  line-height: var(--line-height-large);
-  color: var(--text-weak);
-
-  [data-component="icon"] {
-    flex-shrink: 0;
-    color: var(--icon-weak);
-  }
+	display: flex;
+	align-items: center;
+	gap: 8px;
+	padding: 4px 0 4px 28px;
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-small);
+	font-weight: var(--font-weight-regular);
+	line-height: var(--line-height-large);
+	color: var(--text-weak);
+
+	[data-component="icon"] {
+		flex-shrink: 0;
+		color: var(--icon-weak);
+	}
 }

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

@@ -49,6 +49,7 @@ import { Tooltip } from "./tooltip"
 import { IconButton } from "./icon-button"
 import { createAutoScroll } from "../hooks"
 import { createResizeObserver } from "@solid-primitives/resize-observer"
+import { MorphChevron } from "./morph-chevron"
 
 interface Diagnostic {
   range: {
@@ -415,7 +416,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
               toggleExpanded()
             }}
           >
-            <Icon name="chevron-down" size="small" />
+            <MorphChevron expanded={expanded()} />
           </button>
           <div data-slot="user-message-copy-wrapper">
             <Tooltip

+ 10 - 0
packages/ui/src/components/morph-chevron.css

@@ -0,0 +1,10 @@
+[data-slot="morph-chevron-svg"] {
+	width: 16px;
+	height: 16px;
+	display: block;
+	fill: none;
+	stroke-width: 1.5;
+	stroke: currentcolor;
+	stroke-linecap: round;
+	stroke-linejoin: round;
+}

+ 73 - 0
packages/ui/src/components/morph-chevron.tsx

@@ -0,0 +1,73 @@
+import { createEffect, createUniqueId, on } from "solid-js"
+
+export interface MorphChevronProps {
+  expanded: boolean
+  class?: string
+}
+
+const COLLAPSED = "M4 6L8 10L12 6"
+const EXPANDED = "M4 10L8 6L12 10"
+
+export function MorphChevron(props: MorphChevronProps) {
+  const id = createUniqueId()
+  let path: SVGPathElement | undefined
+  let expandAnim: SVGAnimateElement | undefined
+  let collapseAnim: SVGAnimateElement | undefined
+
+  createEffect(
+    on(
+      () => props.expanded,
+      (expanded, prev) => {
+        if (prev === undefined) {
+          // Set initial state without animation
+          path?.setAttribute("d", expanded ? EXPANDED : COLLAPSED)
+          return
+        }
+        if (expanded) {
+          expandAnim?.beginElement()
+        } else {
+          collapseAnim?.beginElement()
+        }
+      },
+    ),
+  )
+
+  return (
+    <svg
+      viewBox="0 0 16 16"
+      data-slot="morph-chevron-svg"
+      class={props.class}
+      xmlns="http://www.w3.org/2000/svg"
+      aria-hidden="true"
+    >
+      <path ref={path} d={COLLAPSED} id={`morph-chevron-path-${id}`}>
+        <animate
+          ref={(el) => {
+            expandAnim = el
+          }}
+          id={`morph-expand-${id}`}
+          attributeName="d"
+          dur="200ms"
+          fill="freeze"
+          calcMode="spline"
+          keySplines="0.25 0 0.5 1"
+          values="M4 6L8 10L12 6;M4 10L8 6L12 10"
+          begin="indefinite"
+        />
+        <animate
+          ref={(el) => {
+            collapseAnim = el
+          }}
+          id={`morph-collapse-${id}`}
+          attributeName="d"
+          dur="200ms"
+          fill="freeze"
+          calcMode="spline"
+          keySplines="0.25 0 0.5 1"
+          values="M4 10L8 6L12 10;M4 6L8 10L12 6"
+          begin="indefinite"
+        />
+      </path>
+    </svg>
+  )
+}

+ 127 - 89
packages/ui/src/components/popover.css

@@ -1,98 +1,136 @@
 [data-slot="popover-trigger"] {
-  display: inline-flex;
+	display: inline-flex;
 }
 
 [data-component="popover-content"] {
-  z-index: 50;
-  min-width: 200px;
-  max-width: 320px;
-  border-radius: var(--radius-md);
-  background-color: var(--surface-raised-stronger-non-alpha);
-
-  border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
-  background-clip: padding-box;
-  box-shadow: var(--shadow-md);
-
-  transform-origin: var(--kb-popover-content-transform-origin);
-
-  &:focus-within {
-    outline: none;
-  }
-
-  &[data-closed] {
-    animation: popover-close 0.15s ease-out;
-  }
-
-  &[data-expanded] {
-    animation: popover-open 0.15s ease-out;
-  }
-
-  [data-slot="popover-header"] {
-    display: flex;
-    padding: 12px;
-    padding-bottom: 0;
-    justify-content: space-between;
-    align-items: center;
-    gap: 8px;
-
-    [data-slot="popover-title"] {
-      flex: 1;
-      color: var(--text-strong);
-      margin: 0;
-
-      font-family: var(--font-family-sans);
-      font-size: var(--font-size-base);
-      font-style: normal;
-      font-weight: var(--font-weight-medium);
-      line-height: var(--line-height-large);
-      letter-spacing: var(--letter-spacing-normal);
-    }
-
-    [data-slot="popover-close-button"] {
-      flex-shrink: 0;
-    }
-  }
-
-  [data-slot="popover-description"] {
-    padding: 0 12px;
-    margin: 0;
-    color: var(--text-base);
-
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  [data-slot="popover-body"] {
-    padding: 12px;
-  }
-
-  [data-slot="popover-arrow"] {
-    fill: var(--surface-raised-stronger-non-alpha);
-  }
+	z-index: 50;
+	min-width: 200px;
+	max-width: 320px;
+	border-radius: var(--radius-md);
+	background-color: var(--surface-raised-stronger-non-alpha);
+
+	border: 1px solid color-mix(in oklch, var(--border-base) 50%, transparent);
+	background-clip: padding-box;
+	box-shadow: var(--shadow-md);
+
+	transform-origin: var(--kb-popover-content-transform-origin);
+
+	animation: popoverContentHide var(--transition-duration)
+		var(--transition-easing) forwards;
+
+	@starting-style {
+		animation: none;
+	}
+
+	&[data-expanded] {
+		pointer-events: auto;
+		animation: popoverContentShow var(--transition-duration)
+			var(--transition-easing) forwards;
+	}
+
+	[data-origin-top-right] {
+		transform-origin: top right;
+	}
+
+	[data-origin-top-left] {
+		transform-origin: top left;
+	}
+
+	[data-origin-bottom-right] {
+		transform-origin: bottom right;
+	}
+
+	[data-origin-bottom-left] {
+		transform-origin: bottom left;
+	}
+
+	&:focus-within {
+		outline: none;
+	}
+
+	[data-slot="popover-header"] {
+		display: flex;
+		padding: 12px;
+		padding-bottom: 0;
+		justify-content: space-between;
+		align-items: center;
+		gap: 8px;
+
+		[data-slot="popover-title"] {
+			flex: 1;
+			color: var(--text-strong);
+			margin: 0;
+
+			font-family: var(--font-family-sans);
+			font-size: var(--font-size-base);
+			font-style: normal;
+			font-weight: var(--font-weight-medium);
+			line-height: var(--line-height-large);
+			letter-spacing: var(--letter-spacing-normal);
+		}
+
+		[data-slot="popover-close-button"] {
+			flex-shrink: 0;
+		}
+	}
+
+	[data-slot="popover-description"] {
+		padding: 0 12px;
+		margin: 0;
+		color: var(--text-base);
+
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	[data-slot="popover-body"] {
+		padding: 12px;
+	}
+
+	[data-slot="popover-arrow"] {
+		fill: var(--surface-raised-stronger-non-alpha);
+	}
 }
 
-@keyframes popover-open {
-  from {
-    opacity: 0;
-    transform: scale(0.96);
-  }
-  to {
-    opacity: 1;
-    transform: scale(1);
-  }
+@keyframes popoverContentShow {
+	from {
+		opacity: 0;
+		transform: scaleY(0.95);
+	}
+	to {
+		opacity: 1;
+		transform: scaleY(1);
+	}
 }
 
-@keyframes popover-close {
-  from {
-    opacity: 1;
-    transform: scale(1);
-  }
-  to {
-    opacity: 0;
-    transform: scale(0.96);
-  }
+@keyframes popoverContentHide {
+	from {
+		opacity: 1;
+		transform: scaleY(1);
+	}
+	to {
+		opacity: 0;
+		transform: scaleY(0.95);
+	}
+}
+
+[data-component="model-popover-content"] {
+	transform-origin: var(--kb-popper-content-transform-origin);
+	pointer-events: none;
+	animation: popoverContentHide var(--transition-duration)
+		var(--transition-easing) forwards;
+
+	@starting-style {
+		animation: none;
+	}
+
+	&[data-expanded] {
+		pointer-events: auto;
+		animation: popoverContentShow var(--transition-duration)
+			var(--transition-easing) forwards;
+	}
 }

+ 7 - 9
packages/ui/src/components/progress-circle.css

@@ -1,12 +1,10 @@
 [data-component="progress-circle"] {
-  transform: rotate(-90deg);
+	color: inherit;
 
-  [data-slot="progress-circle-background"] {
-    stroke: var(--border-weak-base);
-  }
-
-  [data-slot="progress-circle-progress"] {
-    stroke: var(--border-active);
-    transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1);
-  }
+	[data-slot="progress-circle-background"] {
+		transform-origin: 50% 50%;
+		transform: rotate(270deg);
+		stroke-opacity: 0.5;
+		transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1);
+	}
 }

+ 16 - 30
packages/ui/src/components/progress-circle.tsx

@@ -1,4 +1,4 @@
-import { type ComponentProps, createMemo, splitProps } from "solid-js"
+import { type ComponentProps, splitProps } from "solid-js"
 
 export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class" | "classList"> {
   percentage: number
@@ -9,26 +9,15 @@ export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class"
 export function ProgressCircle(props: ProgressCircleProps) {
   const [split, rest] = splitProps(props, ["percentage", "size", "strokeWidth", "class", "classList"])
 
-  const size = () => split.size || 16
-  const strokeWidth = () => split.strokeWidth || 3
-
-  const viewBoxSize = 16
-  const center = viewBoxSize / 2
-  const radius = () => center - strokeWidth() / 2
-  const circumference = createMemo(() => 2 * Math.PI * radius())
-
-  const offset = createMemo(() => {
-    const clampedPercentage = Math.max(0, Math.min(100, split.percentage || 0))
-    const progress = clampedPercentage / 100
-    return circumference() * (1 - progress)
-  })
+  const size = () => split.size || 18
+  const r = 7
 
   return (
     <svg
       {...rest}
       width={size()}
       height={size()}
-      viewBox={`0 0 ${viewBoxSize} ${viewBoxSize}`}
+      viewBox="0 0 18 18"
       fill="none"
       data-component="progress-circle"
       classList={{
@@ -36,21 +25,18 @@ export function ProgressCircle(props: ProgressCircleProps) {
         [split.class ?? ""]: !!split.class,
       }}
     >
-      <circle
-        cx={center}
-        cy={center}
-        r={radius()}
-        data-slot="progress-circle-background"
-        stroke-width={strokeWidth()}
-      />
-      <circle
-        cx={center}
-        cy={center}
-        r={radius()}
-        data-slot="progress-circle-progress"
-        stroke-width={strokeWidth()}
-        stroke-dasharray={circumference().toString()}
-        stroke-dashoffset={offset()}
+      <circle cx="9" cy="9" r="7.75" stroke="currentColor" stroke-width="1.5" />
+      <path
+        opacity="0.5"
+        d={(() => {
+          const pct = Math.min(100, Math.max(0, split.percentage))
+          const angle = (pct / 100) * 2 * Math.PI - Math.PI / 2
+          const x = 9 + r * Math.cos(angle)
+          const y = 9 + r * Math.sin(angle)
+          const largeArc = pct > 50 ? 1 : 0
+          return `M9 2A${r} ${r} 0 ${largeArc} 1 ${x} ${y}L9 9Z`
+        })()}
+        fill="currentColor"
       />
     </svg>
   )

+ 3 - 3
packages/ui/src/components/provider-icon.css

@@ -1,5 +1,5 @@
 [data-component="provider-icon"] {
-  flex-shrink: 0;
-  width: 16px;
-  height: 16px;
+	flex-shrink: 0;
+	width: 16px;
+	height: 16px;
 }

+ 167 - 155
packages/ui/src/components/radio-group.css

@@ -1,157 +1,169 @@
 [data-component="radio-group"] {
-  display: flex;
-  flex-direction: column;
-  gap: calc(var(--spacing) * 2);
-
-  [data-slot="radio-group-wrapper"] {
-    all: unset;
-    background-color: var(--surface-base);
-    border-radius: var(--radius-md);
-    box-shadow: var(--shadow-xs-border);
-    margin: 0;
-    padding: 0;
-    position: relative;
-    width: fit-content;
-  }
-
-  [data-slot="radio-group-items"] {
-    display: inline-flex;
-    list-style: none;
-    flex-direction: row;
-  }
-
-  [data-slot="radio-group-indicator"] {
-    background: var(--button-secondary-base);
-    border-radius: var(--radius-md);
-    box-shadow: var(--shadow-xs-border);
-    content: "";
-    opacity: var(--indicator-opacity, 1);
-    position: absolute;
-    transition:
-      opacity 300ms ease-in-out,
-      box-shadow 100ms ease-in-out,
-      width 150ms ease,
-      height 150ms ease,
-      transform 150ms ease;
-  }
-
-  [data-slot="radio-group-item"] {
-    position: relative;
-  }
-
-  /* Separator between items */
-  [data-slot="radio-group-item"]:not(:first-of-type)::before {
-    background: var(--border-weak-base);
-    border-radius: var(--radius-xs);
-    content: "";
-    inset: 6px 0;
-    position: absolute;
-    transition: opacity 150ms ease;
-    width: 1px;
-    transform: translateX(-0.5px);
-  }
-
-  /* Hide separator when item or previous item is checked */
-  [data-slot="radio-group-item"]:has([data-slot="radio-group-item-input"][data-checked])::before,
-  [data-slot="radio-group-item"]:has([data-slot="radio-group-item-input"][data-checked])
-    + [data-slot="radio-group-item"]::before {
-    opacity: 0;
-  }
-
-  [data-slot="radio-group-item-label"] {
-    color: var(--text-weak);
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-weight: var(--font-weight-medium);
-    border-radius: var(--radius-md);
-    cursor: pointer;
-    display: flex;
-    flex-wrap: nowrap;
-    gap: calc(var(--spacing) * 1);
-    line-height: 1;
-    padding: 6px 12px;
-    place-content: center;
-    position: relative;
-    transition-duration: 150ms;
-    transition-property: color, opacity;
-    transition-timing-function: ease-in-out;
-    user-select: none;
-  }
-
-  [data-slot="radio-group-item-input"] {
-    all: unset;
-  }
-
-  /* Checked state */
-  [data-slot="radio-group-item-input"][data-checked] + [data-slot="radio-group-item-label"] {
-    color: var(--text-strong);
-  }
-
-  /* Disabled state */
-  [data-slot="radio-group-item-input"][data-disabled] + [data-slot="radio-group-item-label"] {
-    cursor: not-allowed;
-    opacity: 0.5;
-  }
-
-  /* Hover state for unchecked, enabled items */
-  [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled]) + [data-slot="radio-group-item-label"] {
-    cursor: pointer;
-    user-select: none;
-  }
-
-  [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
-    + [data-slot="radio-group-item-label"]:hover {
-    color: var(--text-base);
-  }
-
-  [data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
-    + [data-slot="radio-group-item-label"]:active {
-    opacity: 0.7;
-  }
-
-  /* Focus state */
-  [data-slot="radio-group-wrapper"]:has([data-slot="radio-group-item-input"]:focus-visible)
-    [data-slot="radio-group-indicator"] {
-    box-shadow: var(--shadow-xs-border-focus);
-  }
-
-  /* Hide indicator when nothing is checked */
-  [data-slot="radio-group-wrapper"]:not(:has([data-slot="radio-group-item-input"][data-checked]))
-    [data-slot="radio-group-indicator"] {
-    --indicator-opacity: 0;
-  }
-
-  /* Vertical orientation */
-  &[aria-orientation="vertical"] [data-slot="radio-group-items"] {
-    flex-direction: column;
-  }
-
-  &[aria-orientation="vertical"] [data-slot="radio-group-item"]:not(:first-of-type)::before {
-    height: 1px;
-    width: auto;
-    inset: 0 6px;
-    transform: translateY(-0.5px);
-  }
-
-  /* Small size variant */
-  &[data-size="small"] {
-    [data-slot="radio-group-item-label"] {
-      font-size: 12px;
-      padding: 4px 8px;
-    }
-
-    [data-slot="radio-group-item"]:not(:first-of-type)::before {
-      inset: 4px 0;
-    }
-
-    &[aria-orientation="vertical"] [data-slot="radio-group-item"]:not(:first-of-type)::before {
-      inset: 0 4px;
-    }
-  }
-
-  /* Disabled root state */
-  &[data-disabled] {
-    opacity: 0.5;
-    cursor: not-allowed;
-  }
+	display: flex;
+	flex-direction: column;
+	gap: calc(var(--spacing) * 2);
+
+	[data-slot="radio-group-wrapper"] {
+		all: unset;
+		background-color: var(--surface-base);
+		border-radius: var(--radius-md);
+		box-shadow: var(--shadow-xs-border);
+		margin: 0;
+		padding: 0;
+		position: relative;
+		width: fit-content;
+	}
+
+	[data-slot="radio-group-items"] {
+		display: inline-flex;
+		list-style: none;
+		flex-direction: row;
+	}
+
+	[data-slot="radio-group-indicator"] {
+		background: var(--button-secondary-base);
+		border-radius: var(--radius-md);
+		box-shadow: var(--shadow-xs-border);
+		content: "";
+		opacity: var(--indicator-opacity, 1);
+		position: absolute;
+		transition-property: opacity, box-shadow, width, height, transform;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+	}
+
+	[data-slot="radio-group-item"] {
+		position: relative;
+	}
+
+	/* Separator between items */
+	[data-slot="radio-group-item"]:not(:first-of-type)::before {
+		background: var(--border-weak-base);
+		border-radius: var(--radius-xs);
+		content: "";
+		inset: 6px 0;
+		position: absolute;
+		transition-property: opacity;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+		width: 1px;
+		transform: translateX(-0.5px);
+	}
+
+	/* Hide separator when item or previous item is checked */
+	[data-slot="radio-group-item"]:has(
+			[data-slot="radio-group-item-input"][data-checked]
+		)::before,
+	[data-slot="radio-group-item"]:has(
+			[data-slot="radio-group-item-input"][data-checked]
+		)
+		+ [data-slot="radio-group-item"]::before {
+		opacity: 0;
+	}
+
+	[data-slot="radio-group-item-label"] {
+		color: var(--text-weak);
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-weight: var(--font-weight-medium);
+		border-radius: var(--radius-md);
+		cursor: pointer;
+		display: flex;
+		flex-wrap: nowrap;
+		gap: calc(var(--spacing) * 1);
+		line-height: 1;
+		padding: 6px 12px;
+		place-content: center;
+		position: relative;
+		transition-property: color, opacity;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+		user-select: none;
+	}
+
+	[data-slot="radio-group-item-input"] {
+		all: unset;
+	}
+
+	/* Checked state */
+	[data-slot="radio-group-item-input"][data-checked]
+		+ [data-slot="radio-group-item-label"] {
+		color: var(--text-strong);
+	}
+
+	/* Disabled state */
+	[data-slot="radio-group-item-input"][data-disabled]
+		+ [data-slot="radio-group-item-label"] {
+		cursor: not-allowed;
+		opacity: 0.5;
+	}
+
+	/* Hover state for unchecked, enabled items */
+	[data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
+		+ [data-slot="radio-group-item-label"] {
+		cursor: pointer;
+		user-select: none;
+	}
+
+	[data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
+		+ [data-slot="radio-group-item-label"]:hover {
+		color: var(--text-base);
+	}
+
+	[data-slot="radio-group-item-input"]:not([data-checked], [data-disabled])
+		+ [data-slot="radio-group-item-label"]:active {
+		opacity: 0.7;
+	}
+
+	/* Focus state */
+	[data-slot="radio-group-wrapper"]:has(
+			[data-slot="radio-group-item-input"]:focus-visible
+		)
+		[data-slot="radio-group-indicator"] {
+		box-shadow: var(--shadow-xs-border-focus);
+	}
+
+	/* Hide indicator when nothing is checked */
+	[data-slot="radio-group-wrapper"]:not(
+			:has([data-slot="radio-group-item-input"][data-checked])
+		)
+		[data-slot="radio-group-indicator"] {
+		--indicator-opacity: 0;
+	}
+
+	/* Vertical orientation */
+	&[aria-orientation="vertical"] [data-slot="radio-group-items"] {
+		flex-direction: column;
+	}
+
+	&[aria-orientation="vertical"]
+		[data-slot="radio-group-item"]:not(:first-of-type)::before {
+		height: 1px;
+		width: auto;
+		inset: 0 6px;
+		transform: translateY(-0.5px);
+	}
+
+	/* Small size variant */
+	&[data-size="small"] {
+		[data-slot="radio-group-item-label"] {
+			font-size: 12px;
+			padding: 4px 8px;
+		}
+
+		[data-slot="radio-group-item"]:not(:first-of-type)::before {
+			inset: 4px 0;
+		}
+
+		&[aria-orientation="vertical"]
+			[data-slot="radio-group-item"]:not(:first-of-type)::before {
+			inset: 0 4px;
+		}
+	}
+
+	/* Disabled root state */
+	&[data-disabled] {
+		opacity: 0.5;
+		cursor: not-allowed;
+	}
 }

+ 10 - 0
packages/ui/src/components/reasoning-icon.css

@@ -0,0 +1,10 @@
+[data-component="reasoning-icon"] {
+	color: var(--icon-strong-base);
+
+	[data-slot="reasoning-icon-percentage"] {
+		transition: clip-path 200ms cubic-bezier(0.25, 0, 0.5, 1);
+		clip-path: inset(
+			calc(100% - var(--reasoning-icon-percentage) * 100%) 0 0 0
+		);
+	}
+}

+ 32 - 0
packages/ui/src/components/reasoning-icon.tsx

@@ -0,0 +1,32 @@
+import { type ComponentProps, splitProps } from "solid-js"
+
+export interface ReasoningIconProps extends Pick<ComponentProps<"svg">, "class" | "classList"> {
+  percentage: number
+  size?: number
+  strokeWidth?: number
+}
+
+export function ReasoningIcon(props: ReasoningIconProps) {
+  const [split, rest] = splitProps(props, ["percentage", "size", "strokeWidth", "class", "classList"])
+
+  const size = () => split.size || 16
+  const strokeWidth = () => split.strokeWidth || 1.25
+
+  return (
+    <svg
+      {...rest}
+      width={size()}
+      height={size()}
+      viewBox={`0 0 16 16`}
+      fill="none"
+      data-component="reasoning-icon"
+      classList={{
+        ...(split.classList ?? {}),
+        [split.class ?? ""]: !!split.class,
+      }}
+    >
+      <path d="M5.83196 10.3225V11.1666C5.83196 11.7189 6.27967 12.1666 6.83196 12.1666H9.16687C9.71915 12.1666 10.1669 11.7189 10.1669 11.1666V10.3225M5.83196 10.3225C5.55695 10.1843 5.29695 10.0206 5.05505 9.83459C3.90601 8.95086 3.16549 7.56219 3.16549 6.00055C3.16549 3.33085 5.32971 1.16663 7.99941 1.16663C10.6691 1.16663 12.8333 3.33085 12.8333 6.00055C12.8333 7.56219 12.0928 8.95086 10.9438 9.83459C10.7019 10.0206 10.4419 10.1843 10.1669 10.3225M5.83196 10.3225H10.1669M6.5 14.1666H9.5" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" />
+      <circle cx="8" cy="5.83325" r="2.86364" fill="currentColor" stroke="currentColor" stroke-width={strokeWidth()} style={{ '--reasoning-icon-percentage': split.percentage / 100 }} data-slot="reasoning-icon-percentage" />
+    </svg>
+  )
+}

+ 58 - 56
packages/ui/src/components/resize-handle.css

@@ -1,58 +1,60 @@
 [data-component="resize-handle"] {
-  position: absolute;
-  z-index: 10;
-
-  &::after {
-    content: "";
-    position: absolute;
-    opacity: 0;
-    transition: opacity 0.15s ease-in-out;
-  }
-
-  &:hover::after,
-  &:active::after {
-    opacity: 1;
-  }
-
-  &[data-direction="horizontal"] {
-    inset-block: 0;
-    inset-inline-end: 0;
-    width: 8px;
-    transform: translateX(50%);
-    cursor: col-resize;
-
-    &[data-edge="start"] {
-      inset-inline-start: 0;
-      inset-inline-end: auto;
-      transform: translateX(-50%);
-    }
-
-    &::after {
-      width: 3px;
-      inset-block: 0;
-      inset-inline-start: 50%;
-      transform: translateX(-50%);
-    }
-  }
-
-  &[data-direction="vertical"] {
-    inset-inline: 0;
-    inset-block-start: 0;
-    height: 8px;
-    transform: translateY(-50%);
-    cursor: row-resize;
-
-    &[data-edge="end"] {
-      inset-block-start: auto;
-      inset-block-end: 0;
-      transform: translateY(50%);
-    }
-
-    &::after {
-      height: 3px;
-      inset-inline: 0;
-      inset-block-start: 50%;
-      transform: translateY(-50%);
-    }
-  }
+	position: absolute;
+	z-index: 10;
+
+	&::after {
+		content: "";
+		position: absolute;
+		opacity: 0;
+		transition-property: opacity;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+	}
+
+	&:hover::after,
+	&:active::after {
+		opacity: 1;
+	}
+
+	&[data-direction="horizontal"] {
+		inset-block: 0;
+		inset-inline-end: 0;
+		width: 8px;
+		transform: translateX(50%);
+		cursor: col-resize;
+
+		&[data-edge="start"] {
+			inset-inline-start: 0;
+			inset-inline-end: auto;
+			transform: translateX(-50%);
+		}
+
+		&::after {
+			width: 3px;
+			inset-block: 0;
+			inset-inline-start: 50%;
+			transform: translateX(-50%);
+		}
+	}
+
+	&[data-direction="vertical"] {
+		inset-inline: 0;
+		inset-block-start: 0;
+		height: 8px;
+		transform: translateY(-50%);
+		cursor: row-resize;
+
+		&[data-edge="end"] {
+			inset-block-start: auto;
+			inset-block-end: 0;
+			transform: translateY(50%);
+		}
+
+		&::after {
+			height: 3px;
+			inset-inline: 0;
+			inset-block-start: 50%;
+			transform: translateY(-50%);
+		}
+	}
 }

+ 122 - 0
packages/ui/src/components/scroll-fade.css

@@ -0,0 +1,122 @@
+[data-component="scroll-fade"] {
+	overflow: auto;
+	overscroll-behavior: contain;
+	scrollbar-width: none;
+	box-sizing: border-box;
+	color: inherit;
+	font: inherit;
+	-ms-overflow-style: none;
+
+	&::-webkit-scrollbar {
+		display: none;
+	}
+
+	&[data-direction="horizontal"] {
+		overflow-x: auto;
+		overflow-y: hidden;
+
+		/* Both fades */
+		&[data-fade-start][data-fade-end] {
+			mask-image: linear-gradient(
+				to right,
+				transparent,
+				black var(--scroll-fade-start),
+				black calc(100% - var(--scroll-fade-end)),
+				transparent
+			);
+			-webkit-mask-image: linear-gradient(
+				to right,
+				transparent,
+				black var(--scroll-fade-start),
+				black calc(100% - var(--scroll-fade-end)),
+				transparent
+			);
+		}
+
+		/* Only start fade */
+		&[data-fade-start]:not([data-fade-end]) {
+			mask-image: linear-gradient(
+				to right,
+				transparent,
+				black var(--scroll-fade-start),
+				black 100%
+			);
+			-webkit-mask-image: linear-gradient(
+				to right,
+				transparent,
+				black var(--scroll-fade-start),
+				black 100%
+			);
+		}
+
+		/* Only end fade */
+		&:not([data-fade-start])[data-fade-end] {
+			mask-image: linear-gradient(
+				to right,
+				black 0%,
+				black calc(100% - var(--scroll-fade-end)),
+				transparent
+			);
+			-webkit-mask-image: linear-gradient(
+				to right,
+				black 0%,
+				black calc(100% - var(--scroll-fade-end)),
+				transparent
+			);
+		}
+	}
+
+	&[data-direction="vertical"] {
+		overflow-y: auto;
+		overflow-x: hidden;
+
+		&[data-fade-start][data-fade-end] {
+			mask-image: linear-gradient(
+				to bottom,
+				transparent,
+				black var(--scroll-fade-start),
+				black calc(100% - var(--scroll-fade-end)),
+				transparent
+			);
+			-webkit-mask-image: linear-gradient(
+				to bottom,
+				transparent,
+				black var(--scroll-fade-start),
+				black calc(100% - var(--scroll-fade-end)),
+				transparent
+			);
+		}
+
+		/* Only start fade */
+		&[data-fade-start]:not([data-fade-end]) {
+			mask-image: linear-gradient(
+				to bottom,
+				transparent,
+				black var(--scroll-fade-start),
+				black 100%
+			);
+			-webkit-mask-image: linear-gradient(
+				to bottom,
+				transparent,
+				black var(--scroll-fade-start),
+				black 100%
+			);
+		}
+
+		/* Only end fade */
+		&:not([data-fade-start])[data-fade-end] {
+			mask-image: linear-gradient(
+				to bottom,
+				black 0%,
+				black calc(100% - var(--scroll-fade-end)),
+				transparent
+			);
+			-webkit-mask-image: linear-gradient(
+				to bottom,
+				black 0%,
+				black calc(100% - var(--scroll-fade-end)),
+				transparent
+			);
+		}
+	}
+}

+ 207 - 0
packages/ui/src/components/scroll-fade.tsx

@@ -0,0 +1,207 @@
+import { type JSX, createEffect, createSignal, onCleanup, onMount, splitProps } from "solid-js"
+import "./scroll-fade.css"
+
+export interface ScrollFadeProps extends JSX.HTMLAttributes<HTMLDivElement> {
+  direction?: "horizontal" | "vertical"
+  fadeStartSize?: number
+  fadeEndSize?: number
+  trackTransformSelector?: string
+  ref?: (el: HTMLDivElement) => void
+}
+
+export function ScrollFade(props: ScrollFadeProps) {
+  const [local, others] = splitProps(props, [
+    "children",
+    "direction",
+    "fadeStartSize",
+    "fadeEndSize",
+    "trackTransformSelector",
+    "class",
+    "style",
+    "ref",
+  ])
+
+  const direction = () => local.direction ?? "vertical"
+  const fadeStartSize = () => local.fadeStartSize ?? 20
+  const fadeEndSize = () => local.fadeEndSize ?? 20
+
+  const getTransformOffset = (element: Element): number => {
+    const style = getComputedStyle(element)
+    const transform = style.transform
+    if (!transform || transform === "none") return 0
+
+    const match = transform.match(/matrix(?:3d)?\(([^)]+)\)/)
+    if (!match) return 0
+
+    const values = match[1].split(",").map((v) => parseFloat(v.trim()))
+    const isHorizontal = direction() === "horizontal"
+
+    if (transform.startsWith("matrix3d")) {
+      return isHorizontal ? -(values[12] || 0) : -(values[13] || 0)
+    } else {
+      return isHorizontal ? -(values[4] || 0) : -(values[5] || 0)
+    }
+  }
+
+  let containerRef: HTMLDivElement | undefined
+
+  const [fadeStart, setFadeStart] = createSignal(0)
+  const [fadeEnd, setFadeEnd] = createSignal(0)
+  const [isScrollable, setIsScrollable] = createSignal(false)
+
+  let lastScrollPos = 0
+  let lastTransformPos = 0
+  let lastScrollSize = 0
+  let lastClientSize = 0
+
+  const updateFade = () => {
+    if (!containerRef) return
+
+    const isHorizontal = direction() === "horizontal"
+    const scrollPos = isHorizontal ? containerRef.scrollLeft : containerRef.scrollTop
+    const scrollSize = isHorizontal ? containerRef.scrollWidth : containerRef.scrollHeight
+    const clientSize = isHorizontal ? containerRef.clientWidth : containerRef.clientHeight
+
+    let transformPos = 0
+    if (local.trackTransformSelector) {
+      const transformElement = containerRef.querySelector(local.trackTransformSelector)
+      if (transformElement) {
+        transformPos = getTransformOffset(transformElement)
+      }
+    }
+
+    const effectiveScrollPos = Math.max(scrollPos, transformPos)
+
+    if (
+      effectiveScrollPos === lastScrollPos &&
+      transformPos === lastTransformPos &&
+      scrollSize === lastScrollSize &&
+      clientSize === lastClientSize
+    ) {
+      return
+    }
+
+    lastScrollPos = effectiveScrollPos
+    lastTransformPos = transformPos
+    lastScrollSize = scrollSize
+    lastClientSize = clientSize
+
+    const maxScroll = scrollSize - clientSize
+    const canScroll = maxScroll > 1
+
+    setIsScrollable(canScroll)
+
+    if (!canScroll) {
+      setFadeStart(0)
+      setFadeEnd(0)
+      return
+    }
+
+    const progress = maxScroll > 0 ? effectiveScrollPos / maxScroll : 0
+
+    const startProgress = Math.min(progress / 0.1, 1)
+    setFadeStart(startProgress * fadeStartSize())
+
+    const endProgress = progress > 0.9 ? (1 - progress) / 0.1 : 1
+    setFadeEnd(Math.max(0, endProgress) * fadeEndSize())
+  }
+
+  onMount(() => {
+    if (!containerRef) return
+
+    updateFade()
+
+    let rafId: number | undefined
+    let isPolling = false
+    let pollTimeout: ReturnType<typeof setTimeout> | undefined
+
+    const startPolling = () => {
+      if (isPolling) return
+      isPolling = true
+
+      const pollScroll = () => {
+        updateFade()
+        rafId = requestAnimationFrame(pollScroll)
+      }
+      rafId = requestAnimationFrame(pollScroll)
+    }
+
+    const stopPolling = () => {
+      if (!isPolling) return
+      isPolling = false
+      if (rafId !== undefined) {
+        cancelAnimationFrame(rafId)
+        rafId = undefined
+      }
+    }
+
+    const schedulePollingStop = () => {
+      if (pollTimeout !== undefined) clearTimeout(pollTimeout)
+      pollTimeout = setTimeout(stopPolling, 1000)
+    }
+
+    const onActivity = () => {
+      updateFade()
+      if (local.trackTransformSelector) {
+        startPolling()
+        schedulePollingStop()
+      }
+    }
+
+    containerRef.addEventListener("scroll", onActivity, { passive: true })
+
+    const resizeObserver = new ResizeObserver(() => {
+      lastScrollSize = 0
+      lastClientSize = 0
+      onActivity()
+    })
+    resizeObserver.observe(containerRef)
+
+    const mutationObserver = new MutationObserver(() => {
+      lastScrollSize = 0
+      lastClientSize = 0
+      requestAnimationFrame(onActivity)
+    })
+    mutationObserver.observe(containerRef, {
+      childList: true,
+      subtree: true,
+      characterData: true,
+    })
+
+    onCleanup(() => {
+      containerRef?.removeEventListener("scroll", onActivity)
+      resizeObserver.disconnect()
+      mutationObserver.disconnect()
+      stopPolling()
+      if (pollTimeout !== undefined) clearTimeout(pollTimeout)
+    })
+  })
+
+  createEffect(() => {
+    local.children
+    requestAnimationFrame(updateFade)
+  })
+
+  return (
+    <div
+      ref={(el) => {
+        containerRef = el
+        local.ref?.(el)
+      }}
+      data-component="scroll-fade"
+      data-direction={direction()}
+      data-scrollable={isScrollable() || undefined}
+      data-fade-start={fadeStart() > 0 || undefined}
+      data-fade-end={fadeEnd() > 0 || undefined}
+      class={local.class}
+      style={{
+        ...(typeof local.style === "object" ? local.style : {}),
+        "--scroll-fade-start": `${fadeStart()}px`,
+        "--scroll-fade-end": `${fadeEnd()}px`,
+      }}
+      {...others}
+    >
+      {local.children}
+    </div>
+  )
+}

+ 141 - 0
packages/ui/src/components/scroll-reveal.tsx

@@ -0,0 +1,141 @@
+import { type JSX, onCleanup, splitProps } from "solid-js"
+import { ScrollFade, type ScrollFadeProps } from './scroll-fade'
+
+const SCROLL_SPEED = 60
+const PAUSE_DURATION = 800
+
+type ScrollAnimationState = {
+  rafId: number | null
+  startTime: number
+  running: boolean
+}
+
+const startScrollAnimation = (containerEl: HTMLElement): ScrollAnimationState | null => {
+  containerEl.offsetHeight
+
+  const extraWidth = containerEl.scrollWidth - containerEl.clientWidth
+
+  if (extraWidth <= 0) {
+    return null
+  }
+
+  const scrollDuration = (extraWidth / SCROLL_SPEED) * 1000
+  const totalDuration = PAUSE_DURATION + scrollDuration + PAUSE_DURATION + scrollDuration + PAUSE_DURATION
+
+  const state: ScrollAnimationState = {
+    rafId: null,
+    startTime: performance.now(),
+    running: true,
+  }
+
+  const animate = (currentTime: number) => {
+    if (!state.running) return
+
+    const elapsed = currentTime - state.startTime
+    const progress = (elapsed % totalDuration) / totalDuration
+
+    const pausePercent = PAUSE_DURATION / totalDuration
+    const scrollPercent = scrollDuration / totalDuration
+
+    const pauseEnd1 = pausePercent
+    const scrollEnd1 = pauseEnd1 + scrollPercent
+    const pauseEnd2 = scrollEnd1 + pausePercent
+    const scrollEnd2 = pauseEnd2 + scrollPercent
+
+    let scrollPos = 0
+
+    if (progress < pauseEnd1) {
+      scrollPos = 0
+    } else if (progress < scrollEnd1) {
+      const scrollProgress = (progress - pauseEnd1) / scrollPercent
+      scrollPos = scrollProgress * extraWidth
+    } else if (progress < pauseEnd2) {
+      scrollPos = extraWidth
+    } else if (progress < scrollEnd2) {
+      const scrollProgress = (progress - pauseEnd2) / scrollPercent
+      scrollPos = extraWidth * (1 - scrollProgress)
+    } else {
+      scrollPos = 0
+    }
+
+    containerEl.scrollLeft = scrollPos
+    state.rafId = requestAnimationFrame(animate)
+  }
+
+  state.rafId = requestAnimationFrame(animate)
+  return state
+}
+
+const stopScrollAnimation = (state: ScrollAnimationState | null, containerEl?: HTMLElement) => {
+  if (state) {
+    state.running = false
+    if (state.rafId !== null) {
+      cancelAnimationFrame(state.rafId)
+    }
+  }
+  if (containerEl) {
+    containerEl.scrollLeft = 0
+  }
+}
+
+export interface ScrollRevealProps extends Omit<ScrollFadeProps, "direction"> {
+  hoverDelay?: number
+}
+
+export function ScrollReveal(props: ScrollRevealProps) {
+  const [local, others] = splitProps(props, ["children", "hoverDelay", "ref"])
+
+  const hoverDelay = () => local.hoverDelay ?? 300
+
+  let containerRef: HTMLDivElement | undefined
+  let hoverTimeout: ReturnType<typeof setTimeout> | undefined
+  let scrollAnimationState: ScrollAnimationState | null = null
+
+  const handleMouseEnter: JSX.EventHandler<HTMLDivElement, MouseEvent> = () => {
+    hoverTimeout = setTimeout(() => {
+      if (!containerRef) return
+
+      containerRef.offsetHeight
+
+      const isScrollable = containerRef.scrollWidth > containerRef.clientWidth + 1
+
+      if (isScrollable) {
+        stopScrollAnimation(scrollAnimationState, containerRef)
+        scrollAnimationState = startScrollAnimation(containerRef)
+      }
+    }, hoverDelay())
+  }
+
+  const handleMouseLeave: JSX.EventHandler<HTMLDivElement, MouseEvent> = () => {
+    if (hoverTimeout) {
+      clearTimeout(hoverTimeout)
+      hoverTimeout = undefined
+    }
+    stopScrollAnimation(scrollAnimationState, containerRef)
+    scrollAnimationState = null
+  }
+
+  onCleanup(() => {
+    if (hoverTimeout) {
+      clearTimeout(hoverTimeout)
+    }
+    stopScrollAnimation(scrollAnimationState, containerRef)
+  })
+
+  return (
+    <ScrollFade
+      ref={(el) => {
+        containerRef = el
+        local.ref?.(el)
+      }}
+      fadeStartSize={8}
+      fadeEndSize={8}
+      direction="horizontal"
+      onMouseEnter={handleMouseEnter}
+      onMouseLeave={handleMouseLeave}
+      {...others}
+    >
+      {local.children}
+    </ScrollFade>
+  )
+}

+ 156 - 193
packages/ui/src/components/select.css

@@ -1,202 +1,165 @@
 [data-component="select"] {
-  [data-slot="select-select-trigger"] {
-    padding: 0 4px 0 8px;
-    box-shadow: none;
-
-    [data-slot="select-select-trigger-value"] {
-      overflow: hidden;
-      text-overflow: ellipsis;
-      white-space: nowrap;
-    }
-    [data-slot="select-select-trigger-icon"] {
-      width: 16px;
-      height: 16px;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      flex-shrink: 0;
-      color: var(--text-weak);
-      transition: transform 0.1s ease-in-out;
-    }
-
-    &[data-expanded] {
-      &[data-variant="secondary"] {
-        background-color: var(--button-secondary-hover);
-      }
-      &[data-variant="ghost"] {
-        background-color: var(--surface-raised-base-active);
-      }
-      &[data-variant="primary"] {
-        background-color: var(--icon-strong-active);
-      }
-    }
-
-    &:not([data-expanded]):focus-visible {
-      &[data-variant="secondary"] {
-        background-color: var(--button-secondary-base);
-      }
-      &[data-variant="ghost"] {
-        background-color: var(--surface-raised-base-hover);
-      }
-      &[data-variant="primary"] {
-        background-color: var(--icon-strong-base);
-      }
-    }
-  }
-
-  &[data-trigger-style="settings"] {
-    [data-slot="select-select-trigger"] {
-      padding: 6px 6px 6px 12px;
-      box-shadow: none;
-      border-radius: 6px;
-      min-width: 160px;
-      height: 32px;
-      justify-content: flex-end;
-      gap: 12px;
-      background-color: transparent;
-
-      [data-slot="select-select-trigger-value"] {
-        overflow: hidden;
-        text-overflow: ellipsis;
-        white-space: nowrap;
-        font-size: var(--font-size-base);
-        font-weight: var(--font-weight-regular);
-      }
-      [data-slot="select-select-trigger-icon"] {
-        width: 16px;
-        height: 20px;
-        display: flex;
-        align-items: center;
-        justify-content: center;
-        flex-shrink: 0;
-        color: var(--text-weak);
-        background-color: var(--surface-raised-base);
-        border-radius: 4px;
-        transition: transform 0.1s ease-in-out;
-      }
-
-      &[data-slot="select-select-trigger"]:hover:not(:disabled),
-      &[data-slot="select-select-trigger"][data-expanded],
-      &[data-slot="select-select-trigger"][data-expanded]:hover:not(:disabled) {
-        background-color: var(--input-base);
-        box-shadow: var(--shadow-xs-border-base);
-      }
-
-      &:not([data-expanded]):focus {
-        background-color: transparent;
-        box-shadow: none;
-      }
-    }
-  }
+	[data-slot="select-select-trigger"] {
+		display: flex;
+		padding: 4px 8px !important;
+		align-items: center;
+		justify-content: space-between;
+		box-shadow: none;
+		transition-property: background-color;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+
+		[data-slot="select-select-trigger-value"] {
+			overflow: hidden;
+			text-overflow: ellipsis;
+			white-space: nowrap;
+		}
+		[data-slot="select-select-trigger-icon"] {
+			width: 16px;
+			height: 16px;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			flex-shrink: 0;
+			color: var(--icon-base);
+		}
+
+		&:hover,
+		&[data-expanded] {
+			&[data-variant="secondary"] {
+				background-color: var(--button-secondary-hover);
+			}
+			&[data-variant="ghost"] {
+				background-color: var(--surface-raised-base-active);
+			}
+			&[data-variant="primary"] {
+				background-color: var(--icon-strong-active);
+			}
+		}
+		&:not([data-expanded]):focus,
+		&:not([data-expanded]):focus-visible {
+			&[data-variant="secondary"] {
+				background-color: var(--button-secondary-base);
+			}
+			&[data-variant="ghost"] {
+				background-color: transparent;
+			}
+			&[data-variant="primary"] {
+				background-color: var(--icon-strong-base);
+			}
+		}
+	}
 }
 
 [data-component="select-content"] {
-  min-width: 104px;
-  max-width: 23rem;
-  overflow: hidden;
-  border-radius: var(--radius-md);
-  background-color: var(--surface-raised-stronger-non-alpha);
-  padding: 4px;
-  box-shadow: var(--shadow-xs-border);
-  z-index: 60;
-
-  &[data-expanded] {
-    animation: select-open 0.15s ease-out;
-  }
-
-  [data-slot="select-select-content-list"] {
-    overflow-y: auto;
-    max-height: 12rem;
-    white-space: nowrap;
-    overflow-x: hidden;
-    display: flex;
-    flex-direction: column;
-
-    &:focus {
-      outline: none;
-    }
-
-    > *:not([role="presentation"]) + *:not([role="presentation"]) {
-      margin-top: 2px;
-    }
-  }
-
-  [data-slot="select-select-item"] {
-    position: relative;
-    display: flex;
-    align-items: center;
-    padding: 2px 8px;
-    gap: 12px;
-    border-radius: 4px;
-    cursor: default;
-
-    /* text-12-medium */
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large); /* 166.667% */
-    letter-spacing: var(--letter-spacing-normal);
-
-    color: var(--text-strong);
-
-    transition:
-      background-color 0.2s ease-in-out,
-      color 0.2s ease-in-out;
-    outline: none;
-    user-select: none;
-
-    &[data-highlighted] {
-      background: var(--surface-raised-base-hover);
-    }
-    &[data-disabled] {
-      background-color: var(--surface-raised-base);
-      pointer-events: none;
-    }
-    [data-slot="select-select-item-indicator"] {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      margin-left: auto;
-      width: 16px;
-      height: 16px;
-    }
-    &:focus {
-      outline: none;
-    }
-    &:hover {
-      background: var(--surface-raised-base-hover);
-    }
-  }
+	min-width: 8rem;
+	max-width: 23rem;
+	overflow: hidden;
+	border-radius: var(--radius-md);
+	background-color: var(--surface-raised-stronger-non-alpha);
+	padding: 4px;
+	box-shadow: var(--shadow-xs-border);
+	z-index: 50;
+	transform-origin: var(--kb-popper-content-transform-origin);
+	pointer-events: none;
+
+	animation: selectContentHide var(--transition-duration)
+		var(--transition-easing) forwards;
+
+	@starting-style {
+		animation: none;
+	}
+
+	&[data-expanded] {
+		pointer-events: auto;
+		animation: selectContentShow var(--transition-duration)
+			var(--transition-easing) forwards;
+	}
+
+	[data-slot="select-select-content-list"] {
+		overflow-y: auto;
+		max-height: 12rem;
+		white-space: nowrap;
+		overflow-x: hidden;
+		display: flex;
+		flex-direction: column;
+		&:focus {
+			outline: none;
+		}
+		> *:not([role="presentation"]) + *:not([role="presentation"]) {
+			margin-top: 2px;
+		}
+	}
+	[data-slot="select-select-item"] {
+		position: relative;
+		display: flex;
+		align-items: center;
+		padding: 4px 8px;
+		gap: 12px;
+		border-radius: var(--radius-sm);
+
+		/* text-12-medium */
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-base);
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large); /* 166.667% */
+		letter-spacing: var(--letter-spacing-normal);
+		color: var(--text-strong);
+
+		transition-property: background-color, color;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+		outline: none;
+		user-select: none;
+
+		&:hover {
+			background-color: var(--surface-raised-base-hover);
+		}
+		&[data-disabled] {
+			background-color: var(--surface-raised-base);
+			pointer-events: none;
+		}
+		[data-slot="select-select-item-indicator"] {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			margin-left: auto;
+			width: 16px;
+			height: 16px;
+			color: var(--icon-strong-base);
+
+			svg {
+				color: var(--icon-strong-base);
+			}
+		}
+		&:focus {
+			outline: none;
+		}
+		&:hover {
+			background: var(--surface-raised-base-hover);
+		}
+	}
 }
 
-[data-component="select-content"][data-trigger-style="settings"] {
-  min-width: 160px;
-  border-radius: 8px;
-  padding: 0;
-
-  [data-slot="select-select-content-list"] {
-    padding: 4px;
-  }
-
-  [data-slot="select-select-item"] {
-    /* text-14-regular */
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-base);
-    font-style: normal;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-  }
+@keyframes selectContentShow {
+	from {
+		opacity: 0;
+		transform: scaleY(0.95);
+	}
+	to {
+		opacity: 1;
+		transform: scaleY(1);
+	}
 }
 
-@keyframes select-open {
-  from {
-    opacity: 0;
-    transform: scale(0.95);
-  }
-  to {
-    opacity: 1;
-    transform: scale(1);
-  }
+@keyframes selectContentHide {
+	from {
+		opacity: 1;
+		transform: scaleY(1);
+	}
+	to {
+		opacity: 0;
+		transform: scaleY(0.95);
+	}
 }

+ 14 - 4
packages/ui/src/components/select.tsx

@@ -1,8 +1,10 @@
 import { Select as Kobalte } from "@kobalte/core/select"
-import { createMemo, onCleanup, splitProps, type ComponentProps, type JSX } from "solid-js"
+import { createMemo, createSignal, onCleanup, splitProps, type ComponentProps, type JSX } from "solid-js"
 import { pipe, groupBy, entries, map } from "remeda"
+import { Show } from "solid-js"
 import { Button, ButtonProps } from "./button"
 import { Icon } from "./icon"
+import { MorphChevron } from "./morph-chevron"
 
 export type SelectProps<T> = Omit<ComponentProps<typeof Kobalte<T>>, "value" | "onSelect" | "children"> & {
   placeholder?: string
@@ -38,6 +40,8 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
     "triggerVariant",
   ])
 
+  const [isOpen, setIsOpen] = createSignal(false)
+
   const state = {
     key: undefined as string | undefined,
     cleanup: undefined as (() => void) | void,
@@ -85,7 +89,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
       data-component="select"
       data-trigger-style={local.triggerVariant}
       placement={local.triggerVariant === "settings" ? "bottom-end" : "bottom-start"}
-      gutter={4}
+      gutter={8}
       value={local.current}
       options={grouped()}
       optionValue={(x) => (local.value ? local.value(x) : (x as string))}
@@ -115,7 +119,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
                 : (itemProps.item.rawValue as string)}
           </Kobalte.ItemLabel>
           <Kobalte.ItemIndicator data-slot="select-select-item-indicator">
-            <Icon name="check-small" size="small" />
+            <Icon name="check" size="small" />
           </Kobalte.ItemIndicator>
         </Kobalte.Item>
       )}
@@ -124,6 +128,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
         stop()
       }}
       onOpenChange={(open) => {
+        setIsOpen(open)
         local.onOpenChange?.(open)
         if (!open) stop()
       }}
@@ -149,7 +154,12 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
           }}
         </Kobalte.Value>
         <Kobalte.Icon data-slot="select-select-trigger-icon">
-          <Icon name={local.triggerVariant === "settings" ? "selector" : "chevron-down"} size="small" />
+          <Show when={local.triggerVariant === "settings"}>
+            <Icon name="selector" size="small" />
+          </Show>
+          <Show when={local.triggerVariant !== "settings"}>
+            <MorphChevron expanded={isOpen()} />
+          </Show>
         </Kobalte.Icon>
       </Kobalte.Trigger>
       <Kobalte.Portal>

+ 214 - 215
packages/ui/src/components/session-review.css

@@ -1,217 +1,216 @@
 [data-component="session-review"] {
-  display: flex;
-  flex-direction: column;
-  gap: 8px;
-  height: 100%;
-  overflow-y: auto;
-  scrollbar-width: none;
-  contain: strict;
-  &::-webkit-scrollbar {
-    display: none;
-  }
-
-  /* [data-slot="session-review-container"] { */
-  /*   height: 100%; */
-  /* } */
-
-  [data-slot="session-review-header"] {
-    position: sticky;
-    top: 0;
-    z-index: 20;
-    background-color: var(--background-stronger);
-    height: 32px;
-    flex-shrink: 0;
-    display: flex;
-    justify-content: space-between;
-    align-items: center;
-    align-self: stretch;
-  }
-
-  [data-slot="session-review-title"] {
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-large);
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large);
-    color: var(--text-strong);
-  }
-
-  [data-slot="session-review-actions"] {
-    display: flex;
-    align-items: center;
-    column-gap: 16px;
-    padding-right: 1px;
-  }
-
-  [data-component="sticky-accordion-header"] {
-    top: 40px;
-
-    &[data-expanded]::before {
-      top: -40px;
-    }
-  }
-
-  [data-slot="accordion-trigger"] {
-    background-color: var(--background-stronger) !important;
-  }
-
-  [data-slot="session-review-accordion-item"][data-selected] {
-    [data-slot="session-review-accordion-content"] {
-      box-shadow: var(--shadow-xs-border-select);
-      border-radius: var(--radius-lg);
-    }
-  }
-
-  [data-slot="accordion-item"] {
-    [data-slot="accordion-content"] {
-      display: none;
-    }
-    &[data-expanded] {
-      [data-slot="accordion-content"] {
-        display: block;
-      }
-    }
-  }
-
-  [data-slot="accordion-content"] {
-    -webkit-user-select: text;
-    user-select: text;
-  }
-
-  [data-slot="session-review-accordion-content"] {
-    position: relative;
-    overflow: hidden;
-  }
-
-  [data-slot="session-review-trigger-content"] {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    width: 100%;
-    gap: 20px;
-  }
-
-  [data-slot="session-review-file-info"] {
-    flex-grow: 1;
-    display: flex;
-    align-items: center;
-    gap: 20px;
-    min-width: 0;
-  }
-
-  [data-slot="session-review-file-name-container"] {
-    display: flex;
-    flex-grow: 1;
-    min-width: 0;
-  }
-
-  [data-slot="session-review-directory"] {
-    color: var(--text-base);
-    text-overflow: ellipsis;
-    overflow: hidden;
-    white-space: nowrap;
-    direction: rtl;
-    text-align: left;
-  }
-
-  [data-slot="session-review-filename"] {
-    color: var(--text-strong);
-    flex-shrink: 0;
-  }
-
-  [data-slot="session-review-view-button"] {
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    padding: 2px;
-    margin-left: 8px;
-    border: none;
-    background: transparent;
-    color: var(--text-base);
-    cursor: pointer;
-    border-radius: 4px;
-    opacity: 0;
-    transition: opacity 0.15s ease;
-
-    &:hover {
-      color: var(--text-strong);
-      background: var(--surface-base);
-    }
-  }
-
-  [data-slot="accordion-trigger"]:hover [data-slot="session-review-view-button"] {
-    opacity: 1;
-  }
-
-  [data-slot="session-review-trigger-actions"] {
-    flex-shrink: 0;
-    display: flex;
-    gap: 16px;
-    align-items: center;
-    justify-content: flex-end;
-  }
-
-  [data-slot="session-review-change"] {
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-weight: var(--font-weight-medium);
-  }
-
-  [data-slot="session-review-change"][data-type="added"] {
-    color: var(--icon-diff-add-base);
-  }
-
-  [data-slot="session-review-change"][data-type="removed"] {
-    color: var(--icon-diff-delete-base);
-  }
-
-  [data-slot="session-review-file-container"] {
-    padding: 0;
-  }
-
-  [data-slot="session-review-image-container"] {
-    padding: 12px;
-    display: flex;
-    justify-content: center;
-    background: var(--background-stronger);
-  }
-
-  [data-slot="session-review-image"] {
-    max-width: 100%;
-    max-height: 60vh;
-    object-fit: contain;
-    border-radius: 8px;
-    border: 1px solid var(--border-weak-base);
-    background: var(--background-base);
-  }
-
-  [data-slot="session-review-image-placeholder"] {
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    color: var(--text-weak);
-  }
-
-  [data-slot="session-review-audio-container"] {
-    padding: 12px;
-    display: flex;
-    justify-content: center;
-    background: var(--background-stronger);
-  }
-
-  [data-slot="session-review-audio"] {
-    width: 100%;
-    max-width: 560px;
-  }
-
-  [data-slot="session-review-audio-placeholder"] {
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    color: var(--text-weak);
-  }
-
-  [data-slot="session-review-diff-wrapper"] {
-    position: relative;
-    overflow: hidden;
-    --line-comment-z: 5;
-    --line-comment-popover-z: 30;
-  }
+	display: flex;
+	flex-direction: column;
+	gap: 8px;
+	height: 100%;
+	overflow-y: auto;
+	scrollbar-width: none;
+	contain: strict;
+	&::-webkit-scrollbar {
+		display: none;
+	}
+
+	/* [data-slot="session-review-container"] { */
+	/*   height: 100%; */
+	/* } */
+
+	[data-slot="session-review-header"] {
+		position: sticky;
+		top: 0;
+		z-index: 20;
+		background-color: var(--background-stronger);
+		height: 32px;
+		flex-shrink: 0;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		align-self: stretch;
+	}
+
+	[data-slot="session-review-title"] {
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-large);
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large);
+		color: var(--text-strong);
+	}
+
+	[data-slot="session-review-actions"] {
+		display: flex;
+		align-items: center;
+		column-gap: 16px;
+		padding-right: 1px;
+	}
+
+	[data-component="sticky-accordion-header"] {
+		top: 40px;
+
+		&[data-expanded]::before {
+			top: -40px;
+		}
+	}
+
+	[data-slot="accordion-trigger"] {
+		background-color: var(--background-stronger) !important;
+	}
+
+	[data-slot="session-review-accordion-item"][data-selected] {
+		[data-slot="session-review-accordion-content"] {
+			box-shadow: var(--shadow-xs-border-select);
+			border-radius: var(--radius-lg);
+		}
+	}
+
+	[data-slot="accordion-item"] {
+		[data-slot="accordion-content"] {
+			/* Use grid-template-rows for smooth height transition */
+			display: grid;
+		}
+	}
+
+	[data-slot="accordion-content"] {
+		-webkit-user-select: text;
+		user-select: text;
+	}
+
+	[data-slot="session-review-accordion-content"] {
+		position: relative;
+		overflow: hidden;
+	}
+
+	[data-slot="session-review-trigger-content"] {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		width: 100%;
+		gap: 20px;
+	}
+
+	[data-slot="session-review-file-info"] {
+		flex-grow: 1;
+		display: flex;
+		align-items: center;
+		gap: 20px;
+		min-width: 0;
+	}
+
+	[data-slot="session-review-file-name-container"] {
+		display: flex;
+		flex-grow: 1;
+		min-width: 0;
+	}
+
+	[data-slot="session-review-directory"] {
+		color: var(--text-base);
+		text-overflow: ellipsis;
+		overflow: hidden;
+		white-space: nowrap;
+		direction: rtl;
+		text-align: left;
+	}
+
+	[data-slot="session-review-filename"] {
+		color: var(--text-strong);
+		flex-shrink: 0;
+	}
+
+	[data-slot="session-review-view-button"] {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		padding: 2px;
+		margin-left: 8px;
+		border: none;
+		background: transparent;
+		color: var(--text-base);
+		cursor: pointer;
+		border-radius: 4px;
+		opacity: 0;
+		transition-property: opacity, background-color, color;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+
+		&:hover {
+			color: var(--text-strong);
+			background: var(--surface-base);
+		}
+	}
+
+	[data-slot="accordion-trigger"]:hover
+		[data-slot="session-review-view-button"] {
+		opacity: 1;
+	}
+
+	[data-slot="session-review-trigger-actions"] {
+		flex-shrink: 0;
+		display: flex;
+		gap: 16px;
+		align-items: center;
+		justify-content: flex-end;
+	}
+
+	[data-slot="session-review-change"] {
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-weight: var(--font-weight-medium);
+	}
+
+	[data-slot="session-review-change"][data-type="added"] {
+		color: var(--icon-diff-add-base);
+	}
+
+	[data-slot="session-review-change"][data-type="removed"] {
+		color: var(--icon-diff-delete-base);
+	}
+
+	[data-slot="session-review-file-container"] {
+		padding: 0;
+	}
+
+	[data-slot="session-review-image-container"] {
+		padding: 12px;
+		display: flex;
+		justify-content: center;
+		background: var(--background-stronger);
+	}
+
+	[data-slot="session-review-image"] {
+		max-width: 100%;
+		max-height: 60vh;
+		object-fit: contain;
+		border-radius: 8px;
+		border: 1px solid var(--border-weak-base);
+		background: var(--background-base);
+	}
+
+	[data-slot="session-review-image-placeholder"] {
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		color: var(--text-weak);
+	}
+
+	[data-slot="session-review-audio-container"] {
+		padding: 12px;
+		display: flex;
+		justify-content: center;
+		background: var(--background-stronger);
+	}
+
+	[data-slot="session-review-audio"] {
+		width: 100%;
+		max-width: 560px;
+	}
+
+	[data-slot="session-review-audio-placeholder"] {
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		color: var(--text-weak);
+	}
+
+	[data-slot="session-review-diff-wrapper"] {
+		position: relative;
+		overflow: hidden;
+		--line-comment-z: 5;
+		--line-comment-popover-z: 30;
+	}
 }

+ 3 - 2
packages/ui/src/components/session-review.tsx

@@ -290,8 +290,8 @@ export const SessionReview = (props: SessionReviewProps) => {
         <div data-slot="session-review-title">{i18n.t("ui.sessionReview.title")}</div>
         <div data-slot="session-review-actions">
           <Show when={props.onDiffStyleChange}>
-            <RadioGroup
-              options={["unified", "split"] as const}
+            <RadioGroup<SessionReviewDiffStyle>
+              options={["unified", "split"]}
               current={diffStyle()}
               value={(style) => style}
               label={(style) =>
@@ -501,6 +501,7 @@ export const SessionReview = (props: SessionReviewProps) => {
                   value={diff.file}
                   id={diffId(diff.file)}
                   data-file={diff.file}
+                  expanded={open().includes(diff.file)}
                   data-slot="session-review-accordion-item"
                   data-selected={props.focusedFile === diff.file ? "" : undefined}
                 >

+ 578 - 570
packages/ui/src/components/session-turn.css

@@ -1,572 +1,580 @@
 [data-component="session-turn"] {
-  --session-turn-sticky-height: 0px;
-  --sticky-header-height: calc(var(--session-title-height, 0px) + var(--session-turn-sticky-height, 0px) + 24px);
-  /* flex: 1; */
-  height: 100%;
-  min-height: 0;
-  min-width: 0;
-  display: flex;
-  align-items: flex-start;
-  justify-content: flex-start;
-
-  [data-slot="session-turn-content"] {
-    flex-grow: 1;
-    width: 100%;
-    height: 100%;
-    min-width: 0;
-    overflow-y: auto;
-    scrollbar-width: none;
-  }
-
-  [data-slot="session-turn-content"]::-webkit-scrollbar {
-    display: none;
-  }
-
-  [data-slot="session-turn-message-container"] {
-    display: flex;
-    flex-direction: column;
-    align-items: flex-start;
-    align-self: stretch;
-    min-width: 0;
-    gap: 18px;
-    overflow-anchor: none;
-
-    [data-slot="session-turn-badge"] {
-      display: inline-flex;
-      align-items: center;
-      padding: 2px 6px;
-      border-radius: 4px;
-      font-family: var(--font-family-mono);
-      font-size: var(--font-size-x-small);
-      font-weight: var(--font-weight-medium);
-      line-height: var(--line-height-normal);
-      white-space: nowrap;
-      color: var(--text-base);
-      background: var(--surface-raised-base);
-    }
-  }
-
-  [data-slot="session-turn-attachments"] {
-    width: 100%;
-    min-width: 0;
-    align-self: stretch;
-  }
-
-  [data-slot="session-turn-sticky"] {
-    width: calc(100% + 9px);
-    position: sticky;
-    top: var(--session-title-height, 0px);
-    z-index: 20;
-    background-color: var(--background-stronger);
-    margin-left: -9px;
-    padding-left: 9px;
-    /* padding-bottom: 12px; */
-    display: flex;
-    flex-direction: column;
-    gap: 12px;
-
-    &::before {
-      content: "";
-      position: absolute;
-      top: 0;
-      bottom: 0;
-      left: 0;
-      right: 0;
-      background-color: var(--background-stronger);
-      z-index: -1;
-    }
-
-    &::after {
-      content: "";
-      position: absolute;
-      top: 100%;
-      left: 0;
-      right: 0;
-      height: 32px;
-      background: linear-gradient(to bottom, var(--background-stronger), transparent);
-      pointer-events: none;
-    }
-  }
-
-  [data-slot="session-turn-message-header"] {
-    display: flex;
-    align-items: center;
-    align-self: stretch;
-    height: 32px;
-  }
-
-  [data-slot="session-turn-message-content"] {
-    margin-top: 0;
-    max-width: 100%;
-  }
-
-  [data-component="user-message"] [data-slot="user-message-text"] {
-    max-height: var(--user-message-collapsed-height, 64px);
-  }
-
-  [data-component="user-message"][data-expanded="true"] [data-slot="user-message-text"] {
-    max-height: none;
-  }
-
-  [data-component="user-message"][data-can-expand="true"] [data-slot="user-message-text"] {
-    padding-right: 36px;
-    padding-bottom: 28px;
-  }
-
-  [data-component="user-message"][data-can-expand="true"]:not([data-expanded="true"])
-    [data-slot="user-message-text"]::after {
-    content: "";
-    position: absolute;
-    left: 0;
-    right: 0;
-    height: 8px;
-    bottom: 0px;
-    background:
-      linear-gradient(to bottom, transparent, var(--surface-weak)),
-      linear-gradient(to bottom, transparent, var(--surface-weak));
-    pointer-events: none;
-  }
-
-  [data-component="user-message"] [data-slot="user-message-text"] [data-slot="user-message-expand"] {
-    display: none;
-    position: absolute;
-    bottom: 6px;
-    right: 6px;
-    padding: 0;
-  }
-
-  [data-component="user-message"][data-can-expand="true"]
-    [data-slot="user-message-text"]
-    [data-slot="user-message-expand"],
-  [data-component="user-message"][data-expanded="true"]
-    [data-slot="user-message-text"]
-    [data-slot="user-message-expand"] {
-    display: inline-flex;
-    align-items: center;
-    justify-content: center;
-    height: 22px;
-    width: 22px;
-    border: none;
-    border-radius: 6px;
-    background: transparent;
-    cursor: pointer;
-    color: var(--text-weak);
-
-    [data-slot="icon-svg"] {
-      transition: transform 0.15s ease;
-    }
-  }
-
-  [data-component="user-message"][data-expanded="true"]
-    [data-slot="user-message-text"]
-    [data-slot="user-message-expand"]
-    [data-slot="icon-svg"] {
-    transform: rotate(180deg);
-  }
-
-  [data-component="user-message"] [data-slot="user-message-text"] [data-slot="user-message-expand"]:hover {
-    background: var(--surface-raised-base);
-    color: var(--text-base);
-  }
-
-  [data-slot="session-turn-user-badges"] {
-    display: flex;
-    align-items: center;
-    gap: 6px;
-    padding-left: 16px;
-  }
-
-  [data-slot="session-turn-message-title"] {
-    width: 100%;
-    font-size: var(--font-size-large);
-    font-weight: 500;
-    color: var(--text-strong);
-    overflow: hidden;
-    text-overflow: ellipsis;
-    min-width: 0;
-    white-space: nowrap;
-  }
-
-  [data-slot="session-turn-message-title"] h1 {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    min-width: 0;
-    white-space: nowrap;
-    font-size: inherit;
-    font-weight: inherit;
-  }
-
-  [data-slot="session-turn-typewriter"] {
-    overflow: hidden;
-    text-overflow: ellipsis;
-    min-width: 0;
-    white-space: nowrap;
-  }
-
-  [data-slot="session-turn-summary-section"] {
-    width: 100%;
-    display: flex;
-    flex-direction: column;
-    gap: 24px;
-    align-items: flex-start;
-    align-self: stretch;
-  }
-
-  [data-slot="session-turn-summary-header"] {
-    display: flex;
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 4px;
-    align-self: stretch;
-
-    [data-slot="session-turn-response"] {
-      position: relative;
-      width: 100%;
-    }
-
-    [data-slot="session-turn-response-copy-wrapper"] {
-      position: absolute;
-      top: 8px;
-      right: 8px;
-      opacity: 0;
-      transition: opacity 0.15s ease;
-      z-index: 1;
-    }
-
-    [data-slot="session-turn-response"]:hover [data-slot="session-turn-response-copy-wrapper"] {
-      opacity: 1;
-    }
-
-    p {
-      font-size: var(--font-size-base);
-      line-height: var(--line-height-x-large);
-    }
-  }
-
-  [data-slot="session-turn-summary-title"] {
-    font-size: 13px;
-    /* text-12-medium */
-    font-weight: 500;
-    color: var(--text-weak);
-  }
-
-  [data-slot="session-turn-markdown"],
-  [data-slot="session-turn-accordion"] [data-slot="accordion-content"] {
-    -webkit-user-select: text;
-    user-select: text;
-  }
-
-  [data-slot="session-turn-markdown"] {
-    &[data-diffs="true"] {
-      font-size: 15px;
-    }
-
-    &[data-fade="true"] > * {
-      animation: fadeUp 0.4s ease-out forwards;
-      opacity: 0;
-
-      &:nth-child(1) {
-        animation-delay: 0.1s;
-      }
-
-      &:nth-child(2) {
-        animation-delay: 0.2s;
-      }
-
-      &:nth-child(3) {
-        animation-delay: 0.3s;
-      }
-
-      &:nth-child(4) {
-        animation-delay: 0.4s;
-      }
-
-      &:nth-child(5) {
-        animation-delay: 0.5s;
-      }
-
-      &:nth-child(6) {
-        animation-delay: 0.6s;
-      }
-
-      &:nth-child(7) {
-        animation-delay: 0.7s;
-      }
-
-      &:nth-child(8) {
-        animation-delay: 0.8s;
-      }
-
-      &:nth-child(9) {
-        animation-delay: 0.9s;
-      }
-
-      &:nth-child(10) {
-        animation-delay: 1s;
-      }
-
-      &:nth-child(11) {
-        animation-delay: 1.1s;
-      }
-
-      &:nth-child(12) {
-        animation-delay: 1.2s;
-      }
-
-      &:nth-child(13) {
-        animation-delay: 1.3s;
-      }
-
-      &:nth-child(14) {
-        animation-delay: 1.4s;
-      }
-
-      &:nth-child(15) {
-        animation-delay: 1.5s;
-      }
-
-      &:nth-child(16) {
-        animation-delay: 1.6s;
-      }
-
-      &:nth-child(17) {
-        animation-delay: 1.7s;
-      }
-
-      &:nth-child(18) {
-        animation-delay: 1.8s;
-      }
-
-      &:nth-child(19) {
-        animation-delay: 1.9s;
-      }
-
-      &:nth-child(20) {
-        animation-delay: 2s;
-      }
-
-      &:nth-child(21) {
-        animation-delay: 2.1s;
-      }
-
-      &:nth-child(22) {
-        animation-delay: 2.2s;
-      }
-
-      &:nth-child(23) {
-        animation-delay: 2.3s;
-      }
-
-      &:nth-child(24) {
-        animation-delay: 2.4s;
-      }
-
-      &:nth-child(25) {
-        animation-delay: 2.5s;
-      }
-
-      &:nth-child(26) {
-        animation-delay: 2.6s;
-      }
-
-      &:nth-child(27) {
-        animation-delay: 2.7s;
-      }
-
-      &:nth-child(28) {
-        animation-delay: 2.8s;
-      }
-
-      &:nth-child(29) {
-        animation-delay: 2.9s;
-      }
-
-      &:nth-child(30) {
-        animation-delay: 3s;
-      }
-    }
-  }
-
-  [data-slot="session-turn-summary-section"] {
-    position: relative;
-
-    [data-slot="session-turn-summary-copy"] {
-      position: absolute;
-      top: 0;
-      right: 0;
-      opacity: 0;
-      transition: opacity 0.15s ease;
-    }
-
-    &:hover [data-slot="session-turn-summary-copy"] {
-      opacity: 1;
-    }
-  }
-
-  [data-slot="session-turn-accordion"] {
-    width: 100%;
-  }
-
-  [data-component="sticky-accordion-header"] {
-    top: var(--sticky-header-height, 0px);
-
-    &[data-expanded]::before {
-      top: calc(-1 * var(--sticky-header-height, 0px));
-    }
-  }
-
-  [data-slot="session-turn-accordion-trigger-content"] {
-    display: flex;
-    align-items: center;
-    justify-content: space-between;
-    width: 100%;
-    gap: 20px;
-
-    [data-expandable="false"] {
-      pointer-events: none;
-    }
-  }
-
-  [data-slot="session-turn-file-info"] {
-    flex-grow: 1;
-    display: flex;
-    align-items: center;
-    gap: 20px;
-    min-width: 0;
-  }
-
-  [data-slot="session-turn-file-icon"] {
-    flex-shrink: 0;
-    width: 16px;
-    height: 16px;
-  }
-
-  [data-slot="session-turn-file-path"] {
-    display: flex;
-    flex-grow: 1;
-    min-width: 0;
-  }
-
-  [data-slot="session-turn-directory"] {
-    color: var(--text-base);
-    text-overflow: ellipsis;
-    overflow: hidden;
-    white-space: nowrap;
-    direction: rtl;
-    text-align: left;
-  }
-
-  [data-slot="session-turn-filename"] {
-    color: var(--text-strong);
-    flex-shrink: 0;
-  }
-
-  [data-slot="session-turn-accordion-actions"] {
-    flex-shrink: 0;
-    display: flex;
-    gap: 16px;
-    align-items: center;
-    justify-content: flex-end;
-  }
-
-  [data-slot="session-turn-accordion-content"] {
-    max-height: 240px;
-    /* max-h-60 */
-    overflow-y: auto;
-    scrollbar-width: none;
-  }
-
-  [data-slot="session-turn-accordion-content"]::-webkit-scrollbar {
-    display: none;
-  }
-
-  [data-slot="session-turn-response-section"] {
-    width: calc(100% + 9px);
-    min-width: 0;
-    margin-left: -9px;
-    padding-left: 9px;
-  }
-
-  [data-slot="session-turn-collapsible"] {
-    gap: 32px;
-    overflow: visible;
-  }
-
-  [data-slot="session-turn-collapsible-trigger-content"] {
-    max-width: 100%;
-    display: flex;
-    align-items: center;
-    gap: 8px;
-    color: var(--text-weak);
-
-    [data-slot="session-turn-trigger-icon"] {
-      color: var(--icon-base);
-    }
-
-    [data-component="spinner"] {
-      width: 12px;
-      height: 12px;
-      margin-right: 4px;
-    }
-
-    [data-component="icon"] {
-      width: 14px;
-      height: 14px;
-    }
-  }
-
-  [data-slot="session-turn-retry-message"] {
-    font-weight: 500;
-    color: var(--syntax-critical);
-  }
-
-  [data-slot="session-turn-retry-seconds"] {
-    color: var(--text-weak);
-  }
-
-  [data-slot="session-turn-retry-attempt"] {
-    color: var(--text-weak);
-  }
-
-  [data-slot="session-turn-status-text"] {
-    overflow: hidden;
-    text-overflow: ellipsis;
-  }
-
-  [data-slot="session-turn-details-text"] {
-    font-size: 13px;
-    /* text-12-medium */
-    font-weight: 500;
-  }
-
-  .error-card {
-    color: var(--text-on-critical-base);
-    max-height: 240px;
-    overflow-y: auto;
-  }
-
-  [data-slot="session-turn-collapsible-content-inner"] {
-    width: 100%;
-    min-width: 0;
-    display: flex;
-    flex-direction: column;
-    align-self: stretch;
-    gap: 12px;
-    margin-left: 12px;
-    padding-left: 12px;
-    padding-right: 12px;
-    border-left: 1px solid var(--border-base);
-
-    > :first-child > [data-component="markdown"]:first-child {
-      margin-top: 0;
-    }
-  }
-
-  [data-slot="session-turn-permission-parts"] {
-    width: 100%;
-    min-width: 0;
-    display: flex;
-    flex-direction: column;
-    gap: 12px;
-  }
+	--session-turn-sticky-height: 0px;
+	--sticky-header-height: calc(
+		var(--session-title-height, 0px) +
+		var(--session-turn-sticky-height, 0px) +
+		24px
+	);
+	/* flex: 1; */
+	height: 100%;
+	min-height: 0;
+	min-width: 0;
+	display: flex;
+	align-items: flex-start;
+	justify-content: flex-start;
+
+	[data-slot="session-turn-content"] {
+		flex-grow: 1;
+		width: 100%;
+		height: 100%;
+		min-width: 0;
+		overflow-y: auto;
+		scrollbar-width: none;
+	}
+
+	[data-slot="session-turn-content"]::-webkit-scrollbar {
+		display: none;
+	}
+
+	[data-slot="session-turn-message-container"] {
+		display: flex;
+		flex-direction: column;
+		align-items: flex-start;
+		align-self: stretch;
+		min-width: 0;
+		gap: 18px;
+		overflow-anchor: none;
+
+		[data-slot="session-turn-badge"] {
+			display: inline-flex;
+			align-items: center;
+			padding: 2px 6px;
+			border-radius: 4px;
+			font-family: var(--font-family-mono);
+			font-size: var(--font-size-x-small);
+			font-weight: var(--font-weight-medium);
+			line-height: var(--line-height-normal);
+			white-space: nowrap;
+			color: var(--text-base);
+			background: var(--surface-raised-base);
+		}
+	}
+
+	[data-slot="session-turn-attachments"] {
+		width: 100%;
+		min-width: 0;
+		align-self: stretch;
+	}
+
+	[data-slot="session-turn-sticky"] {
+		width: calc(100% + 9px);
+		position: sticky;
+		top: var(--session-title-height, 0px);
+		z-index: 20;
+		background-color: var(--background-stronger);
+		margin-left: -9px;
+		padding-left: 9px;
+		/* padding-bottom: 12px; */
+		display: flex;
+		flex-direction: column;
+		gap: 12px;
+
+		&::before {
+			content: "";
+			position: absolute;
+			top: 0;
+			bottom: 0;
+			left: 0;
+			right: 0;
+			background-color: var(--background-stronger);
+			z-index: -1;
+		}
+
+		&::after {
+			content: "";
+			position: absolute;
+			top: 100%;
+			left: 0;
+			right: 0;
+			height: 32px;
+			background: linear-gradient(
+				to bottom,
+				var(--background-stronger),
+				transparent
+			);
+			pointer-events: none;
+		}
+	}
+
+	[data-slot="session-turn-message-header"] {
+		display: flex;
+		align-items: center;
+		align-self: stretch;
+		height: 32px;
+	}
+
+	[data-slot="session-turn-message-content"] {
+		margin-top: 0;
+		max-width: 100%;
+	}
+
+	[data-component="user-message"] [data-slot="user-message-text"] {
+		max-height: var(--user-message-collapsed-height, 64px);
+		transition: max-height 200ms cubic-bezier(0.25, 0, 0.5, 1);
+	}
+
+	[data-component="user-message"][data-expanded="true"]
+		[data-slot="user-message-text"] {
+		max-height: 2000px;
+	}
+
+	[data-component="user-message"][data-can-expand="true"]
+		[data-slot="user-message-text"] {
+		padding-right: 36px;
+		padding-bottom: 28px;
+	}
+
+	[data-component="user-message"][data-can-expand="true"]:not(
+			[data-expanded="true"]
+		)
+		[data-slot="user-message-text"]::after {
+		content: "";
+		position: absolute;
+		left: 0;
+		right: 0;
+		height: 8px;
+		bottom: 0px;
+		background:
+			linear-gradient(to bottom, transparent, var(--surface-weak)),
+			linear-gradient(to bottom, transparent, var(--surface-weak));
+		pointer-events: none;
+	}
+
+	[data-component="user-message"]
+		[data-slot="user-message-text"]
+		[data-slot="user-message-expand"] {
+		display: none;
+		position: absolute;
+		bottom: 6px;
+		right: 6px;
+		padding: 0;
+	}
+
+	[data-component="user-message"][data-can-expand="true"]
+		[data-slot="user-message-text"]
+		[data-slot="user-message-expand"],
+	[data-component="user-message"][data-expanded="true"]
+		[data-slot="user-message-text"]
+		[data-slot="user-message-expand"] {
+		display: inline-flex;
+		align-items: center;
+		justify-content: center;
+		height: 22px;
+		width: 22px;
+		border: none;
+		border-radius: 6px;
+		background: transparent;
+		cursor: pointer;
+		color: var(--text-weak);
+	}
+
+	[data-component="user-message"]
+		[data-slot="user-message-text"]
+		[data-slot="user-message-expand"]:hover {
+		background: var(--surface-raised-base);
+		color: var(--text-base);
+	}
+
+	[data-slot="session-turn-user-badges"] {
+		display: flex;
+		align-items: center;
+		gap: 6px;
+		padding-left: 16px;
+	}
+
+	[data-slot="session-turn-message-title"] {
+		width: 100%;
+		font-size: var(--font-size-large);
+		font-weight: 500;
+		color: var(--text-strong);
+		overflow: hidden;
+		text-overflow: ellipsis;
+		min-width: 0;
+		white-space: nowrap;
+	}
+
+	[data-slot="session-turn-message-title"] h1 {
+		overflow: hidden;
+		text-overflow: ellipsis;
+		min-width: 0;
+		white-space: nowrap;
+		font-size: inherit;
+		font-weight: inherit;
+	}
+
+	[data-slot="session-turn-typewriter"] {
+		overflow: hidden;
+		text-overflow: ellipsis;
+		min-width: 0;
+		white-space: nowrap;
+	}
+
+	[data-slot="session-turn-summary-section"] {
+		width: 100%;
+		display: flex;
+		flex-direction: column;
+		gap: 24px;
+		align-items: flex-start;
+		align-self: stretch;
+	}
+
+	[data-slot="session-turn-summary-header"] {
+		display: flex;
+		flex-direction: column;
+		align-items: flex-start;
+		gap: 4px;
+		align-self: stretch;
+
+		[data-slot="session-turn-response"] {
+			position: relative;
+			width: 100%;
+		}
+
+		[data-slot="session-turn-response-copy-wrapper"] {
+			position: absolute;
+			top: 8px;
+			right: 8px;
+			opacity: 0;
+			transition: opacity 0.15s ease;
+			z-index: 1;
+		}
+
+		[data-slot="session-turn-response"]:hover
+			[data-slot="session-turn-response-copy-wrapper"] {
+			opacity: 1;
+		}
+
+		p {
+			font-size: var(--font-size-base);
+			line-height: var(--line-height-x-large);
+		}
+	}
+
+	[data-slot="session-turn-summary-title"] {
+		font-size: 13px;
+		/* text-12-medium */
+		font-weight: 500;
+		color: var(--text-weak);
+	}
+
+	[data-slot="session-turn-markdown"],
+	[data-slot="session-turn-accordion"] [data-slot="accordion-content"] {
+		-webkit-user-select: text;
+		user-select: text;
+	}
+
+	[data-slot="session-turn-markdown"] {
+		&[data-diffs="true"] {
+			font-size: 15px;
+		}
+
+		&[data-fade="true"] > * {
+			animation: fadeUp 0.4s ease-out forwards;
+			opacity: 0;
+
+			&:nth-child(1) {
+				animation-delay: 0.1s;
+			}
+
+			&:nth-child(2) {
+				animation-delay: 0.2s;
+			}
+
+			&:nth-child(3) {
+				animation-delay: 0.3s;
+			}
+
+			&:nth-child(4) {
+				animation-delay: 0.4s;
+			}
+
+			&:nth-child(5) {
+				animation-delay: 0.5s;
+			}
+
+			&:nth-child(6) {
+				animation-delay: 0.6s;
+			}
+
+			&:nth-child(7) {
+				animation-delay: 0.7s;
+			}
+
+			&:nth-child(8) {
+				animation-delay: 0.8s;
+			}
+
+			&:nth-child(9) {
+				animation-delay: 0.9s;
+			}
+
+			&:nth-child(10) {
+				animation-delay: 1s;
+			}
+
+			&:nth-child(11) {
+				animation-delay: 1.1s;
+			}
+
+			&:nth-child(12) {
+				animation-delay: 1.2s;
+			}
+
+			&:nth-child(13) {
+				animation-delay: 1.3s;
+			}
+
+			&:nth-child(14) {
+				animation-delay: 1.4s;
+			}
+
+			&:nth-child(15) {
+				animation-delay: 1.5s;
+			}
+
+			&:nth-child(16) {
+				animation-delay: 1.6s;
+			}
+
+			&:nth-child(17) {
+				animation-delay: 1.7s;
+			}
+
+			&:nth-child(18) {
+				animation-delay: 1.8s;
+			}
+
+			&:nth-child(19) {
+				animation-delay: 1.9s;
+			}
+
+			&:nth-child(20) {
+				animation-delay: 2s;
+			}
+
+			&:nth-child(21) {
+				animation-delay: 2.1s;
+			}
+
+			&:nth-child(22) {
+				animation-delay: 2.2s;
+			}
+
+			&:nth-child(23) {
+				animation-delay: 2.3s;
+			}
+
+			&:nth-child(24) {
+				animation-delay: 2.4s;
+			}
+
+			&:nth-child(25) {
+				animation-delay: 2.5s;
+			}
+
+			&:nth-child(26) {
+				animation-delay: 2.6s;
+			}
+
+			&:nth-child(27) {
+				animation-delay: 2.7s;
+			}
+
+			&:nth-child(28) {
+				animation-delay: 2.8s;
+			}
+
+			&:nth-child(29) {
+				animation-delay: 2.9s;
+			}
+
+			&:nth-child(30) {
+				animation-delay: 3s;
+			}
+		}
+	}
+
+	[data-slot="session-turn-summary-section"] {
+		position: relative;
+
+		[data-slot="session-turn-summary-copy"] {
+			position: absolute;
+			top: 0;
+			right: 0;
+			opacity: 0;
+			transition: opacity 0.15s ease;
+		}
+
+		&:hover [data-slot="session-turn-summary-copy"] {
+			opacity: 1;
+		}
+	}
+
+	[data-slot="session-turn-accordion"] {
+		width: 100%;
+	}
+
+	[data-component="sticky-accordion-header"] {
+		top: var(--sticky-header-height, 0px);
+
+		&[data-expanded]::before {
+			top: calc(-1 * var(--sticky-header-height, 0px));
+		}
+	}
+
+	[data-slot="session-turn-accordion-trigger-content"] {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		width: 100%;
+		gap: 20px;
+
+		[data-expandable="false"] {
+			pointer-events: none;
+		}
+	}
+
+	[data-slot="session-turn-file-info"] {
+		flex-grow: 1;
+		display: flex;
+		align-items: center;
+		gap: 20px;
+		min-width: 0;
+	}
+
+	[data-slot="session-turn-file-icon"] {
+		flex-shrink: 0;
+		width: 16px;
+		height: 16px;
+	}
+
+	[data-slot="session-turn-file-path"] {
+		display: flex;
+		flex-grow: 1;
+		min-width: 0;
+	}
+
+	[data-slot="session-turn-directory"] {
+		color: var(--text-base);
+		text-overflow: ellipsis;
+		overflow: hidden;
+		white-space: nowrap;
+		direction: rtl;
+		text-align: left;
+	}
+
+	[data-slot="session-turn-filename"] {
+		color: var(--text-strong);
+		flex-shrink: 0;
+	}
+
+	[data-slot="session-turn-accordion-actions"] {
+		flex-shrink: 0;
+		display: flex;
+		gap: 16px;
+		align-items: center;
+		justify-content: flex-end;
+		color: var(--icon-base);
+	}
+
+	[data-slot="session-turn-accordion-content"] {
+		max-height: 240px;
+		/* max-h-60 */
+		overflow-y: auto;
+		scrollbar-width: none;
+	}
+
+	[data-slot="session-turn-accordion-content"]::-webkit-scrollbar {
+		display: none;
+	}
+
+	[data-slot="session-turn-response-section"] {
+		width: calc(100% + 9px);
+		min-width: 0;
+		margin-left: -9px;
+		padding-left: 9px;
+	}
+
+	[data-slot="session-turn-collapsible"] {
+		gap: 32px;
+		overflow: visible;
+	}
+
+	[data-slot="session-turn-collapsible-trigger-content"] {
+		max-width: 100%;
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		color: var(--text-weak);
+
+		[data-slot="session-turn-trigger-icon"] {
+			color: var(--icon-base);
+		}
+
+		[data-component="spinner"] {
+			width: 12px;
+			height: 12px;
+			margin-right: 4px;
+		}
+
+		[data-component="icon"] {
+			width: 14px;
+			height: 14px;
+		}
+	}
+
+	[data-slot="session-turn-retry-message"] {
+		font-weight: 500;
+		color: var(--syntax-critical);
+	}
+
+	[data-slot="session-turn-retry-seconds"] {
+		color: var(--text-weak);
+	}
+
+	[data-slot="session-turn-retry-attempt"] {
+		color: var(--text-weak);
+	}
+
+	[data-slot="session-turn-status-text"] {
+		overflow: hidden;
+		text-overflow: ellipsis;
+	}
+
+	[data-slot="session-turn-details-text"] {
+		font-size: 13px;
+		/* text-12-medium */
+		font-weight: 500;
+	}
+
+	.error-card {
+		color: var(--text-on-critical-base);
+		max-height: 240px;
+		overflow-y: auto;
+	}
+
+	[data-slot="session-turn-collapsible-content-inner"] {
+		width: 100%;
+		min-width: 0;
+		display: flex;
+		flex-direction: column;
+		align-self: stretch;
+		gap: 12px;
+		margin-left: 12px;
+		padding-left: 12px;
+		padding-right: 12px;
+		border-left: 1px solid var(--border-base);
+
+		> :first-child > [data-component="markdown"]:first-child {
+			margin-top: 0;
+		}
+	}
+
+	[data-slot="session-turn-permission-parts"] {
+		width: 100%;
+		min-width: 0;
+		display: flex;
+		flex-direction: column;
+		gap: 12px;
+	}
 }

+ 1 - 1
packages/ui/src/components/session-turn.tsx

@@ -553,7 +553,7 @@ export function SessionTurn(
                             data-slot="session-turn-collapsible-trigger-content"
                             variant="ghost"
                             size="small"
-                            onClick={props.onStepsExpandedToggle ?? (() => {})}
+                            onClick={props.onStepsExpandedToggle ?? (() => { })}
                             aria-expanded={props.stepsExpanded}
                           >
                             <Switch>

+ 4 - 4
packages/ui/src/components/spinner.css

@@ -1,6 +1,6 @@
 [data-component="spinner"] {
-  color: inherit;
-  flex-shrink: 0;
-  width: 18px;
-  aspect-ratio: 1;
+	color: inherit;
+	flex-shrink: 0;
+	width: 18px;
+	aspect-ratio: 1;
 }

+ 12 - 12
packages/ui/src/components/sticky-accordion-header.css

@@ -1,16 +1,16 @@
 [data-component="sticky-accordion-header"] {
-  position: sticky;
-  top: 0px;
+	position: sticky;
+	top: 0px;
 
-  &[data-expanded] {
-    z-index: 10;
+	&[data-expanded] {
+		z-index: 10;
 
-    &::before {
-      content: "";
-      z-index: -10;
-      position: absolute;
-      inset: 0;
-      background-color: var(--background-stronger);
-    }
-  }
+		&::before {
+			content: "";
+			z-index: -10;
+			position: absolute;
+			inset: 0;
+			background-color: var(--background-stronger);
+		}
+	}
 }

+ 131 - 130
packages/ui/src/components/switch.css

@@ -1,132 +1,133 @@
 [data-component="switch"] {
-  position: relative;
-  display: flex;
-  align-items: center;
-  gap: 8px;
-  cursor: default;
-
-  [data-slot="switch-input"] {
-    position: absolute;
-    width: 1px;
-    height: 1px;
-    padding: 0;
-    margin: -1px;
-    overflow: hidden;
-    clip: rect(0, 0, 0, 0);
-    white-space: nowrap;
-    border-width: 0;
-  }
-
-  [data-slot="switch-control"] {
-    display: inline-flex;
-    align-items: center;
-    width: 28px;
-    height: 16px;
-    flex-shrink: 0;
-    border-radius: 3px;
-    border: 1px solid var(--border-weak-base);
-    background: var(--surface-base);
-    transition:
-      background-color 150ms,
-      border-color 150ms;
-  }
-
-  [data-slot="switch-thumb"] {
-    width: 14px;
-    height: 14px;
-    box-sizing: content-box;
-
-    border-radius: 2px;
-    border: 1px solid var(--border-base);
-    background: var(--icon-invert-base);
-
-    /* shadows/shadow-xs */
-    box-shadow:
-      0 1px 2px -1px rgba(19, 16, 16, 0.04),
-      0 1px 2px 0 rgba(19, 16, 16, 0.06),
-      0 1px 3px 0 rgba(19, 16, 16, 0.08);
-
-    transform: translateX(-1px);
-    transition:
-      transform 150ms,
-      background-color 150ms;
-  }
-
-  [data-slot="switch-label"] {
-    user-select: none;
-    color: var(--text-base);
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  [data-slot="switch-description"] {
-    color: var(--text-base);
-    font-family: var(--font-family-sans);
-    font-size: 12px;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-normal);
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  [data-slot="switch-error"] {
-    color: var(--text-error);
-    font-family: var(--font-family-sans);
-    font-size: 12px;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-normal);
-    letter-spacing: var(--letter-spacing-normal);
-  }
-
-  &:hover:not([data-disabled], [data-readonly]) [data-slot="switch-control"] {
-    border-color: var(--border-hover);
-    background-color: var(--surface-hover);
-  }
-
-  &:focus-within:not([data-readonly]) [data-slot="switch-control"] {
-    border-color: var(--border-focus);
-    box-shadow: 0 0 0 2px var(--surface-focus);
-  }
-
-  &[data-checked] [data-slot="switch-control"] {
-    box-sizing: border-box;
-    border-color: var(--icon-strong-base);
-    background-color: var(--icon-strong-base);
-  }
-
-  &[data-checked] [data-slot="switch-thumb"] {
-    border: none;
-    transform: translateX(12px);
-    background-color: var(--icon-invert-base);
-  }
-
-  &[data-checked]:hover:not([data-disabled], [data-readonly]) [data-slot="switch-control"] {
-    border-color: var(--border-hover);
-    background-color: var(--surface-hover);
-  }
-
-  &[data-disabled] {
-    cursor: not-allowed;
-  }
-
-  &[data-disabled] [data-slot="switch-control"] {
-    border-color: var(--border-disabled);
-    background-color: var(--surface-disabled);
-  }
-
-  &[data-disabled] [data-slot="switch-thumb"] {
-    background-color: var(--icon-disabled);
-  }
-
-  &[data-invalid] [data-slot="switch-control"] {
-    border-color: var(--border-error);
-  }
-
-  &[data-readonly] {
-    cursor: default;
-    pointer-events: none;
-  }
+	position: relative;
+	display: flex;
+	align-items: center;
+	gap: 8px;
+	cursor: default;
+
+	[data-slot="switch-input"] {
+		position: absolute;
+		width: 1px;
+		height: 1px;
+		padding: 0;
+		margin: -1px;
+		overflow: hidden;
+		clip: rect(0, 0, 0, 0);
+		white-space: nowrap;
+		border-width: 0;
+	}
+
+	[data-slot="switch-control"] {
+		display: inline-flex;
+		align-items: center;
+		width: 28px;
+		height: 16px;
+		flex-shrink: 0;
+		border-radius: 3px;
+		border: 1px solid var(--border-weak-base);
+		background: var(--surface-base);
+		transition-property: background-color, border-color, box-shadow;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+	}
+
+	[data-slot="switch-thumb"] {
+		width: 14px;
+		height: 14px;
+		box-sizing: content-box;
+
+		border-radius: 2px;
+		border: 1px solid var(--border-base);
+		background: var(--icon-invert-base);
+
+		/* shadows/shadow-xs */
+		box-shadow:
+			0 1px 2px -1px rgba(19, 16, 16, 0.04),
+			0 1px 2px 0 rgba(19, 16, 16, 0.06),
+			0 1px 3px 0 rgba(19, 16, 16, 0.08);
+
+		transform: translateX(-1px);
+		transition-property: transform, background-color, border-color;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+	}
+
+	[data-slot="switch-label"] {
+		user-select: none;
+		color: var(--text-base);
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	[data-slot="switch-description"] {
+		color: var(--text-base);
+		font-family: var(--font-family-sans);
+		font-size: 12px;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-normal);
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	[data-slot="switch-error"] {
+		color: var(--text-error);
+		font-family: var(--font-family-sans);
+		font-size: 12px;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-normal);
+		letter-spacing: var(--letter-spacing-normal);
+	}
+
+	&:hover:not([data-disabled], [data-readonly]) [data-slot="switch-control"] {
+		border-color: var(--border-hover);
+		background-color: var(--surface-hover);
+	}
+
+	&:focus-within:not([data-readonly]) [data-slot="switch-control"] {
+		border-color: var(--border-focus);
+		box-shadow: 0 0 0 2px var(--surface-focus);
+	}
+
+	&[data-checked] [data-slot="switch-control"] {
+		box-sizing: border-box;
+		border-color: var(--icon-strong-base);
+		background-color: var(--icon-strong-base);
+	}
+
+	&[data-checked] [data-slot="switch-thumb"] {
+		border: none;
+		transform: translateX(12px);
+		background-color: var(--icon-invert-base);
+	}
+
+	&[data-checked]:hover:not([data-disabled], [data-readonly])
+		[data-slot="switch-control"] {
+		border-color: var(--border-hover);
+		background-color: var(--surface-hover);
+	}
+
+	&[data-disabled] {
+		cursor: not-allowed;
+	}
+
+	&[data-disabled] [data-slot="switch-control"] {
+		border-color: var(--border-disabled);
+		background-color: var(--surface-disabled);
+	}
+
+	&[data-disabled] [data-slot="switch-thumb"] {
+		background-color: var(--icon-disabled);
+	}
+
+	&[data-invalid] [data-slot="switch-control"] {
+		border-color: var(--border-error);
+	}
+
+	&[data-readonly] {
+		cursor: default;
+		pointer-events: none;
+	}
 }

+ 452 - 449
packages/ui/src/components/tabs.css

@@ -1,451 +1,454 @@
 [data-component="tabs"] {
-  width: 100%;
-  height: 100%;
-  display: flex;
-  flex-direction: column;
-  background-color: var(--background-stronger);
-  overflow: clip;
-
-  [data-slot="tabs-list"] {
-    height: 48px;
-    width: 100%;
-    position: relative;
-    display: flex;
-    align-items: center;
-    overflow-x: auto;
-
-    /* Hide scrollbar */
-    scrollbar-width: none;
-    -ms-overflow-style: none;
-    &::-webkit-scrollbar {
-      display: none;
-    }
-
-    /* After element to fill remaining space */
-    &::after {
-      content: "";
-      display: block;
-      flex-grow: 1;
-      height: 100%;
-      border-bottom: 1px solid var(--border-weak-base);
-      background-color: var(--background-base);
-    }
-
-    &:empty::after {
-      display: none;
-    }
-  }
-
-  [data-slot="tabs-trigger-wrapper"] {
-    position: relative;
-    height: 100%;
-    display: flex;
-    align-items: center;
-    gap: 12px;
-    color: var(--text-base);
-
-    /* text-14-medium */
-    font-family: var(--font-family-sans);
-    font-size: 14px;
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large); /* 142.857% */
-    letter-spacing: var(--letter-spacing-normal);
-
-    white-space: nowrap;
-    flex-shrink: 0;
-    max-width: 280px;
-    border-bottom: 1px solid var(--border-weak-base);
-    border-right: 1px solid var(--border-weak-base);
-    background-color: var(--background-base);
-
-    [data-slot="tabs-trigger"] {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      padding: 14px 24px 14px 12px;
-      outline: none;
-      min-width: 0;
-      overflow: hidden;
-      text-overflow: ellipsis;
-
-      &:focus-visible {
-        outline: none;
-        box-shadow: none;
-      }
-    }
-
-    [data-slot="tabs-trigger-close-button"] {
-      display: flex;
-      align-items: center;
-      justify-content: center;
-    }
-
-    [data-component="icon-button"] {
-      margin: -0.25rem;
-    }
-
-    &:disabled {
-      pointer-events: none;
-      color: var(--text-weaker);
-    }
-    &:focus-visible {
-      outline: none;
-      box-shadow: none;
-    }
-    &:has([data-hidden]) {
-      [data-slot="tabs-trigger-close-button"] {
-        opacity: 0;
-      }
-
-      &:hover {
-        [data-slot="tabs-trigger-close-button"] {
-          opacity: 1;
-        }
-      }
-    }
-    &:has([data-selected]) {
-      color: var(--text-strong);
-      background-color: transparent;
-      border-bottom-color: transparent;
-      [data-slot="tabs-trigger-close-button"] {
-        opacity: 1;
-      }
-    }
-    &:hover:not(:disabled):not([data-selected]) {
-      color: var(--text-strong);
-    }
-    &:has([data-slot="tabs-trigger-close-button"]) {
-      padding-right: 12px;
-
-      [data-slot="tabs-trigger"] {
-        padding-right: 0;
-      }
-    }
-  }
-
-  [data-slot="tabs-content"] {
-    overflow-y: auto;
-    flex: 1;
-
-    /* Hide scrollbar */
-    scrollbar-width: none;
-    -ms-overflow-style: none;
-    &::-webkit-scrollbar {
-      display: none;
-    }
-
-    &:focus-visible {
-      outline: none;
-    }
-  }
-
-  &[data-variant="alt"] {
-    [data-slot="tabs-list"] {
-      padding-left: 24px;
-      padding-right: 24px;
-      gap: 12px;
-      border-bottom: 1px solid var(--border-weak-base);
-      background-color: transparent;
-
-      &::after {
-        border: none;
-        background-color: transparent;
-      }
-      &:empty::after {
-        display: none;
-      }
-    }
-
-    [data-slot="tabs-trigger-wrapper"] {
-      border: none;
-      color: var(--text-base);
-      background-color: transparent;
-      border-bottom-width: 2px;
-      border-bottom-style: solid;
-      border-bottom-color: transparent;
-      gap: 4px;
-
-      /* text-14-regular */
-      font-family: var(--font-family-sans);
-      font-size: var(--font-size-base);
-      font-style: normal;
-      font-weight: var(--font-weight-regular);
-      line-height: var(--line-height-x-large); /* 171.429% */
-      letter-spacing: var(--letter-spacing-normal);
-
-      [data-slot="tabs-trigger"] {
-        height: 100%;
-        padding: 4px;
-        background-color: transparent;
-        border-bottom-width: 2px;
-        border-bottom-color: transparent;
-      }
-
-      [data-slot="tabs-trigger-close-button"] {
-        display: flex;
-        align-items: center;
-        justify-content: center;
-      }
-
-      [data-component="icon-button"] {
-        width: 16px;
-        height: 16px;
-        margin: 0;
-      }
-
-      &:has([data-selected]) {
-        color: var(--text-strong);
-        background-color: transparent;
-        border-bottom-color: var(--icon-strong-base);
-      }
-
-      &:hover:not(:disabled):not([data-selected]) {
-        color: var(--text-strong);
-      }
-
-      &:has([data-slot="tabs-trigger-close-button"]) {
-        padding-right: 0;
-        [data-slot="tabs-trigger"] {
-          padding-right: 0;
-        }
-      }
-    }
-
-    /* [data-slot="tabs-content"] { */
-    /* } */
-  }
-
-  &[data-variant="pill"][data-orientation="horizontal"] {
-    background-color: transparent;
-
-    [data-slot="tabs-list"] {
-      height: auto;
-      padding: 6px 0;
-      gap: 4px;
-      background-color: var(--background-base);
-
-      &::after {
-        display: none;
-      }
-    }
-
-    [data-slot="tabs-trigger-wrapper"] {
-      height: 32px;
-      border: none;
-      border-radius: var(--radius-sm);
-      background-color: transparent;
-      gap: 0;
-
-      /* text-13-medium */
-      font-family: var(--font-family-sans);
-      font-size: var(--font-size-small);
-      font-style: normal;
-      font-weight: var(--font-weight-medium);
-      line-height: var(--line-height-large);
-      letter-spacing: var(--letter-spacing-normal);
-
-      [data-slot="tabs-trigger"] {
-        height: 100%;
-        width: 100%;
-        padding: 0 12px;
-        background-color: transparent;
-      }
-
-      &:hover:not(:disabled) {
-        background-color: var(--surface-raised-base-hover);
-        color: var(--text-strong);
-      }
-
-      &:has([data-selected]) {
-        background-color: var(--surface-raised-base-active);
-        color: var(--text-strong);
-
-        &:hover:not(:disabled) {
-          background-color: var(--surface-raised-base-active);
-        }
-      }
-    }
-  }
-
-  &[data-variant="pill"][data-orientation="horizontal"][data-scope="filetree"] {
-    [data-slot="tabs-list"] {
-      height: 48px;
-      padding-inline: 12px;
-      gap: 8px;
-      align-items: center;
-    }
-
-    [data-slot="tabs-trigger-wrapper"] {
-      height: 26px;
-      border-radius: 6px;
-      color: var(--text-weak);
-
-      &:not(:has([data-selected])):hover:not(:disabled) {
-        color: var(--text-base);
-      }
-
-      &:has([data-selected]) {
-        color: var(--text-strong);
-      }
-    }
-  }
-
-  &[data-orientation="vertical"] {
-    flex-direction: row;
-
-    [data-slot="tabs-list"] {
-      flex-direction: column;
-      width: auto;
-      height: 100%;
-      overflow-x: hidden;
-      overflow-y: auto;
-      padding: 8px;
-      gap: 4px;
-      background-color: var(--background-base);
-      border-right: 1px solid var(--border-weak-base);
-
-      &::after {
-        display: none;
-      }
-    }
-
-    [data-slot="tabs-trigger-wrapper"] {
-      width: 100%;
-      height: 32px;
-      border: none;
-      border-radius: 8px;
-      background-color: transparent;
-
-      [data-slot="tabs-trigger"] {
-        height: 100%;
-        padding: 0 8px;
-        gap: 8px;
-        justify-content: flex-start;
-      }
-
-      &:hover:not(:disabled) {
-        background-color: var(--surface-raised-base-hover);
-      }
-
-      &:has([data-selected]) {
-        background-color: var(--surface-raised-base-active);
-        color: var(--text-strong);
-      }
-    }
-
-    [data-slot="tabs-content"] {
-      overflow-x: auto;
-      overflow-y: auto;
-    }
-
-    &[data-variant="alt"] {
-      [data-slot="tabs-list"] {
-        padding: 8px;
-        gap: 4px;
-        border: none;
-
-        &::after {
-          display: none;
-        }
-      }
-
-      [data-slot="tabs-trigger-wrapper"] {
-        height: 32px;
-        border: none;
-        border-radius: 8px;
-
-        [data-slot="tabs-trigger"] {
-          border: none;
-          padding: 0 8px;
-          gap: 8px;
-          justify-content: flex-start;
-        }
-
-        &:hover:not(:disabled) {
-          background-color: var(--surface-raised-base-hover);
-        }
-
-        &:has([data-selected]) {
-          background-color: var(--surface-raised-base-hover);
-          color: var(--text-strong);
-        }
-      }
-    }
-
-    &[data-variant="settings"] {
-      [data-slot="tabs-list"] {
-        width: 150px;
-        min-width: 150px;
-
-        @media (min-width: 640px) {
-          width: 200px;
-          min-width: 200px;
-        }
-        padding: 12px;
-        gap: 0;
-        background-color: var(--background-base);
-        border-right: 1px solid var(--border-weak-base);
-
-        &::after {
-          display: none;
-        }
-      }
-
-      [data-slot="tabs-section-title"] {
-        width: 100%;
-        padding: 0 0 0 4px;
-        font-family: var(--font-family-sans);
-        font-size: var(--font-size-small);
-        font-weight: var(--font-weight-medium);
-        color: var(--text-weak);
-      }
-
-      [data-slot="tabs-trigger-wrapper"] {
-        height: 32px;
-        border: none;
-        border-radius: var(--radius-md);
-
-        /* text-14-medium */
-        font-family: var(--font-family-sans);
-        font-size: var(--font-size-base);
-        font-weight: var(--font-weight-medium);
-        line-height: var(--line-height-large);
-
-        [data-slot="tabs-trigger"] {
-          border: none;
-          padding: 0 8px;
-          gap: 12px;
-          justify-content: flex-start;
-          width: 100%;
-          height: 100%;
-        }
-
-        [data-component="icon"] {
-          color: var(--icon-base);
-        }
-
-        &:hover:not(:disabled) {
-          background-color: var(--surface-raised-base-hover);
-        }
-
-        &:has([data-selected]) {
-          background-color: var(--surface-raised-base-active);
-          color: var(--text-strong);
-
-          [data-component="icon"] {
-            color: var(--icon-strong-base);
-          }
-
-          &:hover:not(:disabled) {
-            background-color: var(--surface-raised-base-active);
-          }
-        }
-      }
-
-      [data-slot="tabs-content"] {
-        background-color: var(--surface-raised-stronger-non-alpha);
-      }
-    }
-  }
+	width: 100%;
+	height: 100%;
+	display: flex;
+	flex-direction: column;
+	background-color: var(--background-stronger);
+	overflow: clip;
+
+	[data-slot="tabs-list"] {
+		height: 48px;
+		width: 100%;
+		position: relative;
+		display: flex;
+		align-items: center;
+		overflow-x: auto;
+
+		/* Hide scrollbar */
+		scrollbar-width: none;
+		-ms-overflow-style: none;
+		&::-webkit-scrollbar {
+			display: none;
+		}
+
+		/* After element to fill remaining space */
+		&::after {
+			content: "";
+			display: block;
+			flex-grow: 1;
+			height: 100%;
+			border-bottom: 1px solid var(--border-weak-base);
+			background-color: var(--background-base);
+		}
+
+		&:empty::after {
+			display: none;
+		}
+	}
+
+	[data-slot="tabs-trigger-wrapper"] {
+		position: relative;
+		height: 100%;
+		display: flex;
+		align-items: center;
+		gap: 12px;
+		color: var(--text-base);
+
+		/* text-14-medium */
+		font-family: var(--font-family-sans);
+		font-size: 14px;
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large); /* 142.857% */
+		letter-spacing: var(--letter-spacing-normal);
+
+		white-space: nowrap;
+		flex-shrink: 0;
+		max-width: 280px;
+		border-bottom: 1px solid var(--border-weak-base);
+		border-right: 1px solid var(--border-weak-base);
+		background-color: var(--background-base);
+		transition-property: background-color, border-color, color;
+		transition-duration: var(--transition-duration);
+		transition-timing-function: var(--transition-easing);
+
+		[data-slot="tabs-trigger"] {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			padding: 14px 24px 14px 12px;
+			outline: none;
+			min-width: 0;
+			overflow: hidden;
+			text-overflow: ellipsis;
+
+			&:focus-visible {
+				outline: none;
+				box-shadow: none;
+			}
+		}
+
+		[data-slot="tabs-trigger-close-button"] {
+			display: flex;
+			align-items: center;
+			justify-content: center;
+		}
+
+		[data-component="icon-button"] {
+			margin: -0.25rem;
+		}
+
+		&:disabled {
+			pointer-events: none;
+			color: var(--text-weaker);
+		}
+		&:focus-visible {
+			outline: none;
+			box-shadow: none;
+		}
+		&:has([data-hidden]) {
+			[data-slot="tabs-trigger-close-button"] {
+				opacity: 0;
+			}
+
+			&:hover {
+				[data-slot="tabs-trigger-close-button"] {
+					opacity: 1;
+				}
+			}
+		}
+		&:has([data-selected]) {
+			color: var(--text-strong);
+			background-color: transparent;
+			border-bottom-color: transparent;
+			[data-slot="tabs-trigger-close-button"] {
+				opacity: 1;
+			}
+		}
+		&:hover:not(:disabled):not([data-selected]) {
+			color: var(--text-strong);
+		}
+		&:has([data-slot="tabs-trigger-close-button"]) {
+			padding-right: 12px;
+
+			[data-slot="tabs-trigger"] {
+				padding-right: 0;
+			}
+		}
+	}
+
+	[data-slot="tabs-content"] {
+		overflow-y: auto;
+		flex: 1;
+
+		/* Hide scrollbar */
+		scrollbar-width: none;
+		-ms-overflow-style: none;
+		&::-webkit-scrollbar {
+			display: none;
+		}
+
+		&:focus-visible {
+			outline: none;
+		}
+	}
+
+	&[data-variant="alt"] {
+		[data-slot="tabs-list"] {
+			padding-left: 24px;
+			padding-right: 24px;
+			gap: 12px;
+			border-bottom: 1px solid var(--border-weak-base);
+			background-color: transparent;
+
+			&::after {
+				border: none;
+				background-color: transparent;
+			}
+			&:empty::after {
+				display: none;
+			}
+		}
+
+		[data-slot="tabs-trigger-wrapper"] {
+			border: none;
+			color: var(--text-base);
+			background-color: transparent;
+			border-bottom-width: 2px;
+			border-bottom-style: solid;
+			border-bottom-color: transparent;
+			gap: 4px;
+
+			/* text-14-regular */
+			font-family: var(--font-family-sans);
+			font-size: var(--font-size-base);
+			font-style: normal;
+			font-weight: var(--font-weight-regular);
+			line-height: var(--line-height-x-large); /* 171.429% */
+			letter-spacing: var(--letter-spacing-normal);
+
+			[data-slot="tabs-trigger"] {
+				height: 100%;
+				padding: 4px;
+				background-color: transparent;
+				border-bottom-width: 2px;
+				border-bottom-color: transparent;
+			}
+
+			[data-slot="tabs-trigger-close-button"] {
+				display: flex;
+				align-items: center;
+				justify-content: center;
+			}
+
+			[data-component="icon-button"] {
+				width: 16px;
+				height: 16px;
+				margin: 0;
+			}
+
+			&:has([data-selected]) {
+				color: var(--text-strong);
+				background-color: transparent;
+				border-bottom-color: var(--icon-strong-base);
+			}
+
+			&:hover:not(:disabled):not([data-selected]) {
+				color: var(--text-strong);
+			}
+
+			&:has([data-slot="tabs-trigger-close-button"]) {
+				padding-right: 0;
+				[data-slot="tabs-trigger"] {
+					padding-right: 0;
+				}
+			}
+		}
+
+		/* [data-slot="tabs-content"] { */
+		/* } */
+	}
+
+	&[data-variant="pill"][data-orientation="horizontal"] {
+		background-color: transparent;
+
+		[data-slot="tabs-list"] {
+			height: auto;
+			padding: 6px 0;
+			gap: 4px;
+			background-color: var(--background-base);
+
+			&::after {
+				display: none;
+			}
+		}
+
+		[data-slot="tabs-trigger-wrapper"] {
+			height: 32px;
+			border: none;
+			border-radius: var(--radius-sm);
+			background-color: transparent;
+			gap: 0;
+
+			/* text-13-medium */
+			font-family: var(--font-family-sans);
+			font-size: var(--font-size-small);
+			font-style: normal;
+			font-weight: var(--font-weight-medium);
+			line-height: var(--line-height-large);
+			letter-spacing: var(--letter-spacing-normal);
+
+			[data-slot="tabs-trigger"] {
+				height: 100%;
+				width: 100%;
+				padding: 0 12px;
+				background-color: transparent;
+			}
+
+			&:hover:not(:disabled) {
+				background-color: var(--surface-raised-base-hover);
+				color: var(--text-strong);
+			}
+
+			&:has([data-selected]) {
+				background-color: var(--surface-raised-base-active);
+				color: var(--text-strong);
+
+				&:hover:not(:disabled) {
+					background-color: var(--surface-raised-base-active);
+				}
+			}
+		}
+	}
+
+	&[data-variant="pill"][data-orientation="horizontal"][data-scope="filetree"] {
+		[data-slot="tabs-list"] {
+			height: 48px;
+			padding-inline: 12px;
+			gap: 8px;
+			align-items: center;
+		}
+
+		[data-slot="tabs-trigger-wrapper"] {
+			height: 26px;
+			border-radius: 6px;
+			color: var(--text-weak);
+
+			&:not(:has([data-selected])):hover:not(:disabled) {
+				color: var(--text-base);
+			}
+
+			&:has([data-selected]) {
+				color: var(--text-strong);
+			}
+		}
+	}
+
+	&[data-orientation="vertical"] {
+		flex-direction: row;
+
+		[data-slot="tabs-list"] {
+			flex-direction: column;
+			width: auto;
+			height: 100%;
+			overflow-x: hidden;
+			overflow-y: auto;
+			padding: 8px;
+			gap: 4px;
+			background-color: var(--background-base);
+			border-right: 1px solid var(--border-weak-base);
+
+			&::after {
+				display: none;
+			}
+		}
+
+		[data-slot="tabs-trigger-wrapper"] {
+			width: 100%;
+			height: 32px;
+			border: none;
+			border-radius: 8px;
+			background-color: transparent;
+
+			[data-slot="tabs-trigger"] {
+				height: 100%;
+				padding: 0 8px;
+				gap: 8px;
+				justify-content: flex-start;
+			}
+
+			&:hover:not(:disabled) {
+				background-color: var(--surface-raised-base-hover);
+			}
+
+			&:has([data-selected]) {
+				background-color: var(--surface-raised-base-active);
+				color: var(--text-strong);
+			}
+		}
+
+		[data-slot="tabs-content"] {
+			overflow-x: auto;
+			overflow-y: auto;
+		}
+
+		&[data-variant="alt"] {
+			[data-slot="tabs-list"] {
+				padding: 8px;
+				gap: 4px;
+				border: none;
+
+				&::after {
+					display: none;
+				}
+			}
+
+			[data-slot="tabs-trigger-wrapper"] {
+				height: 32px;
+				border: none;
+				border-radius: 8px;
+
+				[data-slot="tabs-trigger"] {
+					border: none;
+					padding: 0 8px;
+					gap: 8px;
+					justify-content: flex-start;
+				}
+
+				&:hover:not(:disabled) {
+					background-color: var(--surface-raised-base-hover);
+				}
+
+				&:has([data-selected]) {
+					background-color: var(--surface-raised-base-hover);
+					color: var(--text-strong);
+				}
+			}
+		}
+
+		&[data-variant="settings"] {
+			[data-slot="tabs-list"] {
+				width: 150px;
+				min-width: 150px;
+
+				@media (min-width: 640px) {
+					width: 200px;
+					min-width: 200px;
+				}
+				padding: 12px;
+				gap: 0;
+				background-color: var(--background-base);
+				border-right: 1px solid var(--border-weak-base);
+
+				&::after {
+					display: none;
+				}
+			}
+
+			[data-slot="tabs-section-title"] {
+				width: 100%;
+				padding: 0 0 0 4px;
+				font-family: var(--font-family-sans);
+				font-size: var(--font-size-small);
+				font-weight: var(--font-weight-medium);
+				color: var(--text-weak);
+			}
+
+			[data-slot="tabs-trigger-wrapper"] {
+				height: 32px;
+				border: none;
+				border-radius: var(--radius-md);
+
+				/* text-14-medium */
+				font-family: var(--font-family-sans);
+				font-size: var(--font-size-base);
+				font-weight: var(--font-weight-medium);
+				line-height: var(--line-height-large);
+
+				[data-slot="tabs-trigger"] {
+					border: none;
+					padding: 0 8px;
+					gap: 12px;
+					justify-content: flex-start;
+					width: 100%;
+					height: 100%;
+				}
+
+				[data-component="icon"] {
+					color: var(--icon-base);
+				}
+
+				&:hover:not(:disabled) {
+					background-color: var(--surface-raised-base-hover);
+				}
+
+				&:has([data-selected]) {
+					background-color: var(--surface-raised-base-active);
+					color: var(--text-strong);
+
+					[data-component="icon"] {
+						color: var(--icon-strong-base);
+					}
+
+					&:hover:not(:disabled) {
+						background-color: var(--surface-raised-base-active);
+					}
+				}
+			}
+
+			[data-slot="tabs-content"] {
+				background-color: var(--surface-raised-stronger-non-alpha);
+			}
+		}
+	}
 }

+ 33 - 30
packages/ui/src/components/tag.css

@@ -1,37 +1,40 @@
 [data-component="tag"] {
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  user-select: none;
+	display: inline-flex;
+	align-items: center;
+	justify-content: center;
+	user-select: none;
 
-  border-radius: var(--radius-xs);
-  border: 0.5px solid var(--border-weak-base);
-  background: var(--surface-raised-base);
-  color: var(--text-base);
+	border-radius: var(--radius-xs);
+	border: 0.5px solid var(--border-weak-base);
+	background: var(--surface-raised-base);
+	color: var(--text-base);
+	transition-property: background-color, border-color, color;
+	transition-duration: var(--transition-duration);
+	transition-timing-function: var(--transition-easing);
 
-  &[data-size="normal"] {
-    height: 18px;
-    padding: 0 6px;
+	&[data-size="normal"] {
+		height: 18px;
+		padding: 0 6px;
 
-    /* text-12-medium */
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-small);
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large); /* 166.667% */
-    letter-spacing: var(--letter-spacing-normal);
-  }
+		/* text-12-medium */
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-small);
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large); /* 166.667% */
+		letter-spacing: var(--letter-spacing-normal);
+	}
 
-  &[data-size="large"] {
-    height: 22px;
-    padding: 0 8px;
+	&[data-size="large"] {
+		height: 22px;
+		padding: 0 8px;
 
-    /* text-14-medium */
-    font-family: var(--font-family-sans);
-    font-size: 14px;
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large); /* 142.857% */
-    letter-spacing: var(--letter-spacing-normal);
-  }
+		/* text-14-medium */
+		font-family: var(--font-family-sans);
+		font-size: 14px;
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large); /* 142.857% */
+		letter-spacing: var(--letter-spacing-normal);
+	}
 }

+ 132 - 132
packages/ui/src/components/text-field.css

@@ -1,134 +1,134 @@
 [data-component="input"] {
-  width: 100%;
-
-  [data-slot="input-input"] {
-    width: 100%;
-    color: var(--text-strong);
-
-    /* text-14-regular */
-    font-family: var(--font-family-sans);
-    font-size: 14px;
-    font-style: normal;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-large); /* 142.857% */
-    letter-spacing: var(--letter-spacing-normal);
-
-    &:focus {
-      outline: none;
-    }
-
-    &::placeholder {
-      color: var(--text-weak);
-    }
-  }
-
-  &[data-variant="normal"] {
-    display: flex;
-    flex-direction: column;
-    align-items: flex-start;
-    gap: 8px;
-
-    [data-slot="input-label"] {
-      color: var(--text-weak);
-
-      /* text-12-medium */
-      font-family: var(--font-family-sans);
-      font-size: var(--font-size-small);
-      font-style: normal;
-      font-weight: var(--font-weight-medium);
-      line-height: 18px; /* 150% */
-      letter-spacing: var(--letter-spacing-normal);
-    }
-
-    [data-slot="input-wrapper"] {
-      display: flex;
-      align-items: start;
-      justify-content: space-between;
-      width: 100%;
-      padding-right: 4px;
-
-      border-radius: var(--radius-md);
-      border: 1px solid var(--border-weak-base);
-      background: var(--input-base);
-
-      &:focus-within:not(:has([data-readonly])) {
-        border-color: transparent;
-        /* border/shadow-xs/select */
-        box-shadow:
-          0 0 0 3px var(--border-weak-selected),
-          0 0 0 1px var(--border-selected),
-          0 1px 2px -1px rgba(19, 16, 16, 0.25),
-          0 1px 2px 0 rgba(19, 16, 16, 0.08),
-          0 1px 3px 0 rgba(19, 16, 16, 0.12);
-      }
-
-      &:has([data-invalid]) {
-        background: var(--surface-critical-weak);
-        border: 1px solid var(--border-critical-selected);
-      }
-
-      &:not(:has([data-slot="input-copy-button"])) {
-        padding-right: 0;
-      }
-    }
-
-    [data-slot="input-input"] {
-      color: var(--text-strong);
-
-      display: flex;
-      height: 32px;
-      padding: 2px 12px;
-      align-items: center;
-      flex: 1;
-      min-width: 0;
-
-      background: transparent;
-      border: none;
-
-      /* text-14-regular */
-      font-family: var(--font-family-sans);
-      font-size: 14px;
-      font-style: normal;
-      font-weight: var(--font-weight-regular);
-      line-height: var(--line-height-large); /* 142.857% */
-      letter-spacing: var(--letter-spacing-normal);
-
-      &:focus {
-        outline: none;
-      }
-
-      &::placeholder {
-        color: var(--text-weak);
-      }
-    }
-
-    textarea[data-slot="input-input"] {
-      height: auto;
-      min-height: 32px;
-      padding: 6px 12px;
-      resize: none;
-    }
-
-    [data-slot="input-copy-button"] {
-      flex-shrink: 0;
-      margin-top: 4px;
-      color: var(--icon-base);
-
-      &:hover {
-        color: var(--icon-strong-base);
-      }
-    }
-
-    [data-slot="input-error"] {
-      color: var(--text-on-critical-base);
-
-      /* text-12-medium */
-      font-family: var(--font-family-sans);
-      font-size: var(--font-size-small);
-      font-style: normal;
-      font-weight: var(--font-weight-medium);
-      line-height: 18px; /* 150% */
-      letter-spacing: var(--letter-spacing-normal);
-    }
-  }
+	width: 100%;
+
+	[data-slot="input-input"] {
+		width: 100%;
+		color: var(--text-strong);
+
+		/* text-14-regular */
+		font-family: var(--font-family-sans);
+		font-size: 14px;
+		font-style: normal;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-large); /* 142.857% */
+		letter-spacing: var(--letter-spacing-normal);
+
+		&:focus {
+			outline: none;
+		}
+
+		&::placeholder {
+			color: var(--text-weak);
+		}
+	}
+
+	&[data-variant="normal"] {
+		display: flex;
+		flex-direction: column;
+		align-items: flex-start;
+		gap: 8px;
+
+		[data-slot="input-label"] {
+			color: var(--text-weak);
+
+			/* text-12-medium */
+			font-family: var(--font-family-sans);
+			font-size: var(--font-size-small);
+			font-style: normal;
+			font-weight: var(--font-weight-medium);
+			line-height: 18px; /* 150% */
+			letter-spacing: var(--letter-spacing-normal);
+		}
+
+		[data-slot="input-wrapper"] {
+			display: flex;
+			align-items: start;
+			justify-content: space-between;
+			width: 100%;
+			padding-right: 4px;
+
+			border-radius: var(--radius-md);
+			border: 1px solid var(--border-weak-base);
+			background: var(--input-base);
+
+			&:focus-within:not(:has([data-readonly])) {
+				border-color: transparent;
+				/* border/shadow-xs/select */
+				box-shadow:
+					0 0 0 3px var(--border-weak-selected),
+					0 0 0 1px var(--border-selected),
+					0 1px 2px -1px rgba(19, 16, 16, 0.25),
+					0 1px 2px 0 rgba(19, 16, 16, 0.08),
+					0 1px 3px 0 rgba(19, 16, 16, 0.12);
+			}
+
+			&:has([data-invalid]) {
+				background: var(--surface-critical-weak);
+				border: 1px solid var(--border-critical-selected);
+			}
+
+			&:not(:has([data-slot="input-copy-button"])) {
+				padding-right: 0;
+			}
+		}
+
+		[data-slot="input-input"] {
+			color: var(--text-strong);
+
+			display: flex;
+			height: 32px;
+			padding: 2px 12px;
+			align-items: center;
+			flex: 1;
+			min-width: 0;
+
+			background: transparent;
+			border: none;
+
+			/* text-14-regular */
+			font-family: var(--font-family-sans);
+			font-size: 14px;
+			font-style: normal;
+			font-weight: var(--font-weight-regular);
+			line-height: var(--line-height-large); /* 142.857% */
+			letter-spacing: var(--letter-spacing-normal);
+
+			&:focus {
+				outline: none;
+			}
+
+			&::placeholder {
+				color: var(--text-weak);
+			}
+		}
+
+		textarea[data-slot="input-input"] {
+			height: auto;
+			min-height: 32px;
+			padding: 6px 12px;
+			resize: none;
+		}
+
+		[data-slot="input-copy-button"] {
+			flex-shrink: 0;
+			margin-top: 4px;
+			color: var(--icon-base);
+
+			&:hover {
+				color: var(--icon-strong-base);
+			}
+		}
+
+		[data-slot="input-error"] {
+			color: var(--text-on-critical-base);
+
+			/* text-12-medium */
+			font-family: var(--font-family-sans);
+			font-size: var(--font-size-small);
+			font-style: normal;
+			font-weight: var(--font-weight-medium);
+			line-height: 18px; /* 150% */
+			letter-spacing: var(--letter-spacing-normal);
+		}
+	}
 }

+ 191 - 191
packages/ui/src/components/toast.css

@@ -1,205 +1,205 @@
 [data-component="toast-region"] {
-  position: fixed;
-  bottom: 48px;
-  right: 32px;
-  z-index: 1000;
-  display: flex;
-  flex-direction: column;
-  gap: 8px;
-  max-width: 400px;
-  width: 100%;
-  pointer-events: none;
-
-  [data-slot="toast-list"] {
-    display: flex;
-    flex-direction: column;
-    gap: 8px;
-    list-style: none;
-    margin: 0;
-    padding: 0;
-  }
+	position: fixed;
+	bottom: 48px;
+	right: 32px;
+	z-index: 1000;
+	display: flex;
+	flex-direction: column;
+	gap: 8px;
+	max-width: 400px;
+	width: 100%;
+	pointer-events: none;
+
+	[data-slot="toast-list"] {
+		display: flex;
+		flex-direction: column;
+		gap: 8px;
+		list-style: none;
+		margin: 0;
+		padding: 0;
+	}
 }
 
 [data-component="toast"] {
-  display: flex;
-  align-items: flex-start;
-  gap: 20px;
-  padding: 16px 20px;
-  pointer-events: auto;
-  transition: all 150ms ease-out;
-
-  border-radius: var(--radius-lg);
-  border: 1px solid var(--border-weak-base);
-  background: var(--surface-float-base);
-  color: var(--text-invert-base);
-  box-shadow: var(--shadow-md);
-
-  [data-slot="toast-inner"] {
-    display: flex;
-    align-items: flex-start;
-    gap: 10px;
-  }
-
-  &[data-opened] {
-    animation: toastPopIn 150ms ease-out;
-  }
-
-  &[data-closed] {
-    animation: toastPopOut 100ms ease-in forwards;
-  }
-
-  &[data-swipe="move"] {
-    transform: translateX(var(--kb-toast-swipe-move-x));
-  }
-
-  &[data-swipe="cancel"] {
-    transform: translateX(0);
-    transition: transform 200ms ease-out;
-  }
-
-  &[data-swipe="end"] {
-    animation: toastSwipeOut 100ms ease-out forwards;
-  }
-
-  /* &[data-variant="success"] { */
-  /*   border-color: var(--color-semantic-positive); */
-  /* } */
-  /**/
-  /* &[data-variant="error"] { */
-  /*   border-color: var(--color-semantic-danger); */
-  /* } */
-  /**/
-  /* &[data-variant="loading"] { */
-  /*   border-color: var(--color-semantic-info); */
-  /* } */
-
-  [data-slot="toast-icon"] {
-    flex-shrink: 0;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-
-    [data-component="icon"] {
-      color: var(--text-invert-stronger);
-      /* color: var(--icon-invert-base); */
-    }
-  }
-
-  [data-slot="toast-content"] {
-    flex: 1;
-    display: flex;
-    flex-direction: column;
-    gap: 2px;
-    min-width: 0;
-  }
-
-  [data-slot="toast-title"] {
-    color: var(--text-invert-strong);
-
-    /* text-14-medium */
-    font-family: var(--font-family-sans);
-    font-size: 14px;
-    font-style: normal;
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large); /* 142.857% */
-    letter-spacing: var(--letter-spacing-normal);
-
-    margin: 0;
-  }
-
-  [data-slot="toast-description"] {
-    color: var(--text-invert-base);
-    text-wrap-style: pretty;
-
-    /* text-14-regular */
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-base);
-    font-style: normal;
-    font-weight: var(--font-weight-regular);
-    line-height: var(--line-height-x-large); /* 171.429% */
-    letter-spacing: var(--letter-spacing-normal);
-
-    margin: 0;
-  }
-
-  [data-slot="toast-actions"] {
-    display: flex;
-    gap: 16px;
-    margin-top: 8px;
-  }
-
-  [data-slot="toast-action"] {
-    background: none;
-    border: none;
-    padding: 0;
-    cursor: pointer;
-
-    color: var(--text-invert-weak);
-    font-family: var(--font-family-sans);
-    font-size: var(--font-size-base);
-    font-weight: var(--font-weight-medium);
-    line-height: var(--line-height-large);
-    letter-spacing: var(--letter-spacing-normal);
-
-    &:hover {
-      text-decoration: underline;
-    }
-
-    &:first-child {
-      color: var(--text-invert-strong);
-    }
-  }
-
-  [data-slot="toast-close-button"] {
-    flex-shrink: 0;
-  }
-
-  [data-slot="toast-progress-track"] {
-    position: absolute;
-    bottom: 0;
-    left: 0;
-    right: 0;
-    height: 3px;
-    background-color: var(--surface-base);
-    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
-    overflow: hidden;
-  }
-
-  [data-slot="toast-progress-fill"] {
-    height: 100%;
-    width: var(--kb-toast-progress-fill-width);
-    background-color: var(--color-primary);
-    transition: width 250ms linear;
-  }
+	display: flex;
+	align-items: flex-start;
+	gap: 20px;
+	padding: 16px 20px;
+	pointer-events: auto;
+	transition: all 150ms ease-out;
+
+	border-radius: var(--radius-lg);
+	border: 1px solid var(--border-weak-base);
+	background: var(--surface-float-base);
+	color: var(--text-invert-base);
+	box-shadow: var(--shadow-md);
+
+	[data-slot="toast-inner"] {
+		display: flex;
+		align-items: flex-start;
+		gap: 10px;
+	}
+
+	&[data-opened] {
+		animation: toastPopIn 150ms ease-out;
+	}
+
+	&[data-closed] {
+		animation: toastPopOut 100ms ease-in forwards;
+	}
+
+	&[data-swipe="move"] {
+		transform: translateX(var(--kb-toast-swipe-move-x));
+	}
+
+	&[data-swipe="cancel"] {
+		transform: translateX(0);
+		transition: transform 200ms ease-out;
+	}
+
+	&[data-swipe="end"] {
+		animation: toastSwipeOut 100ms ease-out forwards;
+	}
+
+	/* &[data-variant="success"] { */
+	/*   border-color: var(--color-semantic-positive); */
+	/* } */
+	/**/
+	/* &[data-variant="error"] { */
+	/*   border-color: var(--color-semantic-danger); */
+	/* } */
+	/**/
+	/* &[data-variant="loading"] { */
+	/*   border-color: var(--color-semantic-info); */
+	/* } */
+
+	[data-slot="toast-icon"] {
+		flex-shrink: 0;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+
+		[data-component="icon"] {
+			color: var(--text-invert-stronger);
+			/* color: var(--icon-invert-base); */
+		}
+	}
+
+	[data-slot="toast-content"] {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		gap: 2px;
+		min-width: 0;
+	}
+
+	[data-slot="toast-title"] {
+		color: var(--text-invert-strong);
+
+		/* text-14-medium */
+		font-family: var(--font-family-sans);
+		font-size: 14px;
+		font-style: normal;
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large); /* 142.857% */
+		letter-spacing: var(--letter-spacing-normal);
+
+		margin: 0;
+	}
+
+	[data-slot="toast-description"] {
+		color: var(--text-invert-base);
+		text-wrap-style: pretty;
+
+		/* text-14-regular */
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-base);
+		font-style: normal;
+		font-weight: var(--font-weight-regular);
+		line-height: var(--line-height-x-large); /* 171.429% */
+		letter-spacing: var(--letter-spacing-normal);
+
+		margin: 0;
+	}
+
+	[data-slot="toast-actions"] {
+		display: flex;
+		gap: 16px;
+		margin-top: 8px;
+	}
+
+	[data-slot="toast-action"] {
+		background: none;
+		border: none;
+		padding: 0;
+		cursor: pointer;
+
+		color: var(--text-invert-weak);
+		font-family: var(--font-family-sans);
+		font-size: var(--font-size-base);
+		font-weight: var(--font-weight-medium);
+		line-height: var(--line-height-large);
+		letter-spacing: var(--letter-spacing-normal);
+
+		&:hover {
+			text-decoration: underline;
+		}
+
+		&:first-child {
+			color: var(--text-invert-strong);
+		}
+	}
+
+	[data-slot="toast-close-button"] {
+		flex-shrink: 0;
+	}
+
+	[data-slot="toast-progress-track"] {
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		height: 3px;
+		background-color: var(--surface-base);
+		border-radius: 0 0 var(--radius-lg) var(--radius-lg);
+		overflow: hidden;
+	}
+
+	[data-slot="toast-progress-fill"] {
+		height: 100%;
+		width: var(--kb-toast-progress-fill-width);
+		background-color: var(--color-primary);
+		transition: width 250ms linear;
+	}
 }
 
 @keyframes toastPopIn {
-  from {
-    opacity: 0;
-    transform: translateY(20px);
-  }
-  to {
-    opacity: 1;
-    transform: translateY(0);
-  }
+	from {
+		opacity: 0;
+		transform: translateY(20px);
+	}
+	to {
+		opacity: 1;
+		transform: translateY(0);
+	}
 }
 
 @keyframes toastPopOut {
-  from {
-    opacity: 1;
-    transform: translateY(0);
-  }
-  to {
-    opacity: 0;
-    transform: translateY(20px);
-  }
+	from {
+		opacity: 1;
+		transform: translateY(0);
+	}
+	to {
+		opacity: 0;
+		transform: translateY(20px);
+	}
 }
 
 @keyframes toastSwipeOut {
-  from {
-    transform: translateX(var(--kb-toast-swipe-end-x));
-  }
-  to {
-    transform: translateX(100%);
-  }
+	from {
+		transform: translateX(var(--kb-toast-swipe-end-x));
+	}
+	to {
+		transform: translateX(100%);
+	}
 }

+ 58 - 58
packages/ui/src/components/tooltip.css

@@ -1,74 +1,74 @@
 [data-component="tooltip-trigger"] {
-  display: flex;
+	display: flex;
 }
 
 [data-slot="tooltip-keybind"] {
-  display: flex;
-  align-items: center;
-  gap: 12px;
+	display: flex;
+	align-items: center;
+	gap: 12px;
 }
 
 [data-slot="tooltip-keybind-key"] {
-  color: var(--text-invert-base);
-  font-size: var(--font-size-small);
-  font-weight: var(--font-weight-medium);
-  line-height: var(--line-height-large);
+	color: var(--text-invert-base);
+	font-size: var(--font-size-small);
+	font-weight: var(--font-weight-medium);
+	line-height: var(--line-height-large);
 }
 
 [data-component="tooltip"] {
-  z-index: 1000;
-  max-width: 320px;
-  border-radius: var(--radius-sm);
-  background-color: var(--surface-float-base);
-  color: var(--text-invert-strong);
-  background: var(--surface-float-base);
-  padding: 2px 8px;
-  border: 1px solid var(--border-weak-base, rgba(0, 0, 0, 0.07));
+	z-index: 1000;
+	max-width: 320px;
+	border-radius: var(--radius-sm);
+	background-color: var(--surface-float-base);
+	color: var(--text-invert-strong);
+	background: var(--surface-float-base);
+	padding: 2px 8px;
+	border: 1px solid var(--border-weak-base, rgba(0, 0, 0, 0.07));
 
-  box-shadow: var(--shadow-md);
-  pointer-events: none !important;
-  /* transition: all 150ms ease-out; */
-  /* transform: translate3d(0, 0, 0); */
-  /* transform-origin: var(--kb-tooltip-content-transform-origin); */
+	box-shadow: var(--shadow-md);
+	pointer-events: none !important;
+	/* transition: all 150ms ease-out; */
+	/* transform: translate3d(0, 0, 0); */
+	/* transform-origin: var(--kb-tooltip-content-transform-origin); */
 
-  /* text-12-medium */
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-small);
-  font-style: normal;
-  font-weight: var(--font-weight-medium);
-  line-height: var(--line-height-large); /* 166.667% */
-  letter-spacing: var(--letter-spacing-normal);
+	/* text-12-medium */
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-small);
+	font-style: normal;
+	font-weight: var(--font-weight-medium);
+	line-height: var(--line-height-large); /* 166.667% */
+	letter-spacing: var(--letter-spacing-normal);
 
-  &[data-expanded] {
-    opacity: 1;
-    /* transform: translate3d(0, 0, 0); */
-  }
+	&[data-expanded] {
+		opacity: 1;
+		/* transform: translate3d(0, 0, 0); */
+	}
 
-  &[data-closed]:not([data-force-open="true"]) {
-    opacity: 0;
-  }
+	&[data-closed]:not([data-force-open="true"]) {
+		opacity: 0;
+	}
 
-  /* &[data-placement="top"] { */
-  /*   &[data-closed] { */
-  /*     transform: translate3d(0, 4px, 0); */
-  /*   } */
-  /* } */
-  /**/
-  /* &[data-placement="bottom"] { */
-  /*   &[data-closed] { */
-  /*     transform: translate3d(0, -4px, 0); */
-  /*   } */
-  /* } */
-  /**/
-  /* &[data-placement="left"] { */
-  /*   &[data-closed] { */
-  /*     transform: translate3d(4px, 0, 0); */
-  /*   } */
-  /* } */
-  /**/
-  /* &[data-placement="right"] { */
-  /*   &[data-closed] { */
-  /*     transform: translate3d(-4px, 0, 0); */
-  /*   } */
-  /* } */
+	/* &[data-placement="top"] { */
+	/*   &[data-closed] { */
+	/*     transform: translate3d(0, 4px, 0); */
+	/*   } */
+	/* } */
+	/**/
+	/* &[data-placement="bottom"] { */
+	/*   &[data-closed] { */
+	/*     transform: translate3d(0, -4px, 0); */
+	/*   } */
+	/* } */
+	/**/
+	/* &[data-placement="left"] { */
+	/*   &[data-closed] { */
+	/*     transform: translate3d(4px, 0, 0); */
+	/*   } */
+	/* } */
+	/**/
+	/* &[data-placement="right"] { */
+	/*   &[data-closed] { */
+	/*     transform: translate3d(-4px, 0, 0); */
+	/*   } */
+	/* } */
 }

+ 9 - 9
packages/ui/src/components/typewriter.css

@@ -1,14 +1,14 @@
 @keyframes blink {
-  0%,
-  50% {
-    opacity: 1;
-  }
-  51%,
-  100% {
-    opacity: 0;
-  }
+	0%,
+	50% {
+		opacity: 1;
+	}
+	51%,
+	100% {
+		opacity: 0;
+	}
 }
 
 .blinking-cursor {
-  animation: blink 1s step-end infinite;
+	animation: blink 1s step-end infinite;
 }

+ 24 - 0
packages/ui/src/context/dialog.tsx

@@ -28,6 +28,7 @@ const Context = createContext<ReturnType<typeof init>>()
 
 function init() {
   const [active, setActive] = createSignal<Active | undefined>()
+  const [renders, setRenders] = createSignal<Record<string, JSX.Element>>({})
   const timer = { current: undefined as ReturnType<typeof setTimeout> | undefined }
   const lock = { value: false }
 
@@ -118,12 +119,28 @@ function init() {
     setActive({ id, node, dispose, owner, onClose, setClosing })
   }
 
+  const render = (element: JSX.Element, id: string, owner: Owner) => {
+    setRenders((renders) => ({ ...renders, [id]: element }))
+    show(() => element, owner, () => {
+      setRenders((renders) => {
+        const { [id]: _, ...rest } = renders
+        return rest
+      })
+    })
+  }
+
+  const isActive = (id: string) => {
+    return renders()[id] !== undefined
+  }
+
   return {
     get active() {
       return active()
     },
+    isActive,
     close,
     show,
+    render,
   }
 }
 
@@ -152,10 +169,17 @@ export function useDialog() {
     get active() {
       return ctx.active
     },
+    isActive(id: string) {
+      return ctx.isActive(id)
+    },
     show(element: DialogElement, onClose?: () => void) {
       const base = ctx.active?.owner ?? owner
       ctx.show(element, base, onClose)
     },
+    render(element: JSX.Element, id: string) {
+      const base = ctx.active?.owner ?? owner
+      ctx.render(element, id, base)
+    },
     close() {
       ctx.close()
     },

+ 2 - 0
packages/ui/src/styles/index.css

@@ -33,8 +33,10 @@
 @import "../components/markdown.css" layer(components);
 @import "../components/message-part.css" layer(components);
 @import "../components/message-nav.css" layer(components);
+@import "../components/morph-chevron.css" layer(components);
 @import "../components/popover.css" layer(components);
 @import "../components/progress-circle.css" layer(components);
+@import "../components/reasoning-icon.css" layer(components);
 @import "../components/radio-group.css" layer(components);
 @import "../components/resize-handle.css" layer(components);
 @import "../components/select.css" layer(components);

+ 139 - 96
packages/ui/src/styles/utilities.css

@@ -1,131 +1,174 @@
 :root {
-  interpolate-size: allow-keywords;
-
-  [data-popper-positioner] {
-    pointer-events: none;
-  }
-
-  /* ::selection { */
-  /*   background-color: color-mix(in srgb, var(--color-primary) 33%, transparent); */
-  /*   background-color: var(--color-primary); */
-  /*   color: var(--color-background); */
-  /* } */
-
-  ::-webkit-scrollbar-track {
-    background: transparent;
-  }
-
-  ::-webkit-scrollbar-thumb {
-    background-color: var(--surface-float-base);
-    border-radius: var(--radius-md);
-  }
-
-  * {
-    scrollbar-color: var(--surface-float-base) transparent;
-  }
+	interpolate-size: allow-keywords;
+
+	/* Transition tokens */
+	--transition-duration: 200ms;
+	--transition-easing: cubic-bezier(0.25, 0, 0.5, 1);
+	--transition-fast: 150ms;
+	--transition-slow: 300ms;
+
+	/* Allow height transitions from 0 to auto */
+	@supports (interpolate-size: allow-keywords) {
+		interpolate-size: allow-keywords;
+	}
+
+	[data-popper-positioner] {
+		pointer-events: none;
+	}
+
+	/* ::selection { */
+	/*   background-color: color-mix(in srgb, var(--color-primary) 33%, transparent); */
+	/*   background-color: var(--color-primary); */
+	/*   color: var(--color-background); */
+	/* } */
+
+	::-webkit-scrollbar-track {
+		background: transparent;
+	}
+
+	::-webkit-scrollbar-thumb {
+		background-color: var(--surface-float-base);
+		border-radius: var(--radius-md);
+	}
+
+	* {
+		scrollbar-color: var(--surface-float-base) transparent;
+	}
 }
 
 .no-scrollbar {
-  &::-webkit-scrollbar {
-    display: none;
-  }
-  /* Hide scrollbar for IE, Edge and Firefox */
-  & {
-    -ms-overflow-style: none; /* IE and Edge */
-    scrollbar-width: none; /* Firefox */
-  }
+	&::-webkit-scrollbar {
+		display: none;
+	}
+	/* Hide scrollbar for IE, Edge and Firefox */
+	& {
+		-ms-overflow-style: none; /* IE and Edge */
+		scrollbar-width: none; /* Firefox */
+	}
 }
 
 .sr-only {
-  position: absolute;
-  width: 1px;
-  height: 1px;
-  padding: 0;
-  margin: -1px;
-  overflow: hidden;
-  clip: rect(0, 0, 0, 0);
-  white-space: nowrap;
-  border-width: 0;
+	position: absolute;
+	width: 1px;
+	height: 1px;
+	padding: 0;
+	margin: -1px;
+	overflow: hidden;
+	clip: rect(0, 0, 0, 0);
+	white-space: nowrap;
+	border-width: 0;
 }
 
 .truncate-start {
-  text-overflow: ellipsis;
-  overflow: hidden;
-  white-space: nowrap;
-  direction: rtl;
-  text-align: left;
+	text-overflow: ellipsis;
+	overflow: hidden;
+	white-space: nowrap;
+	direction: rtl;
+	text-align: left;
 }
 
 .text-12-regular {
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-small);
-  font-style: normal;
-  font-weight: var(--font-weight-regular);
-  line-height: var(--line-height-large); /* 166.667% */
-  letter-spacing: var(--letter-spacing-normal);
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-small);
+	font-style: normal;
+	font-weight: var(--font-weight-regular);
+	line-height: var(--line-height-large); /* 166.667% */
+	letter-spacing: var(--letter-spacing-normal);
 }
 
 .text-12-medium {
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-small);
-  font-style: normal;
-  font-weight: var(--font-weight-medium);
-  line-height: var(--line-height-large); /* 166.667% */
-  letter-spacing: var(--letter-spacing-normal);
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-small);
+	font-style: normal;
+	font-weight: var(--font-weight-medium);
+	line-height: var(--line-height-large); /* 166.667% */
+	letter-spacing: var(--letter-spacing-normal);
 }
 
 .text-12-mono {
-  font-family: var(--font-family-mono);
-  font-feature-settings: var(--font-feature-settings-mono);
-  font-size: var(--font-size-small);
-  font-style: normal;
-  font-weight: var(--font-weight-regular);
-  line-height: var(--line-height-large); /* 166.667% */
-  letter-spacing: var(--letter-spacing-normal);
+	font-family: var(--font-family-mono);
+	font-feature-settings: var(--font-feature-settings-mono);
+	font-size: var(--font-size-small);
+	font-style: normal;
+	font-weight: var(--font-weight-regular);
+	line-height: var(--line-height-large); /* 166.667% */
+	letter-spacing: var(--letter-spacing-normal);
 }
 
 .text-14-regular {
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-base);
-  font-style: normal;
-  font-weight: var(--font-weight-regular);
-  line-height: var(--line-height-x-large); /* 171.429% */
-  letter-spacing: var(--letter-spacing-normal);
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-base);
+	font-style: normal;
+	font-weight: var(--font-weight-regular);
+	line-height: var(--line-height-x-large); /* 171.429% */
+	letter-spacing: var(--letter-spacing-normal);
 }
 
 .text-14-medium {
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-base);
-  font-style: normal;
-  font-weight: var(--font-weight-medium);
-  line-height: var(--line-height-large); /* 171.429% */
-  letter-spacing: var(--letter-spacing-normal);
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-base);
+	font-style: normal;
+	font-weight: var(--font-weight-medium);
+	line-height: var(--line-height-large); /* 171.429% */
+	letter-spacing: var(--letter-spacing-normal);
 }
 
 .text-14-mono {
-  font-family: var(--font-family-mono);
-  font-feature-settings: var(--font-feature-settings-mono);
-  font-size: var(--font-size-base);
-  font-style: normal;
-  font-weight: var(--font-weight-regular);
-  line-height: var(--line-height-large); /* 171.429% */
-  letter-spacing: var(--letter-spacing-normal);
+	font-family: var(--font-family-mono);
+	font-feature-settings: var(--font-feature-settings-mono);
+	font-size: var(--font-size-base);
+	font-style: normal;
+	font-weight: var(--font-weight-regular);
+	line-height: var(--line-height-large); /* 171.429% */
+	letter-spacing: var(--letter-spacing-normal);
 }
 
 .text-16-medium {
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-large);
-  font-style: normal;
-  font-weight: var(--font-weight-medium);
-  line-height: var(--line-height-x-large); /* 150% */
-  letter-spacing: var(--letter-spacing-tight);
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-large);
+	font-style: normal;
+	font-weight: var(--font-weight-medium);
+	line-height: var(--line-height-x-large); /* 150% */
+	letter-spacing: var(--letter-spacing-tight);
 }
 
 .text-20-medium {
-  font-family: var(--font-family-sans);
-  font-size: var(--font-size-x-large);
-  font-style: normal;
-  font-weight: var(--font-weight-medium);
-  line-height: var(--line-height-x-large); /* 120% */
-  letter-spacing: var(--letter-spacing-tightest);
+	font-family: var(--font-family-sans);
+	font-size: var(--font-size-x-large);
+	font-style: normal;
+	font-weight: var(--font-weight-medium);
+	line-height: var(--line-height-x-large); /* 120% */
+	letter-spacing: var(--letter-spacing-tightest);
+}
+
+/* Transition utility classes */
+.transition-colors {
+	transition-property: background-color, border-color, color, fill, stroke;
+	transition-duration: var(--transition-duration);
+	transition-timing-function: var(--transition-easing);
+}
+
+.transition-opacity {
+	transition-property: opacity;
+	transition-duration: var(--transition-duration);
+	transition-timing-function: var(--transition-easing);
+}
+
+.transition-transform {
+	transition-property: transform;
+	transition-duration: var(--transition-duration);
+	transition-timing-function: var(--transition-easing);
+}
+
+.transition-shadow {
+	transition-property: box-shadow;
+	transition-duration: var(--transition-duration);
+	transition-timing-function: var(--transition-easing);
+}
+
+.transition-interactive {
+	transition-property:
+		background-color, border-color, color, box-shadow, opacity;
+	transition-duration: var(--transition-duration);
+	transition-timing-function: var(--transition-easing);
 }