Render the assistant's replies as Markdown
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
This commit is contained in:
+17
-14
@@ -31,22 +31,25 @@ export default defineConfig(({ mode }) => {
|
||||
sourcemap: true,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// One vendor chunk for ALL node_modules: it's rarely-changing, so it
|
||||
// caches across app deploys while the tiny app chunk churns. Kept as a
|
||||
// SINGLE chunk on purpose — splitting react-dom/scheduler into their own
|
||||
// chunk reorders their module init across chunk boundaries and breaks
|
||||
// React 19 at load ("Cannot set 'Activity' of undefined"). The heavy
|
||||
// routes are code-split separately via React.lazy (router.tsx), which is
|
||||
// where the real first-paint win is.
|
||||
// The app-wide core goes in ONE cached vendor chunk (rarely changes, so
|
||||
// it survives deploys while the tiny app chunk churns). react + react-dom
|
||||
// + scheduler MUST stay together — splitting react-dom into its own chunk
|
||||
// reorders module init across chunk boundaries and breaks React 19 at
|
||||
// load ("Cannot set 'Activity' of undefined"). Everything else — the
|
||||
// gesture engine, the assistant's markdown renderer + its ecosystem —
|
||||
// rides with whatever imports it, so a lazily-loaded route/feature keeps
|
||||
// it out of the eager first paint. The routes are code-split via
|
||||
// React.lazy (router.tsx), where the real first-paint win is.
|
||||
manualChunks(id) {
|
||||
if (!id.includes('node_modules')) return undefined
|
||||
// Editor-only libs (the gesture engine) ride with the lazy editor
|
||||
// chunk instead of the eager vendor one, so the first paint doesn't
|
||||
// pay for code only the canvas needs. Everything else — react, tanstack
|
||||
// and the rest — stays in ONE vendor chunk; splitting react-dom out
|
||||
// reorders its init across chunks and breaks React 19 at load.
|
||||
if (id.includes('@use-gesture')) return undefined
|
||||
return 'vendor'
|
||||
if (
|
||||
/[/\\]node_modules[/\\](react|react-dom|scheduler|@tanstack|zustand|zod|clsx|tailwind-merge)[/\\]/.test(
|
||||
id,
|
||||
)
|
||||
) {
|
||||
return 'vendor'
|
||||
}
|
||||
return undefined
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user