From 2bbf2698cb1448d49507418ae013f6eb4da96cc3 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 5 Jan 2026 01:22:19 +0100 Subject: [PATCH] fix(ui): render markdown in tool result cards --- CHANGELOG.md | 1 + ui/src/ui/chat-markdown.browser.test.ts | 50 +++++++++++++++++++++++++ ui/src/ui/views/chat.ts | 4 +- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 ui/src/ui/chat-markdown.browser.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index b7db22b5f..4f2eca491 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Docs: document built-in model shorthands + precedence (user config wins). ### Fixes +- Control UI: render Markdown in tool result cards. - Control UI: prevent overlapping action buttons in Discord guild rules on narrow layouts. - Android: tapping the foreground service notification brings the app to the front. (#179) — thanks @Syhids - Cron tool passes `id` to the gateway for update/remove/run/runs (keeps `jobId` input). (#180) — thanks @adamgall diff --git a/ui/src/ui/chat-markdown.browser.test.ts b/ui/src/ui/chat-markdown.browser.test.ts new file mode 100644 index 000000000..5d8a3ff57 --- /dev/null +++ b/ui/src/ui/chat-markdown.browser.test.ts @@ -0,0 +1,50 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +import { ClawdbotApp } from "./app"; + +const originalConnect = ClawdbotApp.prototype.connect; + +function mountApp(pathname: string) { + window.history.replaceState({}, "", pathname); + const app = document.createElement("clawdbot-app") as ClawdbotApp; + document.body.append(app); + return app; +} + +beforeEach(() => { + ClawdbotApp.prototype.connect = () => { + // no-op: avoid real gateway WS connections in browser tests + }; + window.__CLAWDBOT_CONTROL_UI_BASE_PATH__ = undefined; + document.body.innerHTML = ""; +}); + +afterEach(() => { + ClawdbotApp.prototype.connect = originalConnect; + window.__CLAWDBOT_CONTROL_UI_BASE_PATH__ = undefined; + document.body.innerHTML = ""; +}); + +describe("chat markdown rendering", () => { + it("renders markdown inside tool result cards", async () => { + const app = mountApp("/chat"); + await app.updateComplete; + + app.chatMessages = [ + { + role: "assistant", + content: [ + { type: "toolcall", name: "noop", arguments: {} }, + { type: "toolresult", name: "noop", text: "Hello **world**" }, + ], + timestamp: Date.now(), + }, + ]; + + await app.updateComplete; + + const strong = app.querySelector(".chat-tool-card__output strong"); + expect(strong?.textContent).toBe("world"); + }); +}); + diff --git a/ui/src/ui/views/chat.ts b/ui/src/ui/views/chat.ts index 830cccf58..90660d319 100644 --- a/ui/src/ui/views/chat.ts +++ b/ui/src/ui/views/chat.ts @@ -289,7 +289,9 @@ function renderToolCard(card: ToolCard) { ? html`
${detail}
` : nothing} ${card.text - ? html`
${card.text}
` + ? html`
+ ${unsafeHTML(toSanitizedMarkdownHtml(card.text))} +
` : nothing} `;