Skip to content

Render markdown in Vue chat TextContent#730

Open
shogun444 wants to merge 11 commits into
thesysdev:mainfrom
shogun444:fix/vue-chat-textcontent-markdown-728
Open

Render markdown in Vue chat TextContent#730
shogun444 wants to merge 11 commits into
thesysdev:mainfrom
shogun444:fix/vue-chat-textcontent-markdown-728

Conversation

@shogun444

@shogun444 shogun444 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #728

What

The Vue chat example claims that TextContent supports markdown, but the component rendered text using plain Vue interpolation, causing markdown syntax (e.g. **NVDA**) to appear literally instead of being formatted.

This PR updates TextContent to render markdown as the prompt already describes.

Changes

  • Parse TextContent using marked
  • Sanitize the generated HTML with DOMPurify
  • Render the sanitized HTML using v-html
  • Add marked and dompurify as direct dependencies for the Vue chat example

Why

The issue proposed two possible fixes:

  • Render markdown correctly
  • Remove markdown support from the prompt

This PR chooses the first approach so the implementation matches the documented behavior instead of reducing functionality.

The change is intentionally scoped to examples/vue-chat only. No changes were made to @openuidev/vue-lang since the runtime is functioning correctly; the issue was limited to the example's rendering implementation.

Test Plan

  • Plain text rendering is unchanged
  • HTML is sanitized before rendering (DOMPurify)

Checklist

  • I linked a related issue
  • I considered backwards compatibility
  • No documentation changes required

@AlexQuidditch

Copy link
Copy Markdown

I would be happy to see this feature merged asap 🙏

@vishxrad vishxrad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing #728. I am requesting changes for two issues before merge: model-produced text should stay within a Markdown-only rendering surface, and the component should consistently support either inline Markdown or correctly structured and styled block Markdown. The dependency and lockfile changes look good, and both CI jobs pass.


