Skip to content

bug: AI composer renders intra-word _ and * as italic/bold (e.g. my_file_name.js -> my<em>file</em>name.js) #1372

Description

@ishan-one8

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-react 0.2.2
  • Commit 0654e5c7774b9762ed84d4a2bfde89b8f878a9d6 (branch develop)
  • Node v24.0.0, OS macOS

Steps to reproduce:

  1. Check out develop at commit 0654e5c.

  2. 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);
  3. 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

  1. ishan-one8 commented on Oct 3, 2026

    @ishan-one8
    Author

    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:

    "my_file_name.js"        => <p>my_file_name.js</p>
    "call foo_bar_baz() now" => <p>call foo_bar_baz() now</p>
    "2 * 3 * 4 = 24"         => <p>2 * 3 * 4 = 24</p>
    

    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.

  2. ishan-one8 commented on Oct 3, 2026

    @ishan-one8
    Author

    @Spiral-Memory — heads up, since you've been asked to look at #1375: there are two open PRs for this issue and they overlap.

    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:

    Input develop today #1375
    **bold**text <strong>bold</strong>text *<em>bold</em>*text
    foo*bar*baz foo<em>bar</em>baz foo*bar*baz

    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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions