Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions packages/ui/src/components/basic-tool-defer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expect, test } from "bun:test"
import { basicToolInitialReady } from "./basic-tool"

test("deferred default-open tools do not mount details immediately", () => {
expect(basicToolInitialReady({ defaultOpen: true, defer: true })).toBe(false)
})

test("non-deferred default-open tools keep the previous immediate details behavior", () => {
expect(basicToolInitialReady({ defaultOpen: true })).toBe(true)
expect(basicToolInitialReady({ defaultOpen: true, defer: false })).toBe(true)
})

test("closed tools start without mounted details", () => {
expect(basicToolInitialReady({ defaultOpen: false, defer: true })).toBe(false)
expect(basicToolInitialReady({ defaultOpen: false })).toBe(false)
expect(basicToolInitialReady({ defer: true })).toBe(false)
expect(basicToolInitialReady({})).toBe(false)
})
42 changes: 21 additions & 21 deletions packages/ui/src/components/basic-tool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,15 @@ export interface BasicToolProps {

const SPRING = { type: "spring" as const, visualDuration: 0.35, bounce: 0 }

export function basicToolInitialReady(props: { defaultOpen?: boolean; defer?: boolean }) {
if (props.defer) return false
return props.defaultOpen ?? false
}

export function BasicTool(props: BasicToolProps) {
const [state, setState] = createStore({
open: props.defaultOpen ?? false,
ready: props.defaultOpen ?? false,
ready: basicToolInitialReady(props),
})
const open = () => state.open
const ready = () => state.ready
Expand All @@ -64,27 +69,22 @@ export function BasicTool(props: BasicToolProps) {
if (props.forceOpen) setState("open", true)
})

createEffect(
on(
open,
(value) => {
if (!props.defer) return
if (!value) {
cancel()
setState("ready", false)
return
}
createEffect(() => {
if (!props.defer) return
if (!open()) {
cancel()
setState("ready", false)
Comment thread
Astro-Han marked this conversation as resolved.
Outdated
return
}
if (ready()) return
Comment thread
Astro-Han marked this conversation as resolved.
Outdated

cancel()
frame = requestAnimationFrame(() => {
frame = undefined
if (!open()) return
setState("ready", true)
})
},
{ defer: true },
),
)
cancel()
frame = requestAnimationFrame(() => {
frame = undefined
if (!open()) return
setState("ready", true)
})
})

// Animated height for collapsible open/close
let contentRef: HTMLDivElement | undefined
Expand Down
17 changes: 15 additions & 2 deletions packages/ui/src/components/message-part-registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ function readMessagePartSources() {
].join("\n")
}

function readToolSource(file: string) {
return readFileSync(join(MESSAGE_PART_DIR, "tools", file), "utf8")
}

test("message-part internals do not import through the facade", () => {
const offenders = sourceFiles(MESSAGE_PART_DIR)
.map((file) => ({
Expand Down Expand Up @@ -109,12 +113,21 @@ test("part and tool side-effect barrels cover every registered renderer", () =>

test("split keeps hidden tools and deferred heavy tool bodies explicit", () => {
const source = readMessagePartSources()
const deferCount = [...source.matchAll(/\bdefer\b/g)].length

expect(source).toContain('export const HIDDEN_TOOLS = new Set(["todowrite"])')
expect(source).toContain('if (tool === "edit" || tool === "write" || tool === "apply_patch") return edit')
expect(source).toContain("defaultOpen={completed()}")
expect(deferCount).toBe(4)

const deferredHeavyTools = {
"bash.tsx": 1,
"edit.tsx": 1,
"write.tsx": 1,
"apply-patch.tsx": 2,
} as const

for (const [file, count] of Object.entries(deferredHeavyTools)) {
expect([...readToolSource(file).matchAll(/\bdefer\b/g)].length).toBe(count)
}
})

test("review hardening keeps routing, clipboard, url, and write guards explicit", () => {
Expand Down
1 change: 1 addition & 0 deletions packages/ui/src/components/message-part/tools/bash.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ ToolRegistry.register({
<BasicTool
{...props}
icon="console"
defer
trigger={
<div data-slot="basic-tool-tool-info-structured">
<div data-slot="basic-tool-tool-info-main">
Expand Down
Loading