bug: AI composer renders intra-word _ and * as italic/bold (e.g. my_file_name.js -> my<em>file</em>name.js) #1372
Description
Description:
The AI-composer Markdown renderer (renderComposerMarkdown → appendMarkdown in packages/react/src/lib/contentEditableComposer.js) treats any _, *, __ or ** as an emphasis marker, even when it appears inside a word. As a result, AI suggestions that contain code identifiers, file paths, dunder names, or arithmetic are silently corrupted into <em>/<strong> when rendered inline in the composer.
- EmbeddedChat
@rocket.chat/embeddedchat-react0.2.2 - Commit
0654e5c7774b9762ed84d4a2bfde89b8f878a9d6(branchdevelop) - Node v24.0.0, OS macOS
Steps to reproduce:
-
Check out
developat commit0654e5c. -
Render a string through the composer's Markdown renderer — i.e. as an AI suggestion, which calls
renderComposerMarkdown(element, text). Minimal standalone repro (Node + jsdom, run from the repo root):import { JSDOM } from "jsdom"; const dom = new JSDOM(`<!DOCTYPE html><body></body>`); global.document = dom.window.document; global.NodeFilter = dom.window.NodeFilter; global.window = dom.window; const { renderComposerMarkdown } = await import( "./packages/react/src/lib/contentEditableComposer.js" ); const el = document.createElement("div"); renderComposerMarkdown(el, "my_file_name.js"); console.log(el.innerHTML);
-
Observe the emitted HTML.
Expected behavior:
Underscores/asterisks inside a word should not be treated as emphasis. my_file_name.js should render literally as my_file_name.js (matching CommonMark, where emphasis delimiters must be left/right-flanking, i.e. at word boundaries).
Actual behavior:
Intra-word markers are parsed as emphasis:
| Input | Rendered HTML |
|---|---|
my_file_name.js |
my<em>file</em>name.js |
__init__ |
<strong>init</strong> |
call foo_bar_baz() now |
call foo<em>bar</em>baz() now |
2 * 3 * 4 = 24 |
2 <em> 3 </em> 4 = 24 |
Searched before filing: open + closed issues for "composer markdown formatting", "italic bold asterisk", and "underscore". The closest existing issue is #1315 (parseEmoji converts only the last emoji shortname) — a different function, but the same class of hand-rolled inline-text parser producing wrong output. This composer intra-word emphasis case does not appear to be tracked yet.
Activity
ishan-one8 commentedon Oct 3, 2026
Root cause. In
appendMarkdown(packages/react/src/lib/contentEditableComposer.js), for each index the parser picks the first token whose marker matches withtext.startsWith(marker, index), takesend = text.indexOf(marker, index + marker.length), and emits emphasis wheneverend > index + marker.length. It never checks whether that delimiter run is allowed to open or close emphasis. CommonMark decides that with the left/right-flanking rules, and additionally forbids intra-word_. Without those checks, any_or*that has another one later in the line opens emphasis.A correction to my report. I re-checked all four examples against the reference
commonmarkimplementation.__init__→<strong>init</strong>is correct — those delimiters sit at word boundaries, so that example was my mistake. The other three are real:How I'd fix it. Add a flanking check in
appendMarkdownbefore a run is treated as an opener or closer: a run can't open if it's followed by whitespace, can't close if it's preceded by whitespace, and_additionally can't open or close inside a word. When the nearest matching marker can't close, keep scanning for the next one instead of giving up. It stays local to that function — the link, inline-code and<br>paths are untouched, and**bold**,*italic*and__init__keep working.Could I take this one? I'd ship the fix with tests that fail before it and pass after.
ishan-one8 commentedon Oct 3, 2026
@Spiral-Memory — heads up, since you've been asked to look at #1375: there are two open PRs for this issue and they overlap.
isValidEmphasisguard.I'm obviously not neutral here, so rather than argue for mine I'll just point at what I verified by checking out both branches and running them against the reference
commonmarkpackage. I left the detail on #1375, but the part that matters before either lands:developtoday**bold**text<strong>bold</strong>text*<em>bold</em>*textfoo*bar*bazfoo<em>bar</em>bazfoo*bar*bazThe first is a regression on current behaviour, and worth fixing whichever PR you take. The second is a genuine judgement call — CommonMark allows intra-word
*but not intra-word_, so treating them the same rejects valid emphasis.Happy to close #1376 if you'd rather land #1375 with those two cases handled; the tests in mine would port across easily. Flagging it only so the two don't get reviewed in isolation.