const html = computed(() => {
const raw = marked.parse(props.text ?? "", { async: false });
return DOMPurify.sanitize(raw);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Please disable or escape raw HTML before sanitizing. marked.parse() preserves embedded HTML, while the default DOMPurify policy still permits forms, inputs, external form actions and images, and fixed-position inline styles; a model response can therefore render UI outside the component library controls. HTML-looking plain text such as <script> also loses content instead of being displayed. Prefer treating Marked HTML tokens as text, then keep DOMPurify with a Markdown-specific tag and attribute allowlist.


<template>
<p class="text-sm leading-relaxed text-zinc-700 dark:text-zinc-300">{{ props.text ?? "" }}</p>
<p class="text-sm leading-relaxed text-zinc-700 dark:text-zinc-300" v-html="html"></p>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] marked.parse() emits block elements; even plain text becomes

...

, so injecting the result into an outer

creates invalid nested or block markup. Tailwind preflight also removes list markers, heading and link styling, and default spacing, leaving most block Markdown visually unformatted. If this component only needs inline formatting, use marked.parseInline(); if it should support full Markdown, use a

host and add descendant Markdown styles.

@AlexQuidditch

Copy link
Copy Markdown

@shogun444 maybe https://github.com/Simon-He95/markstream-vue would be more suitable for this case

@shogun444

shogun444 commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

@AlexQuidditch Thanks for the suggestion! markstream-vue looks great for streaming use cases. For now I've addressed the @vishxrad feedback with a minimal fix (HTML escaping, proper block structure, scoped markdown styles). If there's interest in a fuller streaming markdown solution for the Vue chat example, that could be a follow-up.

Sorry for the late PR.

@AlexQuidditch

Copy link
Copy Markdown

@shogun444

I've implemented markstream-vue into my fork with vue-headless package. It's working great.

Screenshot 2026-07-15 at 13 12 46

@vishxrad vishxrad left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on head 49818825.

  • [P1] TextContent.vue:11 — all TextContent output currently throws. With locked marked@17.0.5, the per-call renderer option replaces the complete renderer; this partial { html() {} } object is not merged with the defaults. Even marked.parse("hello", options) calls the missing paragraph() method and throws TypeError: this.renderer.paragraph is not a function. In the running Vue example, submitting What is Vue? renders headings and buttons but no body copy, and the DOM contains zero .markdown-content nodes. Use a complete Renderer instance with html overridden, or configure a dedicated Marked instance through .use({ renderer: ... }).

  • [P2] TextContent.vue:29 — dark-mode body text regresses. The new wrapper removes text-sm leading-relaxed text-zinc-700 dark:text-zinc-300. Neither Card nor Stack supplies a body-text color, while the page uses dark:bg-zinc-950; after fixing the parser, ordinary Markdown will inherit black and be unreadable in dark mode. Restore the base typography and light/dark text colors.

  • [P2] TextContent.vue:20 — Markdown headings have no presentation. h1h6 are allowed through sanitization, but there are no heading rules. Tailwind preflight resets heading size and weight to inherit, so headings render like body text. Add scoped heading typography and spacing.

Please add regression coverage for plain text, bold/inline-code Markdown, and escaped raw HTML. markstream-vue looks worth evaluating separately for streaming Markdown UX, but adopting it needs streaming-state wiring, an explicit escaped-HTML policy, CSS ordering, and bundle/visual QA; it is not required for this focused fix.

@shogun444
shogun444 force-pushed the fix/vue-chat-textcontent-markdown-728 branch from c12273c to e305766 Compare July 15, 2026 18:58
@vishxrad

Copy link
Copy Markdown
Contributor

@shogun444 Thanks — the runtime, security, and styling issues from my review are fixed on e305766.

One small follow-up remains before I can approve: the new TextContent.test.ts suite is currently not runnable. The latest commit removed the test script and the required vitest, @vitejs/plugin-vue, @vue/test-utils, and jsdom dev dependencies. As a result, pnpm --filter vue-chat exec vitest run fails with Command "vitest" not found, and the current CI jobs only build the examples.

Could you please:

  1. Restore the test dev dependencies and update pnpm-lock.yaml.
  2. Add a script such as "test": "nuxt prepare && vitest run" (nuxt prepare is needed for .nuxt/tsconfig.json on a clean checkout).
  3. Run pnpm --filter vue-chat test in CI.

I verified that all four tests pass once the dependencies are installed and Nuxt is prepared. After that, I’m good to approve.

@shogun444
shogun444 force-pushed the fix/vue-chat-textcontent-markdown-728 branch from e305766 to e0c2b23 Compare July 16, 2026 06:00
shogun444 added a commit to shogun444/openui that referenced this pull request Jul 16, 2026
Parse TextContent text using marked, sanitize with DOMPurify,
render via v-html. Uses a complete Renderer instance with
html() overridden to escape raw HTML. Scoped markdown styles
added for headings, lists, code, blockquotes, tables, etc.
Includes regression tests for plain text, bold, inline code,
and raw HTML escaping.

Closes thesysdev#728
@shogun444
shogun444 force-pushed the fix/vue-chat-textcontent-markdown-728 branch from e0c2b23 to b74201a Compare July 16, 2026 06:02
Parse TextContent text using marked, sanitize with DOMPurify,
render via v-html. Uses a complete Renderer instance with
html() overridden to escape raw HTML. Scoped markdown styles
added for headings, lists, code, blockquotes, tables, etc.
Includes regression tests for plain text, bold, inline code,
and raw HTML escaping.

Closes thesysdev#728
@shogun444
shogun444 force-pushed the fix/vue-chat-textcontent-markdown-728 branch from b74201a to f3b4c53 Compare July 16, 2026 06:15
@shogun444
shogun444 force-pushed the fix/vue-chat-textcontent-markdown-728 branch from 44717c1 to 1c8dfbd Compare July 16, 2026 08:18
@shogun444

Copy link
Copy Markdown
Contributor Author

Adding multiple providers in the vue-chat could be a follow up similar to the openui-chat so user's can bring in their own API key and play with it.
Rightnow, it only supports OpenAI models.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Vue chat TextContent claims markdown support but renders markdown literally

3 participants