Merge remote-tracking branch 'upstream/dev' into refactor-shells
This commit is contained in:
commit
f9a633bd0b
1134 changed files with 98917 additions and 58463 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
43
packages/ui/src/components/apply-patch-file.test.ts
Normal file
43
packages/ui/src/components/apply-patch-file.test.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { patchFiles } from "./apply-patch-file"
|
||||
import { text } from "./session-diff"
|
||||
|
||||
describe("apply patch file", () => {
|
||||
test("parses patch metadata from the server", () => {
|
||||
const file = patchFiles([
|
||||
{
|
||||
filePath: "/tmp/a.ts",
|
||||
relativePath: "a.ts",
|
||||
type: "update",
|
||||
patch:
|
||||
"Index: a.ts\n===================================================================\n--- a.ts\t\n+++ a.ts\t\n@@ -1,2 +1,2 @@\n one\n-two\n+three\n",
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
},
|
||||
])[0]
|
||||
|
||||
expect(file).toBeDefined()
|
||||
expect(file?.view.fileDiff.name).toBe("a.ts")
|
||||
expect(text(file!.view, "deletions")).toBe("one\ntwo\n")
|
||||
expect(text(file!.view, "additions")).toBe("one\nthree\n")
|
||||
})
|
||||
|
||||
test("keeps legacy before and after payloads working", () => {
|
||||
const file = patchFiles([
|
||||
{
|
||||
filePath: "/tmp/a.ts",
|
||||
relativePath: "a.ts",
|
||||
type: "update",
|
||||
before: "one\n",
|
||||
after: "two\n",
|
||||
additions: 1,
|
||||
deletions: 1,
|
||||
},
|
||||
])[0]
|
||||
|
||||
expect(file).toBeDefined()
|
||||
expect(file?.view.patch).toContain("@@ -1,1 +1,1 @@")
|
||||
expect(text(file!.view, "deletions")).toBe("one\n")
|
||||
expect(text(file!.view, "additions")).toBe("two\n")
|
||||
})
|
||||
})
|
||||
78
packages/ui/src/components/apply-patch-file.ts
Normal file
78
packages/ui/src/components/apply-patch-file.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { normalize, type ViewDiff } from "./session-diff"
|
||||
|
||||
type Kind = "add" | "update" | "delete" | "move"
|
||||
|
||||
type Raw = {
|
||||
filePath?: string
|
||||
relativePath?: string
|
||||
type?: Kind
|
||||
patch?: string
|
||||
diff?: string
|
||||
before?: string
|
||||
after?: string
|
||||
additions?: number
|
||||
deletions?: number
|
||||
movePath?: string
|
||||
}
|
||||
|
||||
export type ApplyPatchFile = {
|
||||
filePath: string
|
||||
relativePath: string
|
||||
type: Kind
|
||||
additions: number
|
||||
deletions: number
|
||||
movePath?: string
|
||||
view: ViewDiff
|
||||
}
|
||||
|
||||
function kind(value: unknown) {
|
||||
if (value === "add" || value === "update" || value === "delete" || value === "move") return value
|
||||
}
|
||||
|
||||
function status(type: Kind): "added" | "deleted" | "modified" {
|
||||
if (type === "add") return "added"
|
||||
if (type === "delete") return "deleted"
|
||||
return "modified"
|
||||
}
|
||||
|
||||
export function patchFile(raw: unknown): ApplyPatchFile | undefined {
|
||||
if (!raw || typeof raw !== "object") return
|
||||
|
||||
const value = raw as Raw
|
||||
const type = kind(value.type)
|
||||
const filePath = typeof value.filePath === "string" ? value.filePath : undefined
|
||||
const relativePath = typeof value.relativePath === "string" ? value.relativePath : filePath
|
||||
const patch = typeof value.patch === "string" ? value.patch : typeof value.diff === "string" ? value.diff : undefined
|
||||
const before = typeof value.before === "string" ? value.before : undefined
|
||||
const after = typeof value.after === "string" ? value.after : undefined
|
||||
|
||||
if (!type || !filePath || !relativePath) return
|
||||
if (!patch && before === undefined && after === undefined) return
|
||||
|
||||
const additions = typeof value.additions === "number" ? value.additions : 0
|
||||
const deletions = typeof value.deletions === "number" ? value.deletions : 0
|
||||
const movePath = typeof value.movePath === "string" ? value.movePath : undefined
|
||||
|
||||
return {
|
||||
filePath,
|
||||
relativePath,
|
||||
type,
|
||||
additions,
|
||||
deletions,
|
||||
movePath,
|
||||
view: normalize({
|
||||
file: relativePath,
|
||||
patch,
|
||||
before,
|
||||
after,
|
||||
additions,
|
||||
deletions,
|
||||
status: status(type),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
export function patchFiles(raw: unknown) {
|
||||
if (!Array.isArray(raw)) return []
|
||||
return raw.map(patchFile).filter((file): file is ApplyPatchFile => !!file)
|
||||
}
|
||||
|
|
@ -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={{
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
overflow: visible;
|
||||
|
||||
&.tool-collapsible {
|
||||
--tool-content-gap: 8px;
|
||||
--tool-content-gap: 4px;
|
||||
gap: var(--tool-content-gap);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const FileIcon: Component<FileIconProps> = (props) => {
|
|||
data-component="file-icon"
|
||||
{...rest}
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
...local.classList,
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export function Keybind(props: KeybindProps) {
|
|||
<span
|
||||
data-component="keybind"
|
||||
classList={{
|
||||
...(props.classList ?? {}),
|
||||
...props.classList,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\"/g, """)
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
}
|
||||
|
||||
|
|
@ -338,7 +338,7 @@ export function Markdown(
|
|||
<div
|
||||
data-component="markdown"
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
...local.classList,
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
ref={setRoot}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,14 +45,15 @@ 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"
|
||||
import { TextShimmer } from "./text-shimmer"
|
||||
import { AnimatedCountList } from "./tool-count-summary"
|
||||
import { ToolStatusTitle } from "./tool-status-title"
|
||||
import { patchFiles } from "./apply-patch-file"
|
||||
import { animate } from "motion"
|
||||
import { useLocation } from "@solidjs/router"
|
||||
import { attached, inline, kind } from "./message-file"
|
||||
|
|
@ -1162,7 +1162,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")}
|
||||
/>
|
||||
|
|
@ -1273,7 +1273,7 @@ function ToolFileAccordion(props: { path: string; actions?: JSX.Element; childre
|
|||
<Accordion
|
||||
multiple
|
||||
data-scope="apply-patch"
|
||||
style={{ "--sticky-accordion-offset": "40px" }}
|
||||
style={{ "--sticky-accordion-offset": "calc(32px + var(--tool-content-gap))" }}
|
||||
defaultValue={[value()]}
|
||||
>
|
||||
<Accordion.Item value={value()}>
|
||||
|
|
@ -2018,24 +2018,12 @@ ToolRegistry.register({
|
|||
},
|
||||
})
|
||||
|
||||
interface ApplyPatchFile {
|
||||
filePath: string
|
||||
relativePath: string
|
||||
type: "add" | "update" | "delete" | "move"
|
||||
diff: string
|
||||
before: string
|
||||
after: string
|
||||
additions: number
|
||||
deletions: number
|
||||
movePath?: string
|
||||
}
|
||||
|
||||
ToolRegistry.register({
|
||||
name: "apply_patch",
|
||||
render(props) {
|
||||
const i18n = useI18n()
|
||||
const fileComponent = useFileComponent()
|
||||
const files = createMemo(() => (props.metadata.files ?? []) as ApplyPatchFile[])
|
||||
const files = createMemo(() => patchFiles(props.metadata.files))
|
||||
const pending = createMemo(() => props.status === "pending" || props.status === "running")
|
||||
const single = createMemo(() => {
|
||||
const list = files()
|
||||
|
|
@ -2077,7 +2065,7 @@ ToolRegistry.register({
|
|||
<Accordion
|
||||
multiple
|
||||
data-scope="apply-patch"
|
||||
style={{ "--sticky-accordion-offset": "40px" }}
|
||||
style={{ "--sticky-accordion-offset": "calc(32px + var(--tool-content-gap))" }}
|
||||
value={expanded()}
|
||||
onChange={(value) => setExpanded(Array.isArray(value) ? value : value ? [value] : [])}
|
||||
>
|
||||
|
|
@ -2141,12 +2129,7 @@ ToolRegistry.register({
|
|||
<Accordion.Content>
|
||||
<Show when={visible()}>
|
||||
<div data-component="apply-patch-file-diff">
|
||||
<Dynamic
|
||||
component={fileComponent}
|
||||
mode="diff"
|
||||
before={{ name: file.filePath, contents: file.before }}
|
||||
after={{ name: file.movePath ?? file.filePath, contents: file.after }}
|
||||
/>
|
||||
<Dynamic component={fileComponent} mode="diff" fileDiff={file.view.fileDiff} />
|
||||
</div>
|
||||
</Show>
|
||||
</Accordion.Content>
|
||||
|
|
@ -2216,12 +2199,7 @@ ToolRegistry.register({
|
|||
}
|
||||
>
|
||||
<div data-component="apply-patch-file-diff">
|
||||
<Dynamic
|
||||
component={fileComponent}
|
||||
mode="diff"
|
||||
before={{ name: single()!.filePath, contents: single()!.before }}
|
||||
after={{ name: single()!.movePath ?? single()!.filePath, contents: single()!.after }}
|
||||
/>
|
||||
<Dynamic component={fileComponent} mode="diff" fileDiff={single()!.view.fileDiff} />
|
||||
</div>
|
||||
</ToolFileAccordion>
|
||||
</BasicTool>
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export function ProgressCircle(props: ProgressCircleProps) {
|
|||
fill="none"
|
||||
data-component="progress-circle"
|
||||
classList={{
|
||||
...(split.classList ?? {}),
|
||||
...split.classList,
|
||||
[split.class ?? ""]: !!split.class,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export function Progress(props: ProgressProps) {
|
|||
{...others}
|
||||
data-component="progress"
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
...local.classList,
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const ProviderIcon: Component<ProviderIconProps> = (props) => {
|
|||
data-component="provider-icon"
|
||||
{...rest}
|
||||
classList={{
|
||||
...(local.classList ?? {}),
|
||||
...local.classList,
|
||||
[local.class ?? ""]: !!local.class,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ export const iconNames = [
|
|||
"perplexity",
|
||||
"ovhcloud",
|
||||
"openrouter",
|
||||
"llmgateway",
|
||||
"opencode",
|
||||
"opencode-go",
|
||||
"openai",
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -65,6 +65,26 @@ export type SessionReviewFocus = { file: string; id: string }
|
|||
type ReviewDiff = (SnapshotFileDiff | VcsFileDiff) & { preloaded?: PreloadMultiFileDiffResult<any> }
|
||||
type Item = ViewDiff & { preloaded?: PreloadMultiFileDiffResult<any> }
|
||||
|
||||
function diff(value: unknown): value is ReviewDiff {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) return false
|
||||
if (!("file" in value) || typeof value.file !== "string") return false
|
||||
if (!("additions" in value) || typeof value.additions !== "number") return false
|
||||
if (!("deletions" in value) || typeof value.deletions !== "number") return false
|
||||
if ("patch" in value && value.patch !== undefined && typeof value.patch !== "string") return false
|
||||
if ("before" in value && value.before !== undefined && typeof value.before !== "string") return false
|
||||
if ("after" in value && value.after !== undefined && typeof value.after !== "string") return false
|
||||
if (!("status" in value) || value.status === undefined) return true
|
||||
return value.status === "added" || value.status === "deleted" || value.status === "modified"
|
||||
}
|
||||
|
||||
function list(value: unknown): ReviewDiff[] {
|
||||
if (Array.isArray(value) && value.every(diff)) return value
|
||||
if (Array.isArray(value)) return value.filter(diff)
|
||||
if (diff(value)) return [value]
|
||||
if (!value || typeof value !== "object") return []
|
||||
return Object.values(value).filter(diff)
|
||||
}
|
||||
|
||||
export interface SessionReviewProps {
|
||||
title?: JSX.Element
|
||||
empty?: JSX.Element
|
||||
|
|
@ -157,7 +177,9 @@ export const SessionReview = (props: SessionReviewProps) => {
|
|||
const opened = () => store.opened
|
||||
|
||||
const open = () => props.open ?? store.open
|
||||
const items = createMemo<Item[]>(() => props.diffs.map((diff) => ({ ...normalize(diff), preloaded: diff.preloaded })))
|
||||
const items = createMemo<Item[]>(() =>
|
||||
list(props.diffs).map((diff) => ({ ...normalize(diff), preloaded: diff.preloaded })),
|
||||
)
|
||||
const files = createMemo(() => items().map((diff) => diff.file))
|
||||
const grouped = createMemo(() => {
|
||||
const next = new Map<string, SessionReviewComment[]>()
|
||||
|
|
@ -363,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]
|
||||
|
|
@ -474,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" }} />
|
||||
|
|
@ -490,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"
|
||||
|
|
@ -531,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>
|
||||
|
|
@ -542,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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -94,9 +95,15 @@
|
|||
|
||||
[data-slot="session-turn-diffs-header"] {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 12px;
|
||||
position: sticky;
|
||||
top: var(--sticky-accordion-top, 0px);
|
||||
z-index: 20;
|
||||
background-color: var(--background-stronger);
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
[data-slot="session-turn-diffs-label"] {
|
||||
|
|
@ -220,5 +227,5 @@
|
|||
}
|
||||
|
||||
[data-slot="session-turn-list"] {
|
||||
gap: 48px;
|
||||
gap: 24px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -267,14 +267,12 @@ export function SessionTurn(
|
|||
if (!msg) return emptyAssistant
|
||||
|
||||
const messages = allMessages() ?? emptyMessages
|
||||
const index = messageIndex()
|
||||
if (index < 0) return emptyAssistant
|
||||
if (messageIndex() < 0) return emptyAssistant
|
||||
|
||||
const result: AssistantMessage[] = []
|
||||
for (let i = index + 1; i < messages.length; i++) {
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
const item = messages[i]
|
||||
if (!item) continue
|
||||
if (item.role === "user") break
|
||||
if (item.role === "assistant" && item.parentID === msg.id) result.push(item as AssistantMessage)
|
||||
}
|
||||
return result
|
||||
|
|
@ -313,6 +311,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))
|
||||
})
|
||||
|
||||
|
|
@ -447,7 +446,7 @@ export function SessionTurn(
|
|||
<div data-component="session-turn-diffs-content">
|
||||
<Accordion
|
||||
multiple
|
||||
style={{ "--sticky-accordion-offset": "40px" }}
|
||||
style={{ "--sticky-accordion-offset": "44px" }}
|
||||
value={expanded()}
|
||||
onChange={(value) => setState("expanded", Array.isArray(value) ? value : value ? [value] : [])}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export function StickyAccordionHeader(
|
|||
<Accordion.Header
|
||||
data-component="sticky-accordion-header"
|
||||
classList={{
|
||||
...(props.classList ?? {}),
|
||||
...props.classList,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ export function TextField(props: TextFieldProps) {
|
|||
}
|
||||
|
||||
function handleClick() {
|
||||
if (local.copyable) handleCopy()
|
||||
if (local.copyable) void handleCopy()
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -442,7 +442,7 @@ function AnimatedHeading(props) {
|
|||
|
||||
onMount(() => {
|
||||
measure()
|
||||
document.fonts?.ready.finally(() => {
|
||||
void document.fonts?.ready.finally(() => {
|
||||
measure()
|
||||
requestAnimationFrame(() => setState("ready", true))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -319,7 +318,7 @@ const TOOL_SAMPLES = {
|
|||
tool: "shell",
|
||||
input: { command: "bun test --filter session", description: "Run session tests" },
|
||||
output:
|
||||
"bun test v1.3.11\n\n✓ session-turn.test.tsx (3 tests) 45ms\n✓ message-part.test.tsx (7 tests) 120ms\n\nTest Suites: 2 passed, 2 total\nTests: 10 passed, 10 total\nTime: 0.89s",
|
||||
"bun test v1.3.13\n\n✓ session-turn.test.tsx (3 tests) 45ms\n✓ message-part.test.tsx (7 tests) 120ms\n\nTest Suites: 2 passed, 2 total\nTests: 10 passed, 10 total\nTime: 0.89s",
|
||||
title: "Run session tests",
|
||||
metadata: { command: "bun test --filter session" },
|
||||
},
|
||||
|
|
@ -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",
|
||||
|
|
@ -1040,12 +1036,40 @@ 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",
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ function ToastRoot(props: ToastRootComponentProps) {
|
|||
<Kobalte
|
||||
data-component="toast"
|
||||
classList={{
|
||||
...(props.classList ?? {}),
|
||||
...props.classList,
|
||||
[props.class ?? ""]: !!props.class,
|
||||
}}
|
||||
{...props}
|
||||
|
|
|
|||
|
|
@ -129,7 +129,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")}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ export function ToolStatusTitle(props: {
|
|||
finish()
|
||||
return
|
||||
}
|
||||
fonts.ready.finally(() => {
|
||||
void fonts.ready.finally(() => {
|
||||
measure()
|
||||
finish()
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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" /> */}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { type SelectedLineRange } from "@pierre/diffs"
|
||||
import { diffLineIndex, diffRowIndex, findDiffSide } from "./diff-selection"
|
||||
import { diffLineIndex, diffRowIndex } from "./diff-selection"
|
||||
|
||||
export type CommentSide = "additions" | "deletions"
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ function createPool(lineDiffType: "none" | "word-alt") {
|
|||
},
|
||||
)
|
||||
|
||||
pool.initialize()
|
||||
void pool.initialize()
|
||||
return pool
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ a {
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
*[data-tauri-drag-region] {
|
||||
#root:not([aria-hidden]) *[data-tauri-drag-region] {
|
||||
app-region: drag;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue