fix(web): preserve XML-like tags in user messages - #4133
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6fc3bad. Configure here.
ApprovabilityVerdict: Needs human review This PR changes how HTML is rendered in user messages (escaping vs parsing), which affects runtime behavior in a security-sensitive area. The unresolved review comment identifies a real gap where block-level HTML tags cause line breaks to collapse and miss paragraph styling. You can customize Macroscope's approvability policy. Learn more. |
6fc3bad to
f0da0f7
Compare
Render user-authored XML-like source as escaped text while retaining sanitized assistant HTML, with regression coverage for custom tags, code, comparisons, and unsafe input. Co-authored-by: codex <codex@users.noreply.github.com>
Render user-authored XML-like source as escaped text while retaining sanitized assistant HTML, with regression coverage for custom tags, code, comparisons, and unsafe input. Co-authored-by: codex <codex@users.noreply.github.com>
79e6b9c to
6a9a0e8
Compare
Dismissing prior approval to re-evaluate 6a9a0e8
There was a problem hiding this comment.
One consistency issue found in the parseRawHtml switch: disabling the whole rehype chain also disables the sanitize schema, which is the only thing enforcing the project's no-native-title policy on rendered markdown. See the inline comment.
Posted via Macroscope — UI Consistency
Co-authored-by: codex <codex@users.noreply.github.com>
There was a problem hiding this comment.
One finding on the literal-HTML rendering path: block-level HTML in user messages now renders as an unwrapped, whitespace-collapsed text run. The title issue flagged on the previous commit is resolved by the new a/img renderers.
Posted via Macroscope — UI Consistency
| lineBreaks ? CHAT_MARKDOWN_REMARK_PLUGINS_WITH_BREAKS : CHAT_MARKDOWN_REMARK_PLUGINS | ||
| } | ||
| rehypePlugins={CHAT_MARKDOWN_REHYPE_PLUGINS} | ||
| rehypePlugins={parseRawHtml ? CHAT_MARKDOWN_REHYPE_PLUGINS : undefined} |
There was a problem hiding this comment.
With parseRawHtml={false} react-markdown replaces each raw node with one text node at the original node's position. That is fine for inline HTML (it stays inside the paragraph), but a block-level HTML node — a user message whose line starts with a tag, e.g.
<global-agent-instructions>
Do the thing
</global-agent-instructions>
is a single mdast html node covering the whole block, so the escaped source is emitted as a bare text node directly under .chat-markdown, outside any <p>. Two visible consequences: the typed newlines collapse to spaces (the default user-message path renders <ChatMarkdown> without a whitespace-pre-wrap ancestor, unlike the terminal-context path at MessagesTimeline.tsx:1776/1815, so the same message renders with different line structure depending on attached contexts), and the block gets none of the .chat-markdown p block spacing (index.css:1589). remarkBreaks cannot compensate because it only rewrites text nodes, not html nodes.
Smallest fix that keeps the literal-escaping goal: instead of relying on react-markdown's raw→text fallback, run a small remark plugin in literal mode (ordered before remarkBreaks) that rewrites mdast html nodes into text nodes, wrapping root-level ones in a paragraph. The escaping stays identical, and line breaks plus paragraph rhythm match the rest of the user message. Worth covering with a test for a multi-line HTML block, since the current tests only exercise single-line/inline cases.
Posted via Macroscope — UI Consistency
juliusmarminge
left a comment
There was a problem hiding this comment.
Re-approved after the focused regression fix and verification.
## What's Changed * fix(desktop): app zoom no longer zooms the preview browser by @juliusmarminge in pingdotgg/t3code#6649 * fix(server): keep provider notification consumers alive past startSession by @tsouth89 in pingdotgg/t3code#6538 * fix(server): treat removed Bitbucket permissions endpoint as unknown, not blocking by @lnieuwenhuis in pingdotgg/t3code#6525 * fix(ssh): let cold remote servers finish starting by @gbarros-dev in pingdotgg/t3code#6168 * fix(web): preserve Claude insight line breaks by @nateEc in pingdotgg/t3code#4344 * feat(web): accept file drops across the chat workspace by @dbalders in pingdotgg/t3code#6636 * fix(web): widen ordered-list marker gutter for 3+ digit item numbers by @lnieuwenhuis in pingdotgg/t3code#6527 * fix(server): bound thread activity hydration by @t3-code[bot] in pingdotgg/t3code#6153 * fix(web): restore the Archive action in the default sidebar thread menu by @lnieuwenhuis in pingdotgg/t3code#6526 * fix(web): open diff files from nested projects by @gbarros-dev in pingdotgg/t3code#6174 * fix(mobile): use tryOpenExternalUrl for markdown links in ThreadFeed by @mohamedmastouri-hue in pingdotgg/t3code#5872 * fix(web): open the file a bare filename reference names by @Brechard in pingdotgg/t3code#6297 * fix(server): stop the provider title mirror from overwriting real thread titles by @repparw in pingdotgg/t3code#5941 * fix(shared): match source-control providers by DNS label by @gbarros-dev in pingdotgg/t3code#6175 * feat(desktop): Chrome-style hold-to-quit by @Bil0000 in pingdotgg/t3code#5508 * fix(gitlab): submit review comments on context lines by @tarik02 in pingdotgg/t3code#6348 * fix(marketing): keep Grok mark clear of mobile hero copy by @NicL9923 in pingdotgg/t3code#4542 * fix(mobile): recover the QR pairing scanner when camera access is denied by @hey-jj in pingdotgg/t3code#6487 * fix(web): keep a long path from running under the folder picker button by @Sy-D in pingdotgg/t3code#4823 * fix(terminal): right-click paste works in the terminal by @StiensWout in pingdotgg/t3code#5240 * fix(mobile): explain iOS-only settings on Android by @danvernon in pingdotgg/t3code#4981 * fix(web): stop counting a workflow coordinator as a working agent by @Rishet11 in pingdotgg/t3code#6672 * fix(web): keep floating preview anchored after panel closes by @AksharP5 in pingdotgg/t3code#6547 * fix(web): unstick /connect after in-modal sign-in by redirecting to the authorize endpoint by @TorbenWetter in pingdotgg/t3code#5133 * fix(web): keep send reachable while a turn is running on mobile by @AMohamedAakhil in pingdotgg/t3code#4781 * fix(web): reject unsupported composer image types at attach time by @mdshzb04 in pingdotgg/t3code#6574 * Make ClaudeTextGeneration tests hermetic on Windows by @mihneaptu in pingdotgg/t3code#4508 * fix(web): show command output in work log by @LikoKiko in pingdotgg/t3code#4083 * fix(web): reserve sibling column width when resizing the right panel by @Lasdw6 in pingdotgg/t3code#6279 * fix(web): replace whitespace in new ref names with dashes by @jorj-pineda in pingdotgg/t3code#6270 * fix(client-runtime): branch list no longer resets while paging through refs by @a20hek in pingdotgg/t3code#5858 * fix(web): support Shift+Insert terminal paste by @aoright in pingdotgg/t3code#5982 * fix(web): keep the composer glass aligned with the context strip at any interface font size by @Williawar in pingdotgg/t3code#5703 * fix(codex): keep background memory out of chats by @AksharP5 in pingdotgg/t3code#5468 * fix(server): treat a missing Codex rollout as a recoverable resume error by @Rishet11 in pingdotgg/t3code#6671 * fix(web): hide provider Update toast action while an update is running by @mrmg in pingdotgg/t3code#6544 * fix(desktop): agent shells inherit a UTF-8 locale on macOS by @Linus-Boehm in pingdotgg/t3code#6236 * fix(server): ignore Claude command lifecycle messages by @naveed949 in pingdotgg/t3code#6606 * docs: mention Bitbucket user read scope needed by auth probe by @thamrx in pingdotgg/t3code#6291 * fix(server): return valid preview action results by @duncan-vc in pingdotgg/t3code#5966 * fix(claude): make "Always allow for session" stick, and only for the session by @kakismash in pingdotgg/t3code#5041 * fix(ssh): surface a failed remote t3 install instead of a silent 0-byte server.log by @TorbenWetter in pingdotgg/t3code#5132 * perf(server): persist the wire projection for streaming tool.updated data by @mInrOz in pingdotgg/t3code#6675 * fix(web): stop wrapping partial code block selections in markdown fences by @JoeJoeflyn in pingdotgg/t3code#5069 * fix(web): hide T3 Connect toggle in web app settings by @JoeJoeflyn in pingdotgg/t3code#5068 * fix(web): show provider account accent badge in sidebar rows and hover card by @vitalyiegorov in pingdotgg/t3code#5980 * fix(server): wait for concurrent SQLite writers instead of failing with SQLITE_BUSY by @ostapondo in pingdotgg/t3code#5134 * fix(web): reject oversized prompts before provider turn start by @naveed949 in pingdotgg/t3code#6602 * feat(web): collapse the question prompt from its header by @Jardo-51 in pingdotgg/t3code#6773 * fix(shared): degrade an unknown system time zone to UTC in usage windows by @Rishet11 in pingdotgg/t3code#6670 * fix(claude): discover repo-local .agents/skills in skill discovery by @RoshanMhatre in pingdotgg/t3code#5488 * fix(server): let slow provider CLIs raise their discovery probe budget by @CDVolvik in pingdotgg/t3code#6223 * fix(web): retain terminal PR badges after checkout switch by @sebbonit in pingdotgg/t3code#4755 * fix(web): show selected model in context window tooltip by @nqrwhal in pingdotgg/t3code#4772 * fix(web): scale command details with code font by @Serendeep in pingdotgg/t3code#6510 * fix(web): preserve XML-like tags in user messages by @0utsights in pingdotgg/t3code#4133 ## New Contributors * @mohamedmastouri-hue made their first contribution in pingdotgg/t3code#5872 * @NicL9923 made their first contribution in pingdotgg/t3code#4542 * @hey-jj made their first contribution in pingdotgg/t3code#6487 * @danvernon made their first contribution in pingdotgg/t3code#4981 * @Rishet11 made their first contribution in pingdotgg/t3code#6672 * @AksharP5 made their first contribution in pingdotgg/t3code#6547 * @TorbenWetter made their first contribution in pingdotgg/t3code#5133 * @AMohamedAakhil made their first contribution in pingdotgg/t3code#4781 * @mdshzb04 made their first contribution in pingdotgg/t3code#6574 * @mihneaptu made their first contribution in pingdotgg/t3code#4508 * @LikoKiko made their first contribution in pingdotgg/t3code#4083 * @Lasdw6 made their first contribution in pingdotgg/t3code#6279 * @jorj-pineda made their first contribution in pingdotgg/t3code#6270 * @a20hek made their first contribution in pingdotgg/t3code#5858 * @aoright made their first contribution in pingdotgg/t3code#5982 * @Williawar made their first contribution in pingdotgg/t3code#5703 * @mrmg made their first contribution in pingdotgg/t3code#6544 * @Linus-Boehm made their first contribution in pingdotgg/t3code#6236 * @naveed949 made their first contribution in pingdotgg/t3code#6606 * @thamrx made their first contribution in pingdotgg/t3code#6291 * @duncan-vc made their first contribution in pingdotgg/t3code#5966 * @kakismash made their first contribution in pingdotgg/t3code#5041 * @mInrOz made their first contribution in pingdotgg/t3code#6675 * @JoeJoeflyn made their first contribution in pingdotgg/t3code#5069 * @vitalyiegorov made their first contribution in pingdotgg/t3code#5980 * @ostapondo made their first contribution in pingdotgg/t3code#5134 * @Jardo-51 made their first contribution in pingdotgg/t3code#6773 * @RoshanMhatre made their first contribution in pingdotgg/t3code#5488 * @CDVolvik made their first contribution in pingdotgg/t3code#6223 * @sebbonit made their first contribution in pingdotgg/t3code#4755 * @nqrwhal made their first contribution in pingdotgg/t3code#4772 * @Serendeep made their first contribution in pingdotgg/t3code#6510 * @0utsights made their first contribution in pingdotgg/t3code#4133 **Full Changelog**: pingdotgg/t3code@v0.0.34-nightly.20260815.1100...v0.0.34-nightly.20260815.1101 Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.34-nightly.20260815.1101
Re-applies the deltas that mid-stack blob reverts discarded, and merges main's work into the v2-owned surfaces: - keybindings: main's STATIC_KEYBINDING_COMMANDS rename plus both new commands (rightPanel.toggleMaximized alongside threadPanel.toggle) - OpenInPicker: main's remote-open/SSH routing and favorite-editor shortcut layered onto the branch's panel/toolbar variants; the extracted shouldShowOpenInPicker now takes remoteOpenMode - ChatMarkdown: main's bare-filename resolver (#6297) ported into the branch's module-level component factory, plus #4133 title-attribute stripping on links and images - ComposerPrimaryActions: main's #4781 model (stop stays reachable, send joins it when Enter-to-send is unavailable) carrying the branch's steering send button - ComposerPendingUserInputPanel: main's collapsible redesign with the v2 RuntimeRequestId and responseCapability gate - ChatComposer: main's oversized-prompt submission guard wrapping the branch's dispatch-mode send - preview shell: main's container-aware width clamp ported into the branch's usePreviewPanelInlineSize hook - MessagesTimeline/Sidebar: main's day-aware timestamps, code-font tool bodies and provider accent badges on the v2 runtime shell - index.css: main's @variant dark migration (#6381) replaces the branch's standalone .dark block - contracts: main's send-turn image mime allowlist re-homed to chatAttachment.ts, where v2 keeps the other send-turn limits Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Summary
Root cause
ChatMarkdownalways enabled raw HTML parsing and sanitization. Unknown XML-like elements in user input, such as<global-agent-instructions>, were therefore interpreted as HTML and removed from the rendered message.User messages now opt out of raw HTML parsing, so their original text remains visible. Assistant messages keep the existing sanitized HTML behavior.
User impact
The rendered user message now matches what was typed instead of silently omitting XML-like tags. This only changes presentation; the message payload sent to the agent was already correct.
Fixes #4059.
Validation
pnpm exec vp test apps/web/src/components/chat/MessagesTimeline.test.tsx(14 tests passed)pnpm exec vp check(0 errors; 10 existing warnings)pnpm exec vp run typecheckNote
Preserve XML-like tags in user messages by disabling raw HTML parsing
parseRawHtml={false}on allChatMarkdownusages inMessagesTimeline.tsxthat render user-provided text, so XML-like tags and HTML are displayed as escaped source text rather than parsed.parseRawHtmlprop toChatMarkdown(defaults totrue) that conditionally applies rehype plugins; whenfalse, raw HTML is not processed.titleattributes from rendered<a>and<img>elements in markdown output.Macroscope summarized 9e2aa9b.
Note
Low Risk
Presentation-only change for user messages with a clear security win; assistant markdown behavior unchanged.
Overview
User chat messages now render HTML/XML-like syntax as escaped literal text instead of parsing it as HTML, so tags such as
<global-agent-instructions>and comparisons like2 < 3stay visible instead of disappearing.ChatMarkdowngains aparseRawHtmlflag (defaulttrue). Whenfalse, rehype raw HTML parsing/sanitization is skipped andskipHtml={false}keeps unparsed HTML as text. All user-messageChatMarkdownusages inMessagesTimelinepassparseRawHtml={false}; assistant messages keep sanitized raw HTML (e.g.<details>).Link/image renderers drop markdown
titleattributes from the DOM. Regression tests cover user literal tags, XSS as text, and assistant sanitized HTML.Reviewed by Cursor Bugbot for commit 9e2aa9b. Bugbot is set up for automated code reviews on this repo. Configure here.