web: pretty tool cards — args rendered as the real command/path (priority keys, label-less single field), output label; 276 tests green

This commit is contained in:
Raphael Westphal
2026-08-20 14:55:55 +02:00
parent 2d0839357d
commit bdfa61a8b2
4 changed files with 128 additions and 8 deletions
+44
View File
@@ -6,6 +6,7 @@ import ChatStream, {
Bubble,
TypingIndicator,
renderMarkdown,
toolArgsEntries,
} from "./ChatStream";
function msg(partial: Partial<ChatMessage>): ChatMessage {
@@ -761,3 +762,46 @@ describe("copy button", () => {
expect(screen.queryByRole("button", { name: "Copy message" })).toBeNull();
});
});
describe("pretty tool args", () => {
const tool = (args: string): ToolState => ({
id: "t1", name: "bash", args, running: false, isError: false, preview: "out",
});
it("bash command renders as the bare command, no JSON braces", () => {
render(<Bubble msg={{ key: "k", role: "assistant", text: "", thinking: null,
toolCalls: [{ id: "t1", name: "bash", argsJson: "{}" }], toolCallId: null, streaming: false, ts: 0 }}
tools={new Map([["t1", tool('{"command":"ls -la","foo":1}')]])} />);
expect(screen.getByText("ls -la")).toBeInTheDocument();
expect(document.querySelector(".tool-args")?.textContent).not.toContain('"command"');
});
it("priority ordering puts command first even when not first in JSON", () => {
const entries = toolArgsEntries('{"path":"a.go","command":"go build ./..."}');
expect(entries[0]?.v).toBe("go build ./...");
expect(entries[1]?.k).toBe("path");
});
it("single string arg renders label-less; multi-field keeps labels", () => {
render(<Bubble msg={{ key: "k", role: "assistant", text: "", thinking: null,
toolCalls: [{ id: "t1", name: "read", argsJson: "{}" }], toolCallId: null, streaming: false, ts: 0 }}
tools={new Map([["t1", tool('{"path":"src/main.ts"}')]])} />);
expect(screen.getByText("src/main.ts")).toBeInTheDocument();
expect(document.querySelector(".tool-args .arg-k")).toBeNull();
});
it("non-JSON args pass through raw", () => {
const entries = toolArgsEntries("just text");
expect(entries).toEqual([{ k: null, v: "just text" }]);
});
it("JSON string arg unwraps", () => {
const entries = toolArgsEntries('"plain string"');
expect(entries).toEqual([{ k: null, v: "plain string" }]);
});
it("non-string values are pretty JSON", () => {
const entries = toolArgsEntries('{"offset":1}');
expect(entries[0]?.v).toBe("1");
});
});