Merge branch 'dev' into desktop-poilsh-styles-ui-ux

This commit is contained in:
Aaron Iker 2026-01-19 15:15:53 +01:00
commit e3b78fc03a
170 changed files with 12745 additions and 7027 deletions

View file

@ -16,7 +16,6 @@
[data-component="dialog"] {
position: fixed;
inset: 0;
margin-left: var(--dialog-left-margin);
z-index: 50;
display: flex;
align-items: center;
@ -25,7 +24,7 @@
[data-slot="dialog-container"] {
position: relative;
z-index: 50;
width: min(calc(100vw - 16px), 480px);
width: min(calc(100vw - 16px), 640px);
height: min(calc(100vh - 16px), 512px);
display: flex;
flex-direction: column;
@ -120,9 +119,15 @@
outline: none;
}
}
}
&:focus-visible {
outline: none;
&[data-fit] {
[data-slot="dialog-container"] {
height: auto;
[data-slot="dialog-content"] {
min-height: 0;
}
}
}
}

View file

@ -8,11 +8,12 @@ export interface DialogProps extends ParentProps {
action?: JSXElement
class?: ComponentProps<"div">["class"]
classList?: ComponentProps<"div">["classList"]
fit?: boolean
}
export function Dialog(props: DialogProps) {
return (
<div data-component="dialog">
<div data-component="dialog" data-fit={props.fit ? true : undefined}>
<div data-slot="dialog-container">
<Kobalte.Content
data-slot="dialog-content"

View file

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

View file

@ -0,0 +1,11 @@
import type { ComponentProps } from "solid-js"
import { splitProps } from "solid-js"
export type InlineInputProps = ComponentProps<"input"> & {
width?: string
}
export function InlineInput(props: InlineInputProps) {
const [local, others] = splitProps(props, ["class", "width"])
return <input data-component="inline-input" class={local.class} style={{ width: local.width }} {...others} />
}

View file

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

View file

@ -0,0 +1,20 @@
import type { ComponentProps, ParentProps } from "solid-js"
export interface KeybindProps extends ParentProps {
class?: string
classList?: ComponentProps<"span">["classList"]
}
export function Keybind(props: KeybindProps) {
return (
<span
data-component="keybind"
classList={{
...(props.classList ?? {}),
[props.class ?? ""]: !!props.class,
}}
>
{props.children}
</span>
)
}

View file

@ -56,11 +56,14 @@
width: 20px;
height: 20px;
background-color: transparent;
opacity: 0.5;
transition: opacity 0.15s ease;
&:hover:not(:disabled),
&:focus:not(:disabled),
&:active:not(:disabled) {
background-color: transparent;
opacity: 0.7;
}
&:hover:not(:disabled) [data-slot="icon-svg"] {
@ -88,7 +91,7 @@
[data-slot="list-empty-state"] {
display: flex;
padding: 32px 0px;
padding: 32px 48px;
flex-direction: column;
justify-content: center;
align-items: center;
@ -100,8 +103,9 @@
justify-content: center;
align-items: center;
gap: 2px;
max-width: 100%;
color: var(--text-weak);
text-align: center;
white-space: nowrap;
/* text-14-regular */
font-family: var(--font-family-sans);
@ -114,6 +118,8 @@
[data-slot="list-filter"] {
color: var(--text-strong);
overflow: hidden;
text-overflow: ellipsis;
}
}
@ -122,10 +128,14 @@
display: flex;
flex-direction: column;
&:last-child {
padding-bottom: 12px;
}
[data-slot="list-header"] {
display: flex;
z-index: 10;
padding: 0 12px 8px 8px;
padding: 8px 12px 8px 12px;
justify-content: space-between;
align-items: center;
align-self: stretch;
@ -133,7 +143,7 @@
position: sticky;
top: 0;
color: var(--text-base);
color: var(--text-weak);
/* text-14-medium */
font-family: var(--font-family-sans);

View file

@ -9,6 +9,8 @@ import { TextField } from "./text-field"
export interface ListSearchProps {
placeholder?: string
autofocus?: boolean
hideIcon?: boolean
class?: string
}
export interface ListProps<T> extends FilteredListProps<T> {
@ -68,7 +70,7 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
if (!props.current) return
const key = props.key(props.current)
requestAnimationFrame(() => {
const element = scrollRef()?.querySelector(`[data-key="${key}"]`)
const element = scrollRef()?.querySelector(`[data-key="${CSS.escape(key)}"]`)
element?.scrollIntoView({ block: "center" })
})
})
@ -80,8 +82,8 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
scrollRef()?.scrollTo(0, 0)
return
}
const element = scrollRef()?.querySelector(`[data-key="${active()}"]`)
element?.scrollIntoView({ block: "center", behavior: "smooth" })
const element = scrollRef()?.querySelector(`[data-key="${CSS.escape(active()!)}"]`)
element?.scrollIntoView({ block: "center" })
})
createEffect(() => {
@ -147,9 +149,11 @@ export function List<T>(props: ListProps<T> & { ref?: (ref: ListRef) => void })
return (
<div data-component="list" classList={{ [props.class ?? ""]: !!props.class }}>
<Show when={!!props.search}>
<div data-slot="list-search">
<div data-slot="list-search" classList={{ [searchProps().class ?? ""]: !!searchProps().class }}>
<div data-slot="list-search-container">
<Icon name="magnifying-glass" />
<Show when={!searchProps().hideIcon}>
<Icon name="magnifying-glass" />
</Show>
<TextField
autofocus={searchProps().autofocus}
variant="ghost"

View file

@ -13,6 +13,21 @@ export const Mark = (props: { class?: string }) => {
)
}
export const Splash = (props: { class?: string }) => {
return (
<svg
data-component="logo-splash"
classList={{ [props.class ?? ""]: !!props.class }}
viewBox="0 0 80 100"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M60 80H20V40H60V80Z" fill="var(--icon-base)" />
<path d="M60 20H20V80H60V20ZM80 100H0V0H80V100Z" fill="var(--icon-strong-base)" />
</svg>
)
}
export const Logo = (props: { class?: string }) => {
return (
<svg

View file

@ -689,3 +689,75 @@
}
}
}
[data-component="apply-patch-files"] {
display: flex;
flex-direction: column;
}
[data-component="apply-patch-file"] {
display: flex;
flex-direction: column;
border-top: 1px solid var(--border-weaker-base);
&:first-child {
border-top: 1px solid var(--border-weaker-base);
}
[data-slot="apply-patch-file-header"] {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background-color: var(--surface-inset-base);
}
[data-slot="apply-patch-file-action"] {
font-family: var(--font-family-sans);
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
line-height: var(--line-height-large);
color: var(--text-base);
flex-shrink: 0;
&[data-type="delete"] {
color: var(--text-critical-base);
}
&[data-type="add"] {
color: var(--text-success-base);
}
&[data-type="move"] {
color: var(--text-warning-base);
}
}
[data-slot="apply-patch-file-path"] {
font-family: var(--font-family-mono);
font-size: var(--font-size-small);
color: var(--text-weak);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
flex-grow: 1;
}
[data-slot="apply-patch-deletion-count"] {
font-family: var(--font-family-mono);
font-size: var(--font-size-small);
color: var(--text-critical-base);
flex-shrink: 0;
}
}
[data-component="apply-patch-file-diff"] {
max-height: 420px;
overflow-y: auto;
scrollbar-width: none;
-ms-overflow-style: none;
&::-webkit-scrollbar {
display: none;
}
}

View file

@ -10,6 +10,7 @@ import {
onCleanup,
type JSX,
} from "solid-js"
import stripAnsi from "strip-ansi"
import { Dynamic } from "solid-js/web"
import {
AgentPart,
@ -232,6 +233,12 @@ export function getToolInfo(tool: string, input: any = {}): ToolInfo {
title: "Write",
subtitle: input.filePath ? getFilename(input.filePath) : undefined,
}
case "apply_patch":
return {
icon: "code-lines",
title: "Patch",
subtitle: input.files?.length ? `${input.files.length} file${input.files.length > 1 ? "s" : ""}` : undefined,
}
case "todowrite":
return {
icon: "checklist",
@ -926,7 +933,7 @@ ToolRegistry.register({
>
<div data-component="tool-output" data-scrollable>
<Markdown
text={`\`\`\`command\n$ ${props.input.command ?? props.metadata.command ?? ""}${props.output ? "\n\n" + props.output : ""}\n\`\`\``}
text={`\`\`\`command\n$ ${props.input.command ?? props.metadata.command ?? ""}${props.output || props.metadata.output ? "\n\n" + stripAnsi(props.output || props.metadata.output) : ""}\n\`\`\``}
/>
</div>
</BasicTool>
@ -1026,6 +1033,94 @@ 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 diffComponent = useDiffComponent()
const files = createMemo(() => (props.metadata.files ?? []) as ApplyPatchFile[])
const subtitle = createMemo(() => {
const count = files().length
if (count === 0) return ""
return `${count} file${count > 1 ? "s" : ""}`
})
return (
<BasicTool
{...props}
icon="code-lines"
trigger={{
title: "Patch",
subtitle: subtitle(),
}}
>
<Show when={files().length > 0}>
<div data-component="apply-patch-files">
<For each={files()}>
{(file) => (
<div data-component="apply-patch-file">
<div data-slot="apply-patch-file-header">
<Switch>
<Match when={file.type === "delete"}>
<span data-slot="apply-patch-file-action" data-type="delete">
Deleted
</span>
</Match>
<Match when={file.type === "add"}>
<span data-slot="apply-patch-file-action" data-type="add">
Created
</span>
</Match>
<Match when={file.type === "move"}>
<span data-slot="apply-patch-file-action" data-type="move">
Moved
</span>
</Match>
<Match when={file.type === "update"}>
<span data-slot="apply-patch-file-action" data-type="update">
Patched
</span>
</Match>
</Switch>
<span data-slot="apply-patch-file-path">{file.relativePath}</span>
<Show when={file.type !== "delete"}>
<DiffChanges changes={{ additions: file.additions, deletions: file.deletions }} />
</Show>
<Show when={file.type === "delete"}>
<span data-slot="apply-patch-deletion-count">-{file.deletions}</span>
</Show>
</div>
<Show when={file.type !== "delete"}>
<div data-component="apply-patch-file-diff">
<Dynamic
component={diffComponent}
before={{ name: file.filePath, contents: file.before }}
after={{ name: file.filePath, contents: file.after }}
/>
</div>
</Show>
</div>
)}
</For>
</div>
</Show>
</BasicTool>
)
},
})
ToolRegistry.register({
name: "todowrite",
render(props) {

View file

@ -5,6 +5,8 @@ import type { ComponentProps } from "solid-js"
export interface TooltipProps extends ComponentProps<typeof KobalteTooltip> {
value: JSX.Element
class?: string
contentClass?: string
contentStyle?: JSX.CSSProperties
inactive?: boolean
}
@ -30,7 +32,7 @@ export function TooltipKeybind(props: TooltipKeybindProps) {
export function Tooltip(props: TooltipProps) {
const [open, setOpen] = createSignal(false)
const [local, others] = splitProps(props, ["children", "class", "inactive"])
const [local, others] = splitProps(props, ["children", "class", "contentClass", "contentStyle", "inactive"])
const c = children(() => local.children)
@ -58,7 +60,12 @@ export function Tooltip(props: TooltipProps) {
{c()}
</KobalteTooltip.Trigger>
<KobalteTooltip.Portal>
<KobalteTooltip.Content data-component="tooltip" data-placement={props.placement}>
<KobalteTooltip.Content
data-component="tooltip"
data-placement={props.placement}
class={local.contentClass}
style={local.contentStyle}
>
{others.value}
{/* <KobalteTooltip.Arrow data-slot="tooltip-arrow" /> */}
</KobalteTooltip.Content>

View file

@ -24,7 +24,9 @@
@import "../components/icon.css" layer(components);
@import "../components/icon-button.css" layer(components);
@import "../components/image-preview.css" layer(components);
@import "../components/keybind.css" layer(components);
@import "../components/text-field.css" layer(components);
@import "../components/inline-input.css" layer(components);
@import "../components/list.css" layer(components);
@import "../components/logo.css" layer(components);
@import "../components/markdown.css" layer(components);