You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
bug: AI composer renders intra-word _ and * as italic/bold (e.g. my_file_name.js -> my<em>file</em>name.js) #1372
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.
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):
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.
Root cause. In appendMarkdown (packages/react/src/lib/contentEditableComposer.js), for each index the parser picks the first token whose marker matches with text.startsWith(marker, index), takes end = text.indexOf(marker, index + marker.length), and emits emphasis whenever end > 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 commonmark implementation. __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 appendMarkdown before 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.
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 commonmark package. I left the detail on #1375, but the part that matters before either lands:
The 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.
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.