Răsfoiți Sursa

chore(app): cleanup tailwind vs pure css

adamelmore 3 săptămâni în urmă
părinte
comite
099ab929db

+ 9 - 30
packages/app/src/components/dialog-edit-project.tsx

@@ -145,8 +145,7 @@ export function DialogEditProject(props: { project: LocalProject }) {
                         <Avatar
                           fallback={store.name || defaultName()}
                           {...getAvatarColors(store.color)}
-                          class="size-full"
-                          style={{ "font-size": "32px" }}
+                          class="size-full text-[32px]"
                         />
                       </div>
                     }
@@ -159,39 +158,19 @@ export function DialogEditProject(props: { project: LocalProject }) {
                   </Show>
                 </div>
                 <div
-                  style={{
-                    position: "absolute",
-                    top: 0,
-                    left: 0,
-                    width: "64px",
-                    height: "64px",
-                    background: "rgba(0,0,0,0.6)",
-                    "border-radius": "6px",
-                    "z-index": 10,
-                    "pointer-events": "none",
-                    opacity: store.iconHover && !store.iconUrl ? 1 : 0,
-                    display: "flex",
-                    "align-items": "center",
-                    "justify-content": "center",
+                  class="absolute inset-0 size-16 bg-black/60 rounded-[6px] z-10 pointer-events-none flex items-center justify-center transition-opacity"
+                  classList={{
+                    "opacity-100": store.iconHover && !store.iconUrl,
+                    "opacity-0": !(store.iconHover && !store.iconUrl),
                   }}
                 >
                   <Icon name="cloud-upload" size="large" class="text-icon-invert-base" />
                 </div>
                 <div
-                  style={{
-                    position: "absolute",
-                    top: 0,
-                    left: 0,
-                    width: "64px",
-                    height: "64px",
-                    background: "rgba(0,0,0,0.6)",
-                    "border-radius": "6px",
-                    "z-index": 10,
-                    "pointer-events": "none",
-                    opacity: store.iconHover && store.iconUrl ? 1 : 0,
-                    display: "flex",
-                    "align-items": "center",
-                    "justify-content": "center",
+                  class="absolute inset-0 size-16 bg-black/60 rounded-[6px] z-10 pointer-events-none flex items-center justify-center transition-opacity"
+                  classList={{
+                    "opacity-100": store.iconHover && !!store.iconUrl,
+                    "opacity-0": !(store.iconHover && !!store.iconUrl),
                   }}
                 >
                   <Icon name="trash" size="large" class="text-icon-invert-base" />

+ 2 - 6
packages/app/src/components/dialog-fork.tsx

@@ -90,12 +90,8 @@ export const DialogFork: Component = () => {
       >
         {(item) => (
           <div class="w-full flex items-center gap-2">
-            <span class="truncate flex-1 min-w-0 text-left" style={{ "font-weight": "400" }}>
-              {item.text}
-            </span>
-            <span class="text-text-weak shrink-0" style={{ "font-weight": "400" }}>
-              {item.time}
-            </span>
+            <span class="truncate flex-1 min-w-0 text-left font-normal">{item.text}</span>
+            <span class="text-text-weak shrink-0 font-normal">{item.time}</span>
           </div>
         )}
       </List>

+ 70 - 64
packages/app/src/components/dialog-release-notes.tsx

@@ -73,80 +73,86 @@ export function DialogReleaseNotes(props: { highlights: Highlight[] }) {
   })
 
   return (
-    <Dialog class="dialog-release-notes">
+    <Dialog
+      size="large"
+      fit
+      class="w-[min(calc(100vw-40px),720px)] h-[min(calc(100vh-40px),400px)] -mt-20 min-h-0 overflow-hidden"
+    >
       {/* Hidden element to capture initial focus and handle escape */}
       <div ref={focusTrap} tabindex="0" class="absolute opacity-0 pointer-events-none" />
-      {/* Left side - Text content */}
-      <div class="flex flex-col flex-1 min-w-0 p-8">
-        {/* Top section - feature content (fixed position from top) */}
-        <div class="flex flex-col gap-2 pt-22">
-          <div class="flex items-center gap-2">
-            <h1 class="text-16-medium text-text-strong">{feature()?.title ?? ""}</h1>
+      <div class="flex flex-1 min-w-0 min-h-0">
+        {/* Left side - Text content */}
+        <div class="flex flex-col flex-1 min-w-0 p-8">
+          {/* Top section - feature content (fixed position from top) */}
+          <div class="flex flex-col gap-2 pt-22">
+            <div class="flex items-center gap-2">
+              <h1 class="text-16-medium text-text-strong">{feature()?.title ?? ""}</h1>
+            </div>
+            <p class="text-14-regular text-text-base">{feature()?.description ?? ""}</p>
           </div>
-          <p class="text-14-regular text-text-base">{feature()?.description ?? ""}</p>
-        </div>
-
-        {/* Spacer to push buttons to bottom */}
-        <div class="flex-1" />
 
-        {/* Bottom section - buttons and indicators (fixed position) */}
-        <div class="flex flex-col gap-12">
-          <div class="flex flex-col items-start gap-3">
-            {isLast() ? (
-              <Button variant="primary" size="large" onClick={handleClose}>
-                Get started
-              </Button>
-            ) : (
-              <Button variant="secondary" size="large" onClick={handleNext}>
-                Next
+          {/* Spacer to push buttons to bottom */}
+          <div class="flex-1" />
+
+          {/* Bottom section - buttons and indicators (fixed position) */}
+          <div class="flex flex-col gap-12">
+            <div class="flex flex-col items-start gap-3">
+              {isLast() ? (
+                <Button variant="primary" size="large" onClick={handleClose}>
+                  Get started
+                </Button>
+              ) : (
+                <Button variant="secondary" size="large" onClick={handleNext}>
+                  Next
+                </Button>
+              )}
+
+              <Button variant="ghost" size="small" onClick={handleDisable}>
+                Don't show these in the future
               </Button>
-            )}
-
-            <Button variant="ghost" size="small" onClick={handleDisable}>
-              Don't show these in the future
-            </Button>
-          </div>
+            </div>
 
-          {paged() && (
-            <div class="flex items-center gap-1.5 -my-2.5">
-              {props.highlights.map((_, i) => (
-                <button
-                  type="button"
-                  class="h-6 flex items-center cursor-pointer bg-transparent border-none p-0 transition-all duration-200"
-                  classList={{
-                    "w-8": i === index(),
-                    "w-3": i !== index(),
-                  }}
-                  onClick={() => setIndex(i)}
-                >
-                  <div
-                    class="w-full h-0.5 rounded-[1px] transition-colors duration-200"
+            {paged() && (
+              <div class="flex items-center gap-1.5 -my-2.5">
+                {props.highlights.map((_, i) => (
+                  <button
+                    type="button"
+                    class="h-6 flex items-center cursor-pointer bg-transparent border-none p-0 transition-all duration-200"
                     classList={{
-                      "bg-icon-strong-base": i === index(),
-                      "bg-icon-weak-base": i !== index(),
+                      "w-8": i === index(),
+                      "w-3": i !== index(),
                     }}
-                  />
-                </button>
-              ))}
-            </div>
-          )}
+                    onClick={() => setIndex(i)}
+                  >
+                    <div
+                      class="w-full h-0.5 rounded-[1px] transition-colors duration-200"
+                      classList={{
+                        "bg-icon-strong-base": i === index(),
+                        "bg-icon-weak-base": i !== index(),
+                      }}
+                    />
+                  </button>
+                ))}
+              </div>
+            )}
+          </div>
         </div>
-      </div>
 
-      {/* Right side - Media content (edge to edge) */}
-      {feature()?.media && (
-        <div class="flex-1 min-w-0 bg-surface-base overflow-hidden rounded-r-xl">
-          {feature()!.media!.type === "image" ? (
-            <img
-              src={feature()!.media!.src}
-              alt={feature()!.media!.alt ?? feature()?.title ?? "Release preview"}
-              class="w-full h-full object-cover"
-            />
-          ) : (
-            <video src={feature()!.media!.src} autoplay loop muted playsinline class="w-full h-full object-cover" />
-          )}
-        </div>
-      )}
+        {/* Right side - Media content (edge to edge) */}
+        {feature()?.media && (
+          <div class="flex-1 min-w-0 bg-surface-base overflow-hidden rounded-r-xl">
+            {feature()!.media!.type === "image" ? (
+              <img
+                src={feature()!.media!.src}
+                alt={feature()!.media!.alt ?? feature()?.title ?? "Release preview"}
+                class="w-full h-full object-cover"
+              />
+            ) : (
+              <video src={feature()!.media!.src} autoplay loop muted playsinline class="w-full h-full object-cover" />
+            )}
+          </div>
+        )}
+      </div>
     </Dialog>
   )
 }

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

@@ -59,18 +59,16 @@ function AddRow(props: AddRowProps) {
       <div class="flex-1 min-w-0 [&_[data-slot=input-wrapper]]:relative">
         <div
           classList={{
-            "size-1.5 rounded-full absolute left-3 z-10 pointer-events-none": true,
+            "size-1.5 rounded-full absolute left-3 top-1/2 -translate-y-1/2 z-10 pointer-events-none": true,
             "bg-icon-success-base": props.status === true,
             "bg-icon-critical-base": props.status === false,
             "bg-border-weak-base": props.status === undefined,
           }}
-          style={{ top: "50%", transform: "translateY(-50%)" }}
           ref={(el) => {
             // Position relative to input-wrapper
             requestAnimationFrame(() => {
               const wrapper = el.parentElement?.querySelector('[data-slot="input-wrapper"]')
               if (wrapper instanceof HTMLElement) {
-                wrapper.style.position = "relative"
                 wrapper.appendChild(el)
               }
             })

+ 2 - 8
packages/app/src/components/prompt-input.tsx

@@ -1746,10 +1746,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
                   <Tooltip
                     value={
                       <span class="flex max-w-[300px]">
-                        <span
-                          class="text-text-invert-base truncate min-w-0"
-                          style={{ direction: "rtl", "text-align": "left", "unicode-bidi": "plaintext" }}
-                        >
+                        <span class="text-text-invert-base truncate-start [unicode-bidi:plaintext] min-w-0">
                           {getDirectory(item.path)}
                         </span>
                         <span class="shrink-0">{getFilename(item.path)}</span>
@@ -1772,10 +1769,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" />
-                        <div
-                          class="flex items-center text-11-regular min-w-0"
-                          style={{ "font-weight": "var(--font-weight-medium)" }}
-                        >
+                        <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}>
                             {(sel) => (

+ 1 - 4
packages/app/src/components/session/session-new-view.tsx

@@ -45,10 +45,7 @@ export function NewSessionView(props: NewSessionViewProps) {
   }
 
   return (
-    <div
-      class="size-full flex flex-col pb-45 justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto px-6"
-      style={{ "padding-bottom": "calc(var(--prompt-height, 11.25rem) + 64px)" }}
-    >
+    <div class="size-full flex flex-col justify-end items-start gap-4 flex-[1_0_0] self-stretch max-w-200 mx-auto px-6 pb-[calc(var(--prompt-height,11.25rem)+64px)]">
       <div class="text-20-medium text-text-weaker">{language.t("command.session.new")}</div>
       <div class="flex justify-center items-center gap-3">
         <Icon name="folder" size="small" />

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

@@ -146,7 +146,7 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
             />
           }
         >
-          <span onDblClick={edit} style={{ visibility: store.editing ? "hidden" : "visible" }}>
+          <span onDblClick={edit} classList={{ invisible: store.editing }}>
             {label()}
           </span>
         </Tabs.Trigger>
@@ -167,8 +167,8 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
         <DropdownMenu open={store.menuOpen} onOpenChange={(open) => setStore("menuOpen", open)}>
           <DropdownMenu.Portal>
             <DropdownMenu.Content
+              class="fixed"
               style={{
-                position: "fixed",
                 left: `${store.menuPosition.x}px`,
                 top: `${store.menuPosition.y}px`,
               }}

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

@@ -67,14 +67,8 @@ export const SettingsGeneral: Component = () => {
   const soundOptions = [...SOUND_OPTIONS]
 
   return (
-    <div class="flex flex-col h-full overflow-y-auto no-scrollbar" style={{ padding: "0 40px 40px 40px" }}>
-      <div
-        class="sticky top-0 z-10"
-        style={{
-          background:
-            "linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
-        }}
-      >
+    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-10 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>
         </div>

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

@@ -352,14 +352,8 @@ export const SettingsKeybinds: Component = () => {
   })
 
   return (
-    <div class="flex flex-col h-full overflow-y-auto no-scrollbar" style={{ padding: "0 40px 40px 40px" }}>
-      <div
-        class="sticky top-0 z-10"
-        style={{
-          background:
-            "linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
-        }}
-      >
+    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-10 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">
             <h2 class="text-16-medium text-text-strong">{language.t("settings.shortcuts.title")}</h2>

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

@@ -39,14 +39,8 @@ export const SettingsModels: Component = () => {
   })
 
   return (
-    <div class="flex flex-col h-full overflow-y-auto no-scrollbar" style={{ padding: "0 40px 40px 40px" }}>
-      <div
-        class="sticky top-0 z-10"
-        style={{
-          background:
-            "linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
-        }}
-      >
+    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-10 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]">
           <h2 class="text-16-medium text-text-strong">{language.t("settings.models.title")}</h2>
           <div class="flex items-center gap-2 px-3 h-9 rounded-lg bg-surface-base">

+ 1 - 7
packages/app/src/components/settings-permissions.tsx

@@ -175,13 +175,7 @@ export const SettingsPermissions: Component = () => {
 
   return (
     <div class="flex flex-col h-full overflow-y-auto no-scrollbar">
-      <div
-        class="sticky top-0 z-10"
-        style={{
-          background:
-            "linear-gradient(to bottom, var(--surface-raised-stronger-non-alpha) calc(100% - 24px), transparent)",
-        }}
-      >
+      <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 p-8 max-w-[720px]">
           <h2 class="text-16-medium text-text-strong">{language.t("settings.permissions.title")}</h2>
           <p class="text-14-regular text-text-weak">{language.t("settings.permissions.description")}</p>

+ 1 - 1
packages/app/src/components/settings-providers.tsx

@@ -68,7 +68,7 @@ export const SettingsProviders: Component = () => {
   }
 
   return (
-    <div class="flex flex-col h-full overflow-y-auto no-scrollbar" style={{ padding: "0 40px 40px 40px" }}>
+    <div class="flex flex-col h-full overflow-y-auto no-scrollbar px-10 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 max-w-[720px]">
           <h2 class="text-16-medium text-text-strong">{language.t("settings.providers.title")}</h2>

+ 3 - 20
packages/app/src/components/status-popover.tsx

@@ -176,33 +176,16 @@ export function StatusPopover() {
       placement="bottom-end"
       shift={-136}
     >
-      <div
-        class="flex items-center gap-1 w-[360px] rounded-xl"
-        style={{ "box-shadow": "var(--shadow-lg-border-base)" }}
-      >
+      <div class="flex items-center gap-1 w-[360px] rounded-xl shadow-[var(--shadow-lg-border-base)]">
         <Tabs
           aria-label={language.t("status.popover.ariaLabel")}
-          class="tabs"
+          class="tabs bg-background-strong rounded-xl overflow-hidden"
           data-component="tabs"
           data-active="servers"
           defaultValue="servers"
           variant="alt"
-          style={{
-            "background-color": "var(--background-strong)",
-            "border-radius": "12px",
-            overflow: "hidden",
-          }}
         >
-          <Tabs.List
-            data-slot="tablist"
-            style={{
-              "background-color": "transparent",
-              "border-bottom": "none",
-              padding: "8px 16px 0",
-              gap: "16px",
-              height: "40px",
-            }}
-          >
+          <Tabs.List data-slot="tablist" class="bg-transparent border-b-0 px-4 pt-2 pb-0 gap-4 h-10">
             <Tabs.Trigger value="servers" data-slot="tab" class="text-12-regular">
               {serverCount() > 0 ? `${serverCount()} ` : ""}
               {language.t("status.popover.tab.servers")}

+ 0 - 83
packages/app/src/index.css

@@ -1,84 +1 @@
 @import "@opencode-ai/ui/styles/tailwind";
-
-:root {
-  a {
-    cursor: default;
-  }
-}
-
-[data-component="markdown"] ul {
-  list-style: disc outside;
-  padding-left: 1.5rem;
-}
-
-[data-component="markdown"] ol {
-  list-style: decimal outside;
-  padding-left: 1.5rem;
-}
-
-[data-component="markdown"] li > p:first-child {
-  display: inline;
-  margin: 0;
-}
-
-[data-component="markdown"] li > p + p {
-  display: block;
-  margin-top: 0.5rem;
-}
-
-*[data-tauri-drag-region] {
-  app-region: drag;
-}
-
-.session-scroller::-webkit-scrollbar {
-  width: 10px !important;
-  height: 10px !important;
-}
-
-.session-scroller::-webkit-scrollbar-track {
-  background: transparent !important;
-  border-radius: 5px !important;
-}
-
-.session-scroller::-webkit-scrollbar-thumb {
-  background: var(--border-weak-base) !important;
-  border-radius: 5px !important;
-  border: 3px solid transparent !important;
-  background-clip: padding-box !important;
-}
-
-.session-scroller::-webkit-scrollbar-thumb:hover {
-  background: var(--border-weak-base) !important;
-}
-
-.session-scroller {
-  scrollbar-width: thin !important;
-  scrollbar-color: var(--border-weak-base) transparent !important;
-}
-
-/* Wider dialog variant for release notes modal */
-[data-component="dialog"]:has(.dialog-release-notes) {
-  padding: 20px;
-  box-sizing: border-box;
-
-  [data-slot="dialog-container"] {
-    width: min(100%, 720px);
-    height: min(100%, 400px);
-    margin-top: -80px;
-
-    [data-slot="dialog-content"] {
-      min-height: auto;
-      overflow: hidden;
-      height: 100%;
-      border: none;
-      box-shadow: var(--shadow-lg-border-base);
-    }
-
-    [data-slot="dialog-body"] {
-      overflow: hidden;
-      height: 100%;
-      display: flex;
-      flex-direction: row;
-    }
-  }
-}

+ 3 - 10
packages/app/src/pages/layout.tsx

@@ -1563,7 +1563,6 @@ export default function Layout(props: ParentProps) {
     const notifications = createMemo(() => notification.project.unseen(props.project.worktree))
     const hasError = createMemo(() => notifications().some((n) => n.type === "error"))
     const name = createMemo(() => props.project.name || getFilename(props.project.worktree))
-    const mask = "radial-gradient(circle 5px at calc(100% - 4px) 4px, transparent 5px, black 5.5px)"
     const opencode = "4b0ea68d7af9a6031a7ffda7ad66e0cb83315750"
 
     return (
@@ -1574,11 +1573,7 @@ export default function Layout(props: ParentProps) {
             src={props.project.id === opencode ? "https://opencode.ai/favicon.svg" : props.project.icon?.override}
             {...getAvatarColors(props.project.icon?.color)}
             class="size-full rounded"
-            style={
-              notifications().length > 0 && props.notify
-                ? { "-webkit-mask-image": mask, "mask-image": mask }
-                : undefined
-            }
+            classList={{ "badge-mask": notifications().length > 0 && props.notify }}
           />
         </div>
         <Show when={notifications().length > 0 && props.notify}>
@@ -2348,8 +2343,7 @@ export default function Layout(props: ParentProps) {
         ref={(el) => {
           if (!props.mobile) scrollContainerRef = el
         }}
-        class="size-full flex flex-col py-2 overflow-y-auto no-scrollbar"
-        style={{ "overflow-anchor": "none" }}
+        class="size-full flex flex-col py-2 overflow-y-auto no-scrollbar [overflow-anchor:none]"
       >
         <nav class="flex flex-col gap-1 px-2">
           <Show when={loading()}>
@@ -2575,8 +2569,7 @@ export default function Layout(props: ParentProps) {
                         ref={(el) => {
                           if (!panelProps.mobile) scrollContainerRef = el
                         }}
-                        class="size-full flex flex-col py-2 gap-4 overflow-y-auto no-scrollbar"
-                        style={{ "overflow-anchor": "none" }}
+                        class="size-full flex flex-col py-2 gap-4 overflow-y-auto no-scrollbar [overflow-anchor:none]"
                       >
                         <SortableProvider ids={workspaces()}>
                           <For each={workspaces()}>

+ 8 - 0
packages/ui/src/components/avatar.css

@@ -39,3 +39,11 @@
   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;
+}

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

@@ -37,7 +37,7 @@ export function Avatar(props: AvatarProps) {
       }}
     >
       <Show when={src} fallback={split.fallback?.[0]}>
-        {(src) => <img src={src()} draggable={false} class="size-full object-cover rounded-[inherit]" />}
+        {(src) => <img src={src()} draggable={false} data-slot="avatar-image" />}
       </Show>
     </div>
   )

+ 8 - 0
packages/ui/src/components/message-part.css

@@ -81,6 +81,14 @@
     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;

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

@@ -477,20 +477,7 @@ function HighlightedText(props: { text: string; references: FilePart[]; agents:
     return result
   })
 
-  return (
-    <For each={segments()}>
-      {(segment) => (
-        <span
-          classList={{
-            "text-syntax-property": segment.type === "file",
-            "text-syntax-type": segment.type === "agent",
-          }}
-        >
-          {segment.text}
-        </span>
-      )}
-    </For>
-  )
+  return <For each={segments()}>{(segment) => <span data-highlight={segment.type}>{segment.text}</span>}</For>
 }
 
 export function Part(props: MessagePartProps) {

+ 4 - 0
packages/ui/src/components/session-turn.css

@@ -499,6 +499,10 @@
     gap: 8px;
     color: var(--text-weak);
 
+    [data-slot="session-turn-trigger-icon"] {
+      color: var(--icon-base);
+    }
+
     [data-component="spinner"] {
       width: 12px;
       height: 12px;

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

@@ -565,7 +565,7 @@ export function SessionTurn(
                                   viewBox="0 0 10 10"
                                   fill="none"
                                   xmlns="http://www.w3.org/2000/svg"
-                                  class="text-icon-base"
+                                  data-slot="session-turn-trigger-icon"
                                 >
                                   <path
                                     d="M8.125 1.875H1.875L5 8.125L8.125 1.875Z"

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

@@ -75,7 +75,7 @@ function TabsTrigger(props: ParentProps<TabsTriggerProps>) {
       <Kobalte.Trigger
         {...rest}
         data-slot="tabs-trigger"
-        classList={{ "group/tab": true, [split.classes?.button ?? ""]: split.classes?.button }}
+        classList={{ [split.classes?.button ?? ""]: split.classes?.button }}
       >
         {split.children}
       </Kobalte.Trigger>

+ 33 - 0
packages/ui/src/styles/tailwind/utilities.css

@@ -8,6 +8,39 @@
   }
 }
 
+@utility session-scroller {
+  &::-webkit-scrollbar {
+    width: 10px;
+    height: 10px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: transparent;
+    border-radius: 5px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: var(--border-weak-base);
+    border-radius: 5px;
+    border: 3px solid transparent;
+    background-clip: padding-box;
+  }
+
+  &::-webkit-scrollbar-thumb:hover {
+    background: var(--border-weak-base);
+  }
+
+  & {
+    scrollbar-width: thin;
+    scrollbar-color: var(--border-weak-base) transparent;
+  }
+}
+
+@utility badge-mask {
+  -webkit-mask-image: radial-gradient(circle 5px at calc(100% - 4px) 4px, transparent 5px, black 5.5px);
+  mask-image: radial-gradient(circle 5px at calc(100% - 4px) 4px, transparent 5px, black 5.5px);
+}
+
 @utility truncate-start {
   text-overflow: ellipsis;
   overflow: hidden;