summaryrefslogtreecommitdiffhomepage
path: root/packages/ui/src/components
diff options
context:
space:
mode:
authorShoubhit Dash <[email protected]>2026-04-16 17:30:14 +0530
committerShoubhit Dash <[email protected]>2026-04-16 17:30:14 +0530
commit2e18a603f0ea24154908e748493fd4bfaa74fc00 (patch)
treeb723e00af2821b213573b16fa199b64babd5f1f5 /packages/ui/src/components
parent9819eb04614fd607cacb07d754052f1531a82331 (diff)
parent7341718f9234b0cf3a8758c87e91d2006b71bff6 (diff)
downloadopencode-2e18a603f0ea24154908e748493fd4bfaa74fc00.tar.gz
opencode-2e18a603f0ea24154908e748493fd4bfaa74fc00.zip
merge dev
Diffstat (limited to 'packages/ui/src/components')
-rw-r--r--packages/ui/src/components/accordion.css4
-rw-r--r--packages/ui/src/components/accordion.tsx10
-rw-r--r--packages/ui/src/components/app-icon.tsx2
-rw-r--r--packages/ui/src/components/avatar.tsx2
-rw-r--r--packages/ui/src/components/basic-tool.tsx2
-rw-r--r--packages/ui/src/components/button.tsx2
-rw-r--r--packages/ui/src/components/card.tsx8
-rw-r--r--packages/ui/src/components/collapsible.css2
-rw-r--r--packages/ui/src/components/collapsible.tsx2
-rw-r--r--packages/ui/src/components/context-menu.tsx32
-rw-r--r--packages/ui/src/components/dialog.tsx2
-rw-r--r--packages/ui/src/components/dock-surface.tsx6
-rw-r--r--packages/ui/src/components/dropdown-menu.tsx32
-rw-r--r--packages/ui/src/components/file-icon.tsx2
-rw-r--r--packages/ui/src/components/file-ssr.tsx4
-rw-r--r--packages/ui/src/components/file.tsx7
-rw-r--r--packages/ui/src/components/hover-card.tsx2
-rw-r--r--packages/ui/src/components/icon-button.tsx2
-rw-r--r--packages/ui/src/components/icon.tsx2
-rw-r--r--packages/ui/src/components/keybind.tsx2
-rw-r--r--packages/ui/src/components/line-comment.tsx2
-rw-r--r--packages/ui/src/components/list.tsx2
-rw-r--r--packages/ui/src/components/markdown.css30
-rw-r--r--packages/ui/src/components/markdown.tsx6
-rw-r--r--packages/ui/src/components/message-part.css19
-rw-r--r--packages/ui/src/components/message-part.tsx7
-rw-r--r--packages/ui/src/components/popover.tsx2
-rw-r--r--packages/ui/src/components/progress-circle.tsx2
-rw-r--r--packages/ui/src/components/progress.tsx2
-rw-r--r--packages/ui/src/components/provider-icon.tsx2
-rw-r--r--packages/ui/src/components/radio-group.tsx2
-rw-r--r--packages/ui/src/components/resize-handle.tsx2
-rw-r--r--packages/ui/src/components/select.tsx6
-rw-r--r--packages/ui/src/components/session-diff.ts75
-rw-r--r--packages/ui/src/components/session-review.tsx23
-rw-r--r--packages/ui/src/components/session-turn.css5
-rw-r--r--packages/ui/src/components/session-turn.tsx7
-rw-r--r--packages/ui/src/components/spinner.tsx2
-rw-r--r--packages/ui/src/components/sticky-accordion-header.tsx2
-rw-r--r--packages/ui/src/components/tabs.tsx8
-rw-r--r--packages/ui/src/components/tag.tsx2
-rw-r--r--packages/ui/src/components/text-field.tsx2
-rw-r--r--packages/ui/src/components/text-reveal.tsx2
-rw-r--r--packages/ui/src/components/thinking-heading.stories.tsx2
-rw-r--r--packages/ui/src/components/timeline-playground.stories.tsx48
-rw-r--r--packages/ui/src/components/toast.tsx2
-rw-r--r--packages/ui/src/components/tool-error-card.tsx2
-rw-r--r--packages/ui/src/components/tool-status-title.tsx2
-rw-r--r--packages/ui/src/components/tooltip.tsx12
49 files changed, 232 insertions, 175 deletions
diff --git a/packages/ui/src/components/accordion.css b/packages/ui/src/components/accordion.css
index b4d6323d0..cebb887f1 100644
--- a/packages/ui/src/components/accordion.css
+++ b/packages/ui/src/components/accordion.css
@@ -51,10 +51,10 @@
line-height: var(--line-height-large); /* 166.667% */
letter-spacing: var(--letter-spacing-normal);
- &:hover {
+ &:hover:not([data-disabled]) {
background-color: var(--surface-base-hover);
}
- &:active {
+ &:active:not([data-disabled]) {
background-color: var(--surface-base-active);
}
&:focus-visible {
diff --git a/packages/ui/src/components/accordion.tsx b/packages/ui/src/components/accordion.tsx
index 535d38e3d..3179b8a15 100644
--- a/packages/ui/src/components/accordion.tsx
+++ b/packages/ui/src/components/accordion.tsx
@@ -15,7 +15,7 @@ function AccordionRoot(props: AccordionProps) {
{...rest}
data-component="accordion"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
/>
@@ -29,7 +29,7 @@ function AccordionItem(props: AccordionItemProps) {
{...rest}
data-slot="accordion-item"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
/>
@@ -43,7 +43,7 @@ function AccordionHeader(props: ParentProps<AccordionHeaderProps>) {
{...rest}
data-slot="accordion-header"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
@@ -59,7 +59,7 @@ function AccordionTrigger(props: ParentProps<AccordionTriggerProps>) {
{...rest}
data-slot="accordion-trigger"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
@@ -75,7 +75,7 @@ function AccordionContent(props: ParentProps<AccordionContentProps>) {
{...rest}
data-slot="accordion-content"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
diff --git a/packages/ui/src/components/app-icon.tsx b/packages/ui/src/components/app-icon.tsx
index f8b587ff2..541dfc570 100644
--- a/packages/ui/src/components/app-icon.tsx
+++ b/packages/ui/src/components/app-icon.tsx
@@ -77,7 +77,7 @@ export const AppIcon: Component<AppIconProps> = (props) => {
alt={local.alt ?? ""}
draggable={local.draggable ?? false}
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
/>
diff --git a/packages/ui/src/components/avatar.tsx b/packages/ui/src/components/avatar.tsx
index c1617b265..035c2d304 100644
--- a/packages/ui/src/components/avatar.tsx
+++ b/packages/ui/src/components/avatar.tsx
@@ -38,7 +38,7 @@ export function Avatar(props: AvatarProps) {
data-size={split.size || "normal"}
data-has-image={src ? "" : undefined}
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
style={{
diff --git a/packages/ui/src/components/basic-tool.tsx b/packages/ui/src/components/basic-tool.tsx
index 7d18dfacd..27ad7c3c7 100644
--- a/packages/ui/src/components/basic-tool.tsx
+++ b/packages/ui/src/components/basic-tool.tsx
@@ -100,7 +100,7 @@ export function BasicTool(props: BasicToolProps) {
if (isOpen) {
contentRef.style.overflow = "hidden"
heightAnim = animate(contentRef, { height: "auto" }, SPRING)
- heightAnim.finished.then(() => {
+ void heightAnim.finished.then(() => {
if (!contentRef || !open()) return
contentRef.style.overflow = "visible"
contentRef.style.height = "auto"
diff --git a/packages/ui/src/components/button.tsx b/packages/ui/src/components/button.tsx
index 7f974b2f7..d1652145f 100644
--- a/packages/ui/src/components/button.tsx
+++ b/packages/ui/src/components/button.tsx
@@ -20,7 +20,7 @@ export function Button(props: ButtonProps) {
data-variant={split.variant || "secondary"}
data-icon={split.icon}
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
diff --git a/packages/ui/src/components/card.tsx b/packages/ui/src/components/card.tsx
index 7a1bd5e45..320aba718 100644
--- a/packages/ui/src/components/card.tsx
+++ b/packages/ui/src/components/card.tsx
@@ -53,7 +53,7 @@ export function Card(props: CardProps) {
data-variant={variant()}
style={mix(split.style, accent())}
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
@@ -76,7 +76,7 @@ export function CardTitle(props: CardTitleProps) {
{...rest}
data-slot="card-title"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
@@ -97,7 +97,7 @@ export function CardDescription(props: ComponentProps<"div">) {
{...rest}
data-slot="card-description"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
@@ -113,7 +113,7 @@ export function CardActions(props: ComponentProps<"div">) {
{...rest}
data-slot="card-actions"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
diff --git a/packages/ui/src/components/collapsible.css b/packages/ui/src/components/collapsible.css
index 608ca6e0e..82c133f73 100644
--- a/packages/ui/src/components/collapsible.css
+++ b/packages/ui/src/components/collapsible.css
@@ -9,7 +9,7 @@
overflow: visible;
&.tool-collapsible {
- --tool-content-gap: 8px;
+ --tool-content-gap: 4px;
gap: var(--tool-content-gap);
}
diff --git a/packages/ui/src/components/collapsible.tsx b/packages/ui/src/components/collapsible.tsx
index 8b5cd825c..b2a603264 100644
--- a/packages/ui/src/components/collapsible.tsx
+++ b/packages/ui/src/components/collapsible.tsx
@@ -15,7 +15,7 @@ function CollapsibleRoot(props: CollapsibleProps) {
data-component="collapsible"
data-variant={local.variant || "normal"}
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
{...others}
diff --git a/packages/ui/src/components/context-menu.tsx b/packages/ui/src/components/context-menu.tsx
index afdaff7b8..f4566a17a 100644
--- a/packages/ui/src/components/context-menu.tsx
+++ b/packages/ui/src/components/context-menu.tsx
@@ -33,7 +33,7 @@ function ContextMenuTrigger(props: ParentProps<ContextMenuTriggerProps>) {
{...rest}
data-slot="context-menu-trigger"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -49,7 +49,7 @@ function ContextMenuIcon(props: ParentProps<ContextMenuIconProps>) {
{...rest}
data-slot="context-menu-icon"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -69,7 +69,7 @@ function ContextMenuContent(props: ParentProps<ContextMenuContentProps>) {
{...rest}
data-component="context-menu-content"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -85,7 +85,7 @@ function ContextMenuArrow(props: ContextMenuArrowProps) {
{...rest}
data-slot="context-menu-arrow"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
/>
@@ -99,7 +99,7 @@ function ContextMenuSeparator(props: ContextMenuSeparatorProps) {
{...rest}
data-slot="context-menu-separator"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
/>
@@ -113,7 +113,7 @@ function ContextMenuGroup(props: ParentProps<ContextMenuGroupProps>) {
{...rest}
data-slot="context-menu-group"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -129,7 +129,7 @@ function ContextMenuGroupLabel(props: ParentProps<ContextMenuGroupLabelProps>) {
{...rest}
data-slot="context-menu-group-label"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -145,7 +145,7 @@ function ContextMenuItem(props: ParentProps<ContextMenuItemProps>) {
{...rest}
data-slot="context-menu-item"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -161,7 +161,7 @@ function ContextMenuItemLabel(props: ParentProps<ContextMenuItemLabelProps>) {
{...rest}
data-slot="context-menu-item-label"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -177,7 +177,7 @@ function ContextMenuItemDescription(props: ParentProps<ContextMenuItemDescriptio
{...rest}
data-slot="context-menu-item-description"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -193,7 +193,7 @@ function ContextMenuItemIndicator(props: ParentProps<ContextMenuItemIndicatorPro
{...rest}
data-slot="context-menu-item-indicator"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -209,7 +209,7 @@ function ContextMenuRadioGroup(props: ParentProps<ContextMenuRadioGroupProps>) {
{...rest}
data-slot="context-menu-radio-group"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -225,7 +225,7 @@ function ContextMenuRadioItem(props: ParentProps<ContextMenuRadioItemProps>) {
{...rest}
data-slot="context-menu-radio-item"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -241,7 +241,7 @@ function ContextMenuCheckboxItem(props: ParentProps<ContextMenuCheckboxItemProps
{...rest}
data-slot="context-menu-checkbox-item"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -261,7 +261,7 @@ function ContextMenuSubTrigger(props: ParentProps<ContextMenuSubTriggerProps>) {
{...rest}
data-slot="context-menu-sub-trigger"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -277,7 +277,7 @@ function ContextMenuSubContent(props: ParentProps<ContextMenuSubContentProps>) {
{...rest}
data-component="context-menu-sub-content"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
diff --git a/packages/ui/src/components/dialog.tsx b/packages/ui/src/components/dialog.tsx
index ce7704f37..981e3f45d 100644
--- a/packages/ui/src/components/dialog.tsx
+++ b/packages/ui/src/components/dialog.tsx
@@ -28,7 +28,7 @@ export function Dialog(props: DialogProps) {
data-slot="dialog-content"
data-no-header={!props.title && !props.action ? "" : undefined}
classList={{
- ...(props.classList ?? {}),
+ ...props.classList,
[props.class ?? ""]: !!props.class,
}}
onOpenAutoFocus={(e) => {
diff --git a/packages/ui/src/components/dock-surface.tsx b/packages/ui/src/components/dock-surface.tsx
index 1c4af2ed5..06cf2a5eb 100644
--- a/packages/ui/src/components/dock-surface.tsx
+++ b/packages/ui/src/components/dock-surface.tsx
@@ -11,7 +11,7 @@ export function DockShell(props: ComponentProps<"div">) {
{...rest}
data-dock-surface="shell"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
@@ -27,7 +27,7 @@ export function DockShellForm(props: ComponentProps<"form">) {
{...rest}
data-dock-surface="shell"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
@@ -44,7 +44,7 @@ export function DockTray(props: DockTrayProps) {
data-dock-surface="tray"
data-dock-attach={split.attach || "none"}
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx
index efb2b45ca..259cb791a 100644
--- a/packages/ui/src/components/dropdown-menu.tsx
+++ b/packages/ui/src/components/dropdown-menu.tsx
@@ -33,7 +33,7 @@ function DropdownMenuTrigger(props: ParentProps<DropdownMenuTriggerProps>) {
{...rest}
data-slot="dropdown-menu-trigger"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -49,7 +49,7 @@ function DropdownMenuIcon(props: ParentProps<DropdownMenuIconProps>) {
{...rest}
data-slot="dropdown-menu-icon"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -69,7 +69,7 @@ function DropdownMenuContent(props: ParentProps<DropdownMenuContentProps>) {
{...rest}
data-component="dropdown-menu-content"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -85,7 +85,7 @@ function DropdownMenuArrow(props: DropdownMenuArrowProps) {
{...rest}
data-slot="dropdown-menu-arrow"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
/>
@@ -99,7 +99,7 @@ function DropdownMenuSeparator(props: DropdownMenuSeparatorProps) {
{...rest}
data-slot="dropdown-menu-separator"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
/>
@@ -113,7 +113,7 @@ function DropdownMenuGroup(props: ParentProps<DropdownMenuGroupProps>) {
{...rest}
data-slot="dropdown-menu-group"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -129,7 +129,7 @@ function DropdownMenuGroupLabel(props: ParentProps<DropdownMenuGroupLabelProps>)
{...rest}
data-slot="dropdown-menu-group-label"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -145,7 +145,7 @@ function DropdownMenuItem(props: ParentProps<DropdownMenuItemProps>) {
{...rest}
data-slot="dropdown-menu-item"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -161,7 +161,7 @@ function DropdownMenuItemLabel(props: ParentProps<DropdownMenuItemLabelProps>) {
{...rest}
data-slot="dropdown-menu-item-label"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -177,7 +177,7 @@ function DropdownMenuItemDescription(props: ParentProps<DropdownMenuItemDescript
{...rest}
data-slot="dropdown-menu-item-description"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -193,7 +193,7 @@ function DropdownMenuItemIndicator(props: ParentProps<DropdownMenuItemIndicatorP
{...rest}
data-slot="dropdown-menu-item-indicator"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -209,7 +209,7 @@ function DropdownMenuRadioGroup(props: ParentProps<DropdownMenuRadioGroupProps>)
{...rest}
data-slot="dropdown-menu-radio-group"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -225,7 +225,7 @@ function DropdownMenuRadioItem(props: ParentProps<DropdownMenuRadioItemProps>) {
{...rest}
data-slot="dropdown-menu-radio-item"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -241,7 +241,7 @@ function DropdownMenuCheckboxItem(props: ParentProps<DropdownMenuCheckboxItemPro
{...rest}
data-slot="dropdown-menu-checkbox-item"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -261,7 +261,7 @@ function DropdownMenuSubTrigger(props: ParentProps<DropdownMenuSubTriggerProps>)
{...rest}
data-slot="dropdown-menu-sub-trigger"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -277,7 +277,7 @@ function DropdownMenuSubContent(props: ParentProps<DropdownMenuSubContentProps>)
{...rest}
data-component="dropdown-menu-sub-content"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
diff --git a/packages/ui/src/components/file-icon.tsx b/packages/ui/src/components/file-icon.tsx
index 133cb169c..d66ee1c25 100644
--- a/packages/ui/src/components/file-icon.tsx
+++ b/packages/ui/src/components/file-icon.tsx
@@ -18,7 +18,7 @@ export const FileIcon: Component<FileIconProps> = (props) => {
data-component="file-icon"
{...rest}
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
diff --git a/packages/ui/src/components/file-ssr.tsx b/packages/ui/src/components/file-ssr.tsx
index fed5c8931..ad05555bd 100644
--- a/packages/ui/src/components/file-ssr.tsx
+++ b/packages/ui/src/components/file-ssr.tsx
@@ -99,7 +99,7 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
{
...createDefaultOptions(props.diffStyle),
...others,
- ...(local.preloadedDiff.options ?? {}),
+ ...local.preloadedDiff.options,
},
virtualizer,
virtualMetrics,
@@ -109,7 +109,7 @@ function DiffSSRViewer<T>(props: SSRDiffFileProps<T>) {
{
...createDefaultOptions(props.diffStyle),
...others,
- ...(local.preloadedDiff.options ?? {}),
+ ...local.preloadedDiff.options,
},
workerPool,
)
diff --git a/packages/ui/src/components/file.tsx b/packages/ui/src/components/file.tsx
index b78f0bae4..633b23b70 100644
--- a/packages/ui/src/components/file.tsx
+++ b/packages/ui/src/components/file.tsx
@@ -1,4 +1,4 @@
-import { sampledChecksum } from "@opencode-ai/util/encode"
+import { sampledChecksum } from "@opencode-ai/shared/util/encode"
import {
DEFAULT_VIRTUAL_FILE_METRICS,
type DiffLineAnnotation,
@@ -655,7 +655,7 @@ function ViewerShell(props: {
style={styleVariables}
class="relative outline-none"
classList={{
- ...(props.classList || {}),
+ ...props.classList,
[props.class ?? ""]: !!props.class,
}}
ref={(el) => (props.viewer.wrapper = el)}
@@ -698,6 +698,7 @@ function TextViewer<T>(props: TextFileProps<T>) {
if (typeof value === "string") return value
if (Array.isArray(value)) return value.join("\n")
if (value == null) return ""
+ // oxlint-disable-next-line no-base-to-string -- file contents cast to unknown, coercion is intentional
return String(value)
}
@@ -712,11 +713,13 @@ function TextViewer<T>(props: TextFileProps<T>) {
if (typeof value === "string") return value.length
if (Array.isArray(value)) {
return value.reduce(
+ // oxlint-disable-next-line no-base-to-string -- array parts coerced intentionally
(sum, part) => sum + (typeof part === "string" ? part.length + 1 : String(part).length + 1),
0,
)
}
if (value == null) return 0
+ // oxlint-disable-next-line no-base-to-string -- file contents cast to unknown, coercion is intentional
return String(value).length
})
diff --git a/packages/ui/src/components/hover-card.tsx b/packages/ui/src/components/hover-card.tsx
index 8330375aa..4e6647313 100644
--- a/packages/ui/src/components/hover-card.tsx
+++ b/packages/ui/src/components/hover-card.tsx
@@ -20,7 +20,7 @@ export function HoverCard(props: HoverCardProps) {
<Kobalte.Content
data-component="hover-card-content"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
diff --git a/packages/ui/src/components/icon-button.tsx b/packages/ui/src/components/icon-button.tsx
index 89ab00fcd..457283aa0 100644
--- a/packages/ui/src/components/icon-button.tsx
+++ b/packages/ui/src/components/icon-button.tsx
@@ -19,7 +19,7 @@ export function IconButton(props: ComponentProps<"button"> & IconButtonProps) {
data-size={split.size || "normal"}
data-variant={split.variant || "secondary"}
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx
index e2eaf107a..08726d0ff 100644
--- a/packages/ui/src/components/icon.tsx
+++ b/packages/ui/src/components/icon.tsx
@@ -117,7 +117,7 @@ export function Icon(props: IconProps) {
<svg
data-slot="icon-svg"
classList={{
- ...(local.classList || {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
fill="none"
diff --git a/packages/ui/src/components/keybind.tsx b/packages/ui/src/components/keybind.tsx
index a0fa0483f..5c347cb54 100644
--- a/packages/ui/src/components/keybind.tsx
+++ b/packages/ui/src/components/keybind.tsx
@@ -10,7 +10,7 @@ export function Keybind(props: KeybindProps) {
<span
data-component="keybind"
classList={{
- ...(props.classList ?? {}),
+ ...props.classList,
[props.class ?? ""]: !!props.class,
}}
>
diff --git a/packages/ui/src/components/line-comment.tsx b/packages/ui/src/components/line-comment.tsx
index 26e763bb3..e20da5a8d 100644
--- a/packages/ui/src/components/line-comment.tsx
+++ b/packages/ui/src/components/line-comment.tsx
@@ -1,5 +1,5 @@
import { useFilteredList } from "@opencode-ai/ui/hooks"
-import { getDirectory, getFilename } from "@opencode-ai/util/path"
+import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
import { createSignal, For, onMount, Show, splitProps, type JSX } from "solid-js"
import { Button } from "./button"
import { FileIcon } from "./file-icon"
diff --git a/packages/ui/src/components/list.tsx b/packages/ui/src/components/list.tsx
index b5879624e..cc5fc0ce5 100644
--- a/packages/ui/src/components/list.tsx
+++ b/packages/ui/src/components/list.tsx
@@ -107,7 +107,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
// Force a refetch even if the value is unchanged.
// This is important for programmatic changes like Tab completion.
if (prev === value) {
- refetch()
+ void refetch()
return
}
queueMicrotask(() => refetch())
diff --git a/packages/ui/src/components/markdown.css b/packages/ui/src/components/markdown.css
index f82723807..26c9efd47 100644
--- a/packages/ui/src/components/markdown.css
+++ b/packages/ui/src/components/markdown.css
@@ -6,7 +6,7 @@
color: var(--text-strong);
font-family: var(--font-family-sans);
font-size: var(--font-size-base); /* 14px */
- line-height: var(--line-height-x-large);
+ line-height: 160%;
/* Spacing for flow */
> *:first-child {
@@ -23,11 +23,11 @@
h4,
h5,
h6 {
- font-size: var(--font-size-base);
+ font-size: 14px;
color: var(--text-strong);
font-weight: var(--font-weight-medium);
- margin-top: 2rem;
- margin-bottom: 0.75rem;
+ margin-top: 0px;
+ margin-bottom: 24px;
line-height: var(--line-height-large);
}
@@ -40,7 +40,7 @@
/* Paragraphs */
p {
- margin-bottom: 1rem;
+ margin-bottom: 12px;
}
/* Links */
@@ -58,10 +58,10 @@
/* Lists */
ul,
ol {
- margin-top: 0.5rem;
- margin-bottom: 1rem;
+ margin-top: 8px;
+ margin-bottom: 12px;
margin-left: 0;
- padding-left: 1.5rem;
+ padding-left: 32px;
list-style-position: outside;
}
@@ -75,7 +75,7 @@
}
li {
- margin-bottom: 0.5rem;
+ margin-bottom: 8px;
}
li > p:first-child {
@@ -117,12 +117,12 @@
hr {
border: none;
height: 0;
- margin: 2.5rem 0;
+ margin: 40px 0;
}
.shiki {
font-size: 13px;
- padding: 8px 12px;
+ padding: 12px;
border-radius: 6px;
border: 0.5px solid var(--border-weak-base);
}
@@ -201,8 +201,8 @@
}
pre {
- margin-top: 2rem;
- margin-bottom: 2rem;
+ margin-top: 12px;
+ margin-bottom: 32px;
overflow: auto;
scrollbar-width: none;
@@ -229,7 +229,7 @@
table {
width: 100%;
border-collapse: collapse;
- margin: 1.5rem 0;
+ margin: 24px 0;
font-size: var(--font-size-base);
display: block;
overflow-x: auto;
@@ -239,7 +239,7 @@
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;
+ padding: 12px;
text-align: left;
vertical-align: top;
}
diff --git a/packages/ui/src/components/markdown.tsx b/packages/ui/src/components/markdown.tsx
index ceab10df9..28653512e 100644
--- a/packages/ui/src/components/markdown.tsx
+++ b/packages/ui/src/components/markdown.tsx
@@ -2,7 +2,7 @@ import { useMarked } from "../context/marked"
import { useI18n } from "../context/i18n"
import DOMPurify from "dompurify"
import morphdom from "morphdom"
-import { checksum } from "@opencode-ai/util/encode"
+import { checksum } from "@opencode-ai/shared/util/encode"
import { ComponentProps, createEffect, createResource, createSignal, onCleanup, splitProps } from "solid-js"
import { isServer } from "solid-js/web"
import { stream } from "./markdown-stream"
@@ -50,7 +50,7 @@ function escape(text: string) {
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
- .replace(/\"/g, "&quot;")
+ .replace(/"/g, "&quot;")
.replace(/'/g, "&#39;")
}
@@ -338,7 +338,7 @@ export function Markdown(
<div
data-component="markdown"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
ref={setRoot}
diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css
index d9893503f..c84a36892 100644
--- a/packages/ui/src/components/message-part.css
+++ b/packages/ui/src/components/message-part.css
@@ -283,9 +283,9 @@
line-height: var(--line-height-normal);
[data-component="markdown"] {
- margin-top: 24px;
+ margin-top: 16px;
font-style: normal;
- font-size: var(--font-size-base);
+ font-size: 13px;
color: var(--text-weak);
strong,
@@ -556,9 +556,12 @@
[data-component="exa-tool-output"] {
width: 100%;
- padding-top: 8px;
display: flex;
flex-direction: column;
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-base);
+ line-height: var(--line-height-large);
+ color: var(--text-base);
}
[data-slot="basic-tool-tool-subtitle"].exa-tool-query {
@@ -578,6 +581,8 @@
[data-slot="exa-tool-link"] {
display: block;
max-width: 100%;
+ font: inherit;
+ line-height: inherit;
color: var(--text-interactive-base);
text-decoration: underline;
text-underline-offset: 2px;
@@ -636,13 +641,13 @@
}
[data-component="context-tool-group-list"] {
- padding-top: 6px;
+ padding-top: 0;
padding-right: 0;
- padding-bottom: 4px;
- padding-left: 13px;
+ padding-bottom: 0;
+ padding-left: 12px;
display: flex;
flex-direction: column;
- gap: 8px;
+ gap: 4px;
[data-slot="context-tool-group-item"] {
min-width: 0;
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index 02bd80ac9..a47ff1804 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -37,7 +37,6 @@ import { type UiI18n, useI18n } from "../context/i18n"
import { BasicTool, GenericTool } from "./basic-tool"
import { Accordion } from "./accordion"
import { StickyAccordionHeader } from "./sticky-accordion-header"
-import { Card } from "./card"
import { Collapsible } from "./collapsible"
import { FileIcon } from "./file-icon"
import { Icon } from "./icon"
@@ -46,8 +45,8 @@ import { Checkbox } from "./checkbox"
import { DiffChanges } from "./diff-changes"
import { Markdown } from "./markdown"
import { ImagePreview } from "./image-preview"
-import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/util/path"
-import { checksum } from "@opencode-ai/util/encode"
+import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/shared/util/path"
+import { checksum } from "@opencode-ai/shared/util/encode"
import { Tooltip } from "./tooltip"
import { IconButton } from "./icon-button"
import { Spinner } from "./spinner"
@@ -1159,7 +1158,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
onMouseDown={(e) => e.preventDefault()}
onClick={(event) => {
event.stopPropagation()
- handleCopy()
+ void handleCopy()
}}
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.message.copyMessage")}
/>
diff --git a/packages/ui/src/components/popover.tsx b/packages/ui/src/components/popover.tsx
index 8263640a5..be5b78519 100644
--- a/packages/ui/src/components/popover.tsx
+++ b/packages/ui/src/components/popover.tsx
@@ -106,7 +106,7 @@ export function Popover<T extends ValidComponent = "div">(props: PopoverProps<T>
ref={(el: HTMLElement | undefined) => setState("contentRef", el)}
data-component="popover-content"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
style={local.style}
diff --git a/packages/ui/src/components/progress-circle.tsx b/packages/ui/src/components/progress-circle.tsx
index 02bd36bb7..992fb62e8 100644
--- a/packages/ui/src/components/progress-circle.tsx
+++ b/packages/ui/src/components/progress-circle.tsx
@@ -32,7 +32,7 @@ export function ProgressCircle(props: ProgressCircleProps) {
fill="none"
data-component="progress-circle"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
diff --git a/packages/ui/src/components/progress.tsx b/packages/ui/src/components/progress.tsx
index bfe10a1d1..7cbe5d6bc 100644
--- a/packages/ui/src/components/progress.tsx
+++ b/packages/ui/src/components/progress.tsx
@@ -15,7 +15,7 @@ export function Progress(props: ProgressProps) {
{...others}
data-component="progress"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
diff --git a/packages/ui/src/components/provider-icon.tsx b/packages/ui/src/components/provider-icon.tsx
index edfdd0357..7c0eb3d04 100644
--- a/packages/ui/src/components/provider-icon.tsx
+++ b/packages/ui/src/components/provider-icon.tsx
@@ -15,7 +15,7 @@ export const ProviderIcon: Component<ProviderIconProps> = (props) => {
data-component="provider-icon"
{...rest}
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
diff --git a/packages/ui/src/components/radio-group.tsx b/packages/ui/src/components/radio-group.tsx
index 544e852e4..9151a24b0 100644
--- a/packages/ui/src/components/radio-group.tsx
+++ b/packages/ui/src/components/radio-group.tsx
@@ -56,7 +56,7 @@ export function RadioGroup<T>(props: RadioGroupProps<T>) {
data-fill={local.fill ? "" : undefined}
data-pad={local.pad ?? "normal"}
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
value={local.current ? getValue(local.current) : undefined}
diff --git a/packages/ui/src/components/resize-handle.tsx b/packages/ui/src/components/resize-handle.tsx
index e2eed1bb7..d7774a684 100644
--- a/packages/ui/src/components/resize-handle.tsx
+++ b/packages/ui/src/components/resize-handle.tsx
@@ -73,7 +73,7 @@ export function ResizeHandle(props: ResizeHandleProps) {
data-direction={local.direction}
data-edge={local.edge ?? (local.direction === "vertical" ? "start" : "end")}
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
onMouseDown={handleMouseDown}
diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx
index 61804a951..67becf2d9 100644
--- a/packages/ui/src/components/select.tsx
+++ b/packages/ui/src/components/select.tsx
@@ -104,7 +104,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
{...itemProps}
data-slot="select-select-item"
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
onPointerEnter={() => move(itemProps.item.rawValue)}
@@ -141,7 +141,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
variant={props.variant}
style={local.triggerStyle}
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
>
@@ -160,7 +160,7 @@ export function Select<T>(props: SelectProps<T> & Omit<ButtonProps, "children">)
<Kobalte.Portal>
<Kobalte.Content
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
}}
data-component="select-content"
diff --git a/packages/ui/src/components/session-diff.ts b/packages/ui/src/components/session-diff.ts
index cc2b1ce52..a5fbdbc5c 100644
--- a/packages/ui/src/components/session-diff.ts
+++ b/packages/ui/src/components/session-diff.ts
@@ -1,6 +1,5 @@
-import { parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs"
-import { sampledChecksum } from "@opencode-ai/util/encode"
-import { formatPatch, structuredPatch } from "diff"
+import { parseDiffFromFile, type FileDiffMetadata } from "@pierre/diffs"
+import { formatPatch, parsePatch, structuredPatch } from "diff"
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
type LegacyDiff = {
@@ -26,41 +25,51 @@ export type ViewDiff = {
const cache = new Map<string, FileDiffMetadata>()
-function empty(file: string, key: string) {
- return {
- name: file,
- type: "change",
- hunks: [],
- splitLineCount: 0,
- unifiedLineCount: 0,
- isPartial: true,
- deletionLines: [],
- additionLines: [],
- cacheKey: key,
- } satisfies FileDiffMetadata
-}
-
function patch(diff: ReviewDiff) {
- if (typeof diff.patch === "string") return diff.patch
- return formatPatch(
- structuredPatch(
- diff.file,
- diff.file,
- "before" in diff && typeof diff.before === "string" ? diff.before : "",
- "after" in diff && typeof diff.after === "string" ? diff.after : "",
- "",
- "",
- { context: Number.MAX_SAFE_INTEGER },
+ if (typeof diff.patch === "string") {
+ const [patch] = parsePatch(diff.patch)
+
+ const beforeLines = []
+ const afterLines = []
+
+ for (const hunk of patch.hunks) {
+ for (const line of hunk.lines) {
+ if (line.startsWith("-")) {
+ beforeLines.push(line.slice(1))
+ } else if (line.startsWith("+")) {
+ afterLines.push(line.slice(1))
+ } else {
+ // context line (starts with ' ')
+ beforeLines.push(line.slice(1))
+ afterLines.push(line.slice(1))
+ }
+ }
+ }
+
+ return { before: beforeLines.join("\n"), after: afterLines.join("\n"), patch: diff.patch }
+ }
+ return {
+ before: "before" in diff && typeof diff.before === "string" ? diff.before : "",
+ after: "after" in diff && typeof diff.after === "string" ? diff.after : "",
+ patch: formatPatch(
+ structuredPatch(
+ diff.file,
+ diff.file,
+ "before" in diff && typeof diff.before === "string" ? diff.before : "",
+ "after" in diff && typeof diff.after === "string" ? diff.after : "",
+ "",
+ "",
+ { context: Number.MAX_SAFE_INTEGER },
+ ),
),
- )
+ }
}
-function file(file: string, patch: string) {
+function file(file: string, patch: string, before: string, after: string) {
const hit = cache.get(patch)
if (hit) return hit
- const key = sampledChecksum(patch) ?? file
- const value = parsePatchFiles(patch, key).flatMap((item) => item.files)[0] ?? empty(file, key)
+ const value = parseDiffFromFile({ name: file, contents: before }, { name: file, contents: after })
cache.set(patch, value)
return value
}
@@ -69,11 +78,11 @@ export function normalize(diff: ReviewDiff): ViewDiff {
const next = patch(diff)
return {
file: diff.file,
- patch: next,
+ patch: next.patch,
additions: diff.additions,
deletions: diff.deletions,
status: diff.status,
- fileDiff: file(diff.file, next),
+ fileDiff: file(diff.file, next.patch, next.before, next.after),
}
}
diff --git a/packages/ui/src/components/session-review.tsx b/packages/ui/src/components/session-review.tsx
index 4a7205a5d..6e2b0853a 100644
--- a/packages/ui/src/components/session-review.tsx
+++ b/packages/ui/src/components/session-review.tsx
@@ -11,8 +11,8 @@ import { Tooltip } from "./tooltip"
import { ScrollView } from "./scroll-view"
import { useFileComponent } from "../context/file"
import { useI18n } from "../context/i18n"
-import { getDirectory, getFilename } from "@opencode-ai/util/path"
-import { checksum } from "@opencode-ai/util/encode"
+import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
+import { checksum } from "@opencode-ai/shared/util/encode"
import { createEffect, createMemo, For, Match, onCleanup, Show, Switch, untrack, type JSX } from "solid-js"
import { createStore } from "solid-js/store"
import { type FileContent, type SnapshotFileDiff, type VcsFileDiff } from "@opencode-ai/sdk/v2"
@@ -385,9 +385,11 @@ export const SessionReview = (props: SessionReviewProps) => {
<Accordion multiple value={open()} onChange={handleChange}>
<For each={items()}>
{(diff) => {
- let wrapper: HTMLDivElement | undefined
const file = diff.file
+ // binary files have empty diffs that we can't render
+ const diffCanRender = () => diff.additions !== 0 && diff.deletions !== 0
+
const expanded = createMemo(() => open().includes(file))
const mounted = createMemo(() => expanded() && (!!store.visible[file] || pinned(file)))
const force = () => !!store.force[file]
@@ -496,14 +498,14 @@ export const SessionReview = (props: SessionReviewProps) => {
return (
<Accordion.Item
- value={file}
+ value={diffCanRender() ? file : null!}
id={diffId(file)}
data-file={file}
data-slot="session-review-accordion-item"
data-selected={props.focusedFile === file ? "" : undefined}
>
<StickyAccordionHeader>
- <Accordion.Trigger>
+ <Accordion.Trigger disabled={!diffCanRender()} class="cursor-default">
<div data-slot="session-review-trigger-content">
<div data-slot="session-review-file-info">
<FileIcon node={{ path: file, type: "file" }} />
@@ -512,7 +514,7 @@ export const SessionReview = (props: SessionReviewProps) => {
<span data-slot="session-review-directory">{`\u202A${getDirectory(file)}\u202C`}</span>
</Show>
<span data-slot="session-review-filename">{getFilename(file)}</span>
- <Show when={props.onViewFile}>
+ <Show when={props.onViewFile && diffCanRender()}>
<Tooltip value={openFileLabel()} placement="top" gutter={4}>
<button
data-slot="session-review-view-button"
@@ -553,9 +555,11 @@ export const SessionReview = (props: SessionReviewProps) => {
<DiffChanges changes={diff} />
</Match>
</Switch>
- <span data-slot="session-review-diff-chevron">
- <Icon name="chevron-down" size="small" />
- </span>
+ <Show when={diffCanRender()}>
+ <span data-slot="session-review-diff-chevron">
+ <Icon name="chevron-down" size="small" />
+ </span>
+ </Show>
</div>
</div>
</Accordion.Trigger>
@@ -564,7 +568,6 @@ export const SessionReview = (props: SessionReviewProps) => {
<div
data-slot="session-review-diff-wrapper"
ref={(el) => {
- wrapper = el
anchors.set(file, el)
nodes.set(file, el)
queue()
diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css
index b01343a01..54076f3f8 100644
--- a/packages/ui/src/components/session-turn.css
+++ b/packages/ui/src/components/session-turn.css
@@ -26,7 +26,7 @@
align-items: flex-start;
align-self: stretch;
min-width: 0;
- gap: 18px;
+ gap: 0px;
overflow-anchor: none;
}
@@ -47,6 +47,7 @@
display: flex;
align-items: center;
gap: 8px;
+ margin-top: 12px;
width: 100%;
min-width: 0;
color: var(--text-weak);
@@ -226,5 +227,5 @@
}
[data-slot="session-turn-list"] {
- gap: 48px;
+ gap: 24px;
}
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index e891a6feb..75279a90e 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -8,8 +8,8 @@ import type { SessionStatus } from "@opencode-ai/sdk/v2"
import { useData } from "../context"
import { useFileComponent } from "../context/file"
-import { Binary } from "@opencode-ai/util/binary"
-import { getDirectory, getFilename } from "@opencode-ai/util/path"
+import { Binary } from "@opencode-ai/shared/util/binary"
+import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { Dynamic } from "solid-js/web"
@@ -110,7 +110,7 @@ function partState(part: PartType, showReasoningSummaries: boolean) {
function clean(value: string) {
return value
.replace(/`([^`]+)`/g, "$1")
- .replace(/\[([^\]]+)\]\([^\)]+\)/g, "$1")
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/[*_~]+/g, "")
.trim()
}
@@ -313,6 +313,7 @@ export function SessionTurn(
const msg = error()?.data?.message
if (typeof msg === "string") return unwrap(msg)
if (msg === undefined || msg === null) return ""
+ // oxlint-disable-next-line no-base-to-string -- msg is unknown from error data, coercion is intentional
return unwrap(String(msg))
})
diff --git a/packages/ui/src/components/spinner.tsx b/packages/ui/src/components/spinner.tsx
index 3d029d976..f8aacb6e2 100644
--- a/packages/ui/src/components/spinner.tsx
+++ b/packages/ui/src/components/spinner.tsx
@@ -23,7 +23,7 @@ export function Spinner(props: {
viewBox="0 0 15 15"
data-component="spinner"
classList={{
- ...(props.classList ?? {}),
+ ...props.classList,
[props.class ?? ""]: !!props.class,
}}
fill="currentColor"
diff --git a/packages/ui/src/components/sticky-accordion-header.tsx b/packages/ui/src/components/sticky-accordion-header.tsx
index 89d733449..b877aa16e 100644
--- a/packages/ui/src/components/sticky-accordion-header.tsx
+++ b/packages/ui/src/components/sticky-accordion-header.tsx
@@ -8,7 +8,7 @@ export function StickyAccordionHeader(
<Accordion.Header
data-component="sticky-accordion-header"
classList={{
- ...(props.classList ?? {}),
+ ...props.classList,
[props.class ?? ""]: !!props.class,
}}
>
diff --git a/packages/ui/src/components/tabs.tsx b/packages/ui/src/components/tabs.tsx
index 396504dd7..f46e9bfb3 100644
--- a/packages/ui/src/components/tabs.tsx
+++ b/packages/ui/src/components/tabs.tsx
@@ -27,7 +27,7 @@ function TabsRoot(props: TabsProps) {
data-variant={split.variant || "normal"}
data-orientation={split.orientation || "horizontal"}
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
/>
@@ -41,7 +41,7 @@ function TabsList(props: TabsListProps) {
{...rest}
data-slot="tabs-list"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
/>
@@ -63,7 +63,7 @@ function TabsTrigger(props: ParentProps<TabsTriggerProps>) {
data-slot="tabs-trigger-wrapper"
data-value={props.value}
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
onMouseDown={(e) => {
@@ -104,7 +104,7 @@ function TabsContent(props: ParentProps<TabsContentProps>) {
{...rest}
data-slot="tabs-content"
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
diff --git a/packages/ui/src/components/tag.tsx b/packages/ui/src/components/tag.tsx
index 428eedd0f..c54e4d474 100644
--- a/packages/ui/src/components/tag.tsx
+++ b/packages/ui/src/components/tag.tsx
@@ -12,7 +12,7 @@ export function Tag(props: TagProps) {
data-component="tag"
data-size={split.size || "normal"}
classList={{
- ...(split.classList ?? {}),
+ ...split.classList,
[split.class ?? ""]: !!split.class,
}}
>
diff --git a/packages/ui/src/components/text-field.tsx b/packages/ui/src/components/text-field.tsx
index d10f5d6ac..82be20f9e 100644
--- a/packages/ui/src/components/text-field.tsx
+++ b/packages/ui/src/components/text-field.tsx
@@ -75,7 +75,7 @@ export function TextField(props: TextFieldProps) {
}
function handleClick() {
- if (local.copyable) handleCopy()
+ if (local.copyable) void handleCopy()
}
return (
diff --git a/packages/ui/src/components/text-reveal.tsx b/packages/ui/src/components/text-reveal.tsx
index 02bf8084c..2d2a94e6a 100644
--- a/packages/ui/src/components/text-reveal.tsx
+++ b/packages/ui/src/components/text-reveal.tsx
@@ -102,7 +102,7 @@ export function TextReveal(props: {
requestAnimationFrame(() => setState("ready", true))
return
}
- fonts.ready.finally(() => {
+ void fonts.ready.finally(() => {
widen(win())
requestAnimationFrame(() => setState("ready", true))
})
diff --git a/packages/ui/src/components/thinking-heading.stories.tsx b/packages/ui/src/components/thinking-heading.stories.tsx
index 3a65619ce..12a06b4d8 100644
--- a/packages/ui/src/components/thinking-heading.stories.tsx
+++ b/packages/ui/src/components/thinking-heading.stories.tsx
@@ -442,7 +442,7 @@ function AnimatedHeading(props) {
onMount(() => {
measure()
- document.fonts?.ready.finally(() => {
+ void document.fonts?.ready.finally(() => {
measure()
requestAnimationFrame(() => setState("ready", true))
})
diff --git a/packages/ui/src/components/timeline-playground.stories.tsx b/packages/ui/src/components/timeline-playground.stories.tsx
index e79e97a3a..c071db303 100644
--- a/packages/ui/src/components/timeline-playground.stories.tsx
+++ b/packages/ui/src/components/timeline-playground.stories.tsx
@@ -1,5 +1,5 @@
// @ts-nocheck
-import { createSignal, createMemo, createEffect, on, For, Show, Index, batch } from "solid-js"
+import { createSignal, createMemo, createEffect, on, For, Show, batch } from "solid-js"
import { createStore, produce } from "solid-js/store"
import type {
Message,
@@ -9,7 +9,6 @@ import type {
TextPart,
ReasoningPart,
ToolPart,
- CompactionPart,
FilePart,
AgentPart,
} from "@opencode-ai/sdk/v2"
@@ -555,10 +554,6 @@ function toolPart(sample: (typeof TOOL_SAMPLES)[keyof typeof TOOL_SAMPLES], stat
} as ToolPart
}
-function compactionPart(): CompactionPart {
- return { id: uid(), type: "compaction", auto: true } as CompactionPart
-}
-
// ---------------------------------------------------------------------------
// CSS Controls definition
// ---------------------------------------------------------------------------
@@ -568,6 +563,7 @@ const MD = "markdown.css"
const MP = "message-part.css"
const ST = "session-turn.css"
const CL = "collapsible.css"
+const BT = "basic-tool.css"
/**
* Source mapping for a CSS control.
@@ -607,10 +603,10 @@ const CSS_CONTROLS: CSSControl[] = [
// --- Timeline spacing ---
{
key: "turn-gap",
- label: "Turn gap",
+ label: "Above user messages",
group: "Timeline Spacing",
type: "range",
- initial: "48",
+ initial: "32",
selector: '[data-slot="session-turn-list"]',
property: "gap",
min: "0",
@@ -621,10 +617,10 @@ const CSS_CONTROLS: CSSControl[] = [
},
{
key: "container-gap",
- label: "Container gap",
+ label: "Below user messages",
group: "Timeline Spacing",
type: "range",
- initial: "18",
+ initial: "0",
selector: '[data-slot="session-turn-message-container"]',
property: "gap",
min: "0",
@@ -1041,11 +1037,39 @@ const CSS_CONTROLS: CSSControl[] = [
// --- Tool parts ---
{
+ key: "tool-subtitle-font-size",
+ label: "Subtitle font size",
+ group: "Tool Parts",
+ type: "range",
+ initial: "14",
+ selector: '[data-slot="basic-tool-tool-subtitle"]',
+ property: "font-size",
+ min: "10",
+ max: "22",
+ step: "1",
+ unit: "px",
+ source: { file: BT, anchor: '[data-slot="basic-tool-tool-subtitle"]', prop: "font-size", format: px },
+ },
+ {
+ key: "exa-output-font-size",
+ label: "Search output font size",
+ group: "Tool Parts",
+ type: "range",
+ initial: "14",
+ selector: '[data-component="exa-tool-output"]',
+ property: "font-size",
+ min: "10",
+ max: "22",
+ step: "1",
+ unit: "px",
+ source: { file: MP, anchor: '[data-component="exa-tool-output"]', prop: "font-size", format: px },
+ },
+ {
key: "tool-content-gap",
label: "Trigger/content gap",
group: "Tool Parts",
type: "range",
- initial: "8",
+ initial: "4",
selector: '[data-component="collapsible"].tool-collapsible',
property: "--tool-content-gap",
min: "0",
@@ -1059,7 +1083,7 @@ const CSS_CONTROLS: CSSControl[] = [
label: "Explored tool gap",
group: "Explored Group",
type: "range",
- initial: "14",
+ initial: "4",
selector: '[data-component="context-tool-group-list"]',
property: "gap",
min: "0",
diff --git a/packages/ui/src/components/toast.tsx b/packages/ui/src/components/toast.tsx
index e8062a2a8..599cf2a9e 100644
--- a/packages/ui/src/components/toast.tsx
+++ b/packages/ui/src/components/toast.tsx
@@ -30,7 +30,7 @@ function ToastRoot(props: ToastRootComponentProps) {
<Kobalte
data-component="toast"
classList={{
- ...(props.classList ?? {}),
+ ...props.classList,
[props.class ?? ""]: !!props.class,
}}
{...props}
diff --git a/packages/ui/src/components/tool-error-card.tsx b/packages/ui/src/components/tool-error-card.tsx
index 038870d38..9983e2fe7 100644
--- a/packages/ui/src/components/tool-error-card.tsx
+++ b/packages/ui/src/components/tool-error-card.tsx
@@ -128,7 +128,7 @@ export function ToolErrorCard(props: ToolErrorCardProps) {
onMouseDown={(e) => e.preventDefault()}
onClick={(e) => {
e.stopPropagation()
- copy()
+ void copy()
}}
aria-label={copied() ? i18n.t("ui.message.copied") : i18n.t("ui.toolErrorCard.copyError")}
/>
diff --git a/packages/ui/src/components/tool-status-title.tsx b/packages/ui/src/components/tool-status-title.tsx
index 2a58e0e5b..412d92e3d 100644
--- a/packages/ui/src/components/tool-status-title.tsx
+++ b/packages/ui/src/components/tool-status-title.tsx
@@ -86,7 +86,7 @@ export function ToolStatusTitle(props: {
finish()
return
}
- fonts.ready.finally(() => {
+ void fonts.ready.finally(() => {
measure()
finish()
})
diff --git a/packages/ui/src/components/tooltip.tsx b/packages/ui/src/components/tooltip.tsx
index ada597d0f..4366651d2 100644
--- a/packages/ui/src/components/tooltip.tsx
+++ b/packages/ui/src/components/tooltip.tsx
@@ -99,6 +99,8 @@ export function Tooltip(props: TooltipProps) {
onCleanup(() => obs.disconnect())
})
+ let justClickedTrigger = false
+
return (
<Switch>
<Match when={local.inactive}>{local.children}</Match>
@@ -112,6 +114,10 @@ export function Tooltip(props: TooltipProps) {
onOpenChange={(open) => {
if (local.forceOpen) return
if (state.block && open) return
+ if (justClickedTrigger) {
+ justClickedTrigger = false
+ return
+ }
setState("open", open)
}}
>
@@ -137,6 +143,12 @@ export function Tooltip(props: TooltipProps) {
data-force-open={local.forceOpen}
class={local.contentClass}
style={local.contentStyle}
+ onPointerDownOutside={(e) => {
+ if (ref === e.target || (e.target instanceof Node && ref?.contains(e.target))) {
+ justClickedTrigger = true
+ }
+ e.preventDefault()
+ }}
>
{local.value}
{/* <KobalteTooltip.Arrow data-slot="tooltip-arrow" /> */}