diff --git a/CHANGELOG.md b/CHANGELOG.md index 74e1283135d..ab4602ff1e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -46,6 +46,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Control UI: point the Appearance tweakcn browse action and docs at the live tweakcn editor route instead of the removed `/themes` page. Fixes #77048. +- Control UI: render Dream Diary prose through the sanitized markdown pipeline, so diary bold/italic/header markdown no longer appears as literal source text. Fixes #62413. - Diagnostics: keep webhook/message OTEL attributes and Prometheus delivery labels low-cardinality and omit raw chat/message IDs from spans, so progress-draft and message-tool modes do not leak high-cardinality messaging identifiers. - Google Meet: stop advertising legacy `mode: "realtime"` to agents and config UIs, while keeping it as a hidden compatibility alias for `mode: "agent"`, so new joins use the STT -> OpenClaw agent -> TTS path instead of selecting the direct realtime voice fallback. - Google Meet: add `chrome.audioBufferBytes` for generated command-pair SoX audio commands and lower the default buffer from SoX's 8192 bytes to 4096 bytes to reduce Chrome talk-back latency. diff --git a/ui/src/ui/views/dreaming.test.ts b/ui/src/ui/views/dreaming.test.ts index 6cb1552cd77..6a2cd2cbfd5 100644 --- a/ui/src/ui/views/dreaming.test.ts +++ b/ui/src/ui/views/dreaming.test.ts @@ -384,6 +384,29 @@ describe("dreaming view", () => { setDreamSubTab("scene"); }); + it("renders dream diary markdown through the sanitized markdown pipeline", () => { + setDreamSubTab("diary"); + setDreamDiarySubTab("dreams"); + const container = renderInto( + buildProps({ + dreamDiaryContent: [ + "# Dream Diary", + "", + "---", + "", + "*April 8, 2026*", + "", + "**Bold** and *italic*", + ].join("\n"), + }), + ); + + const body = container.querySelector(".dreams-diary__para"); + expect(body?.querySelector("strong")?.textContent).toBe("Bold"); + expect(body?.querySelector("em")?.textContent).toBe("italic"); + setDreamSubTab("scene"); + }); + it("flattens structured backfill diary entries into plain prose", () => { setDreamSubTab("diary"); setDreamDiarySubTab("dreams"); diff --git a/ui/src/ui/views/dreaming.ts b/ui/src/ui/views/dreaming.ts index d654674c847..0c41af98e07 100644 --- a/ui/src/ui/views/dreaming.ts +++ b/ui/src/ui/views/dreaming.ts @@ -1,10 +1,12 @@ import { html, nothing } from "lit"; +import { unsafeHTML } from "lit/directives/unsafe-html.js"; import { t } from "../../i18n/index.ts"; import type { DreamingEntry, WikiImportInsights, WikiMemoryPalace, } from "../controllers/dreaming.ts"; +import { toSanitizedMarkdownHtml } from "../markdown.ts"; // ── Diary entry parser ───────────────────────────────────────────────── @@ -1326,7 +1328,7 @@ function renderDreamDiaryEntries(props: DreamingProps) { ${flattenDiaryBody(entry.body).map( (para, i) => html`

- ${para} + ${unsafeHTML(toSanitizedMarkdownHtml(para))}

`, )}