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
59 lines
2.2 KiB
TypeScript
59 lines
2.2 KiB
TypeScript
import { defineConfig, loadEnv } from 'vite'
|
|
import react from '@vitejs/plugin-react'
|
|
import tailwindcss from '@tailwindcss/vite'
|
|
import { fileURLToPath, URL } from 'node:url'
|
|
|
|
// In dev, Vite serves the SPA with HMR and proxies /api to the Go backend. The
|
|
// backend listens on PANSY_PORT (default 8080); read it so the proxy target
|
|
// tracks an overridden port. The production build is embedded into the Go binary
|
|
// (see internal/webdist), so no proxy is involved there.
|
|
export default defineConfig(({ mode }) => {
|
|
const env = loadEnv(mode, process.cwd(), '')
|
|
const backendPort = env.PANSY_PORT || process.env.PANSY_PORT || '8080'
|
|
return {
|
|
plugins: [react(), tailwindcss()],
|
|
resolve: {
|
|
alias: {
|
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
},
|
|
},
|
|
server: {
|
|
port: 5173,
|
|
proxy: {
|
|
'/api': {
|
|
target: `http://127.0.0.1:${backendPort}`,
|
|
changeOrigin: true,
|
|
},
|
|
},
|
|
},
|
|
build: {
|
|
outDir: 'dist',
|
|
sourcemap: true,
|
|
rollupOptions: {
|
|
output: {
|
|
// 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
|
|
if (
|
|
/[/\\]node_modules[/\\](react|react-dom|scheduler|@tanstack|zustand|zod|clsx|tailwind-merge)[/\\]/.test(
|
|
id,
|
|
)
|
|
) {
|
|
return 'vendor'
|
|
}
|
|
return undefined
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
})
|