← Back to PRs

#20419: fix(webchat): explicitly pass gfm and breaks options to marked.parse()

by Limitless2023 open 2026-02-18 22:10 View on GitHub →
app: web-ui size: XS
## Problem Markdown tables in Gateway WebChat render as raw pipe-separated text inside a `<p>` tag: ```html <!-- Actual (broken) --> <p>Text before. | Col1 | Col2 | |------|------| | A | B | Text after.</p> <!-- Expected --> <table><thead><tr><th>Col1</th><th>Col2</th>...</table> ``` ## Root Cause In **marked v7+**, passing _any_ options object to `marked.parse()` creates an **isolated options context** that does **not** inherit values set via `marked.setOptions()`. This means: ```ts marked.setOptions({ gfm: true, breaks: true }); // ← set globally // Later — the { renderer } argument creates an isolated context: marked.parse(text, { renderer: htmlEscapeRenderer }); // ← gfm silently resets to false! ``` When `gfm: false`, the GFM table extension is not loaded and table syntax passes through unparsed as inline text. **Reproducer:** ```ts marked.parse('| a | b |\n|---|---|\n| 1 | 2 |', { gfm: false }) // → '<p>| a | b |\n|---|---|\n| 1 | 2 |</p>' ✗ marked.parse('| a | b |\n|---|---|\n| 1 | 2 |', { gfm: true }) // → '<table>...</table>' ✓ ``` ## Fix Forward `gfm: true` and `breaks: true` explicitly in the `marked.parse()` call alongside the renderer so the options are always applied regardless of marked version behaviour. This also affects other GFM extensions: strikethrough, autolinks, task list items. Fixes #20410 <!-- greptile_comment --> <h3>Greptile Summary</h3> Fixes GFM markdown table rendering (and other GFM extensions like strikethrough, autolinks, task lists) in Gateway WebChat by explicitly passing `gfm: true` and `breaks: true` in the `marked.parse()` call in `ui/src/ui/markdown.ts`. - In `marked` v7+ (project uses `^17.0.3`), passing any options object to `marked.parse()` creates an isolated options context that does **not** inherit values from `marked.setOptions()`. The existing code passed `{ renderer: htmlEscapeRenderer }`, which silently reset `gfm` to `false`, causing tables to render as raw pipe-separated text. - The fix correctly forwards `gfm: true` and `breaks: true` alongside the renderer so the options are always applied. - The existing `marked.setOptions()` call is preserved for any un-parameterized `marked.parse()` calls elsewhere (e.g., `src/auto-reply/reply/export-html/template.js:1726`). - No test was added for the specific table rendering regression; a test like `expect(toSanitizedMarkdownHtml('| a | b |\n|---|---|\n| 1 | 2 |')).toContain('<table>')` would help prevent future regressions. <h3>Confidence Score: 5/5</h3> - This PR is safe to merge with minimal risk — it's a targeted, well-understood bug fix. - The change is small (2 added option properties + explanatory comments), correctly addresses a documented behavior change in marked v7+, and doesn't alter any logic or introduce new code paths. The project uses marked ^17.0.3, confirming the isolated options context behavior. No other marked.parse() calls with options objects exist in the codebase. - No files require special attention. <sub>Last reviewed commit: 063f52d</sub> <!-- greptile_other_comments_section --> <!-- /greptile_comment -->

Most Similar PRs