Skip to content

fix: render markdown in the Thinking block and never load images from model text - #11

Merged
LukeGus merged 3 commits into
Termix-SSH:dev-1.3.0from
Rhiz3K:fix/thinking-markdown
Oct 22, 2026
Merged

LukeGus merged 3 commits into
Termix-SSH:dev-1.3.0from
Rhiz3K:fix/thinking-markdown

Conversation

@Rhiz3K

@Rhiz3K Rhiz3K commented Oct 22, 2026

Copy link
Copy Markdown

Summary

The Thinking block printed the model's reasoning summary as plain text, so markdown showed up raw, e.g. **Evaluating AI assistant plugins**. It is now rendered as markdown. No new dependency.

Thinking and assistant replies now share one SafeMarkdown component: raw HTML is never rendered, and markdown images are never loaded, so model text that echoes host or tool output cannot make the browser fetch a remote URL (and leak data in it) just by being shown.

Base: dev-1.3.0. Independent of #9 and #10 (neither touches these files), so it can merge in any order.

Changes

  • src/frontend/markdown.tsx (new): SafeMarkdown renders react-markdown with remark-gfm and disallowedElements={["img"]}, without rehype-raw.
  • src/frontend/AiThinking.tsx: renders the reasoning with SafeMarkdown, with compact styling for the collapsed panel (tighter paragraph/list margins, code and pre styling like AiMessage). The toggle, the collapsed default, muted text-xs, max-h-64 scroll and left border are unchanged; whitespace-pre-wrap is dropped because markdown now handles line breaks.
  • src/frontend/AiMessage.tsx: assistant replies use SafeMarkdown (same wrapper and styling as before). Behaviour change: a markdown image in a reply is no longer shown. User messages are still plain text.
  • Table styling (separate style: commit): GFM tables were already unstyled on dev-1.3.0 (no table/th/td rules at all, so cells sat glued together, e.g. "AB / 12 / 34"); the rendered HTML before and after this PR's earlier commits is identical. SafeMarkdown now maps table/th/td:
    • the table sits in a my-2 max-w-full overflow-x-auto wrapper, so wide tables scroll horizontally instead of stretching the panel; the table is w-max border-collapse;
    • th: border border-border bg-muted px-2 py-1 text-left font-semibold; td: border border-border px-2 py-1 align-top;
    • GFM column alignment still applies (it comes as an inline text-align style), and react-markdown's node prop is not passed to the DOM.
    • All of these classes are present in the built dist/frontend.css (w-max, border-collapse, max-w-full and align-top are new rules).
  • Tests (new):
    • tests/frontend/AiThinking.test.tsx (4): bold renders as <strong> with no literal **; <img onerror> / <script> content creates no elements and runs nothing; markdown images are not loaded; collapsed by default.
    • tests/frontend/AiMessage.test.tsx (5): bold in a reply renders as <strong>; raw HTML creates no elements; markdown images are not loaded; a 2x2 table renders a <table> with 2 th and 4 td inside an overflow-x-auto wrapper, every cell with px-2 border border-border and no node attribute; user messages stay plain text.

Verification

Finish condition: **text** in a reasoning summary renders bold, raw HTML and images in model text never become elements, tables have padded, bordered cells and scroll when wide, and the repo's checks pass.

Commands (Node 24 per .nvmrc, same as CI):

  • tsc -p . ✅ · prettier --check . ✅
  • vitest run ✅: 88 files, 798 passed, 1 skipped (base dev-1.3.0: 86 files, 789 passed, 1 skipped)
  • termix-plugin test / typecheck / build / validate / pack ✅

The tests catch deliberately broken fixes (each applied alone, then reverted):

Mutation Result
Render {content} as plain text again "renders the thinking as markdown" failed
Render via dangerouslySetInnerHTML (bold via regex) "never turns raw HTML into elements" failed
Allow images in SafeMarkdown (disallowedElements={[]}) "does not load markdown images" failed in both test files
AiMessage back to plain ReactMarkdown AiMessage "does not load markdown images" failed
AiMessage via dangerouslySetInnerHTML AiMessage "never turns raw HTML into elements" failed
Drop the components mapping (tables unstyled again) table test failed
Drop overflow-x-auto from the table wrapper table test failed
Drop px-2 py-1 from td table test failed

Manual test

Throwaway Termix container (ghcr.io/termix-ssh/termix:latest) with these commits on top of #10, mounted as the bundled ai plugin: Loaded plugin ai@1.3.0, Activated plugin ai. Model gpt-6-luna.

Prompt: "Reply with exactly this markdown and nothing else: **bold text**, a bulleted list with two items, a small 2x2 table, ![test](https://example.com/test.png) and <b>html</b>". Result (two replies):

  • bold text rendered bold; the two-item list rendered as a bulleted list;
  • the table rendered with bordered, padded cells and a shaded header (before the style: commit it showed as "AB / 12 / 34");
  • the markdown image was not loaded and left no broken image;
  • <b>html</b> stayed literal text;
  • the expanded Thinking block showed its heading "Clarifying table requirements" in bold, without ** (an earlier run showed "Inspecting plugin versions" the same way).

Screenshots

Assistant replies with bold text, a list, a bordered 2x2 table, no loaded image, literal HTML, and an expanded Thinking block with a bold heading

Earlier run: Thinking heading rendered bold Expanded Thinking block with a bold heading

@termix-triage-bot

Copy link
Copy Markdown

AI review | Looks safe to merge

Introduces a shared SafeMarkdown component that renders model output with GFM support while explicitly blocking raw HTML and image loading to prevent XSS and data leakage. Adds table styling and comprehensive frontend tests without introducing new dependencies.

  • minor Plugin PRs should target main, not dev-1.3.0.

Reviewed 82ae156. AI can be wrong, a maintainer still decides. Comment /termix review to run it again.

@LukeGus
LukeGus merged commit 57680bd into Termix-SSH:dev-1.3.0 Oct 22, 2026
2 checks passed
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.

2 participants