The assistant answers in Markdown — tables, lists, bold, links — but the
chat bubble showed the raw source (whitespace-pre-wrap), so a table came
through as a wall of pipes.
- New MarkdownMessage: react-markdown + remark-gfm (for GFM tables). It does
NOT enable raw HTML (no rehype-raw), so a model reply can't inject markup;
every element the assistant actually uses is Tailwind-styled for a chat
bubble, and wide content (tables, code) scrolls in its own box so the
bubble can't blow out.
- Only ASSISTANT bubbles render as Markdown; the user's own text stays
literal (their `*` shouldn't become a bullet) and the assistant bubble goes
full-width so a table has room.
- Lazy-loaded: the renderer + its ecosystem (~150 KB) is its own async chunk,
loaded only when an assistant message renders — not for everyone who opens
the editor. To keep it out of the eager first paint, manualChunks now
vendors only the app-wide core (react/react-dom/scheduler kept together —
splitting react-dom breaks React 19 at load) and lets everything else ride
with its importer.
Tested via renderToStaticMarkup (node, no DOM): a GFM table renders with
styled cells + horizontal scroll; **bold**/lists render; raw <script>/<b> are
escaped not emitted; links get rel="noopener noreferrer". App re-verified to
mount cleanly with the re-chunk (no React-19 init-order break). tsc + vitest
(99) + build green, no >500 KB warning.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01H3zbym8Doka2d7D48maSgZ