|
| 1 | +# Plan: Minimize Monaco Reloads on Thread Switch |
| 2 | + |
| 3 | +## Context |
| 4 | + |
| 5 | +Every time a user selects a different thread from the sidebar, all Monaco editor instances in the conversation unmount and remount. A typical thread has 5-20+ tool calls, each rendering a full Monaco editor instance via `DefaultTool` for read-only JSON/markdown display. This causes: |
| 6 | + |
| 7 | +- Monaco CDN scripts to be re-validated (NetworkFirst caching strategy) |
| 8 | +- Hundreds of DOM nodes per editor instance to be destroyed and recreated |
| 9 | +- Visible UI jank during thread transitions |
| 10 | + |
| 11 | +The root cause: `DefaultTool` uses the full Monaco Editor (`@monaco-editor/react`) just to display read-only snippets -- the heaviest code editor available used as a glorified `<pre>` tag. |
| 12 | + |
| 13 | +## Approach: Replace Monaco with Lightweight Syntax Highlighter in Chat Messages |
| 14 | + |
| 15 | +`react-syntax-highlighter` with Prism is **already installed and bundled** (used by `MarkdownCard.tsx`). Reusing it adds zero additional bundle cost. |
| 16 | + |
| 17 | +### Step 1: Modify `DefaultTool` to use `react-syntax-highlighter` |
| 18 | + |
| 19 | +**File:** `frontend/src/components/tools/Default.tsx` |
| 20 | + |
| 21 | +Replace the Monaco `Editor` import with `SyntaxHighlighter` from `react-syntax-highlighter`, following the exact pattern from `MarkdownCard.tsx` (lines 4-8, 42-48): |
| 22 | + |
| 23 | +```tsx |
| 24 | +// Replace: |
| 25 | +import Editor from "@monaco-editor/react"; |
| 26 | + |
| 27 | +// With: |
| 28 | +import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; |
| 29 | +import { oneDark, oneLight } from "react-syntax-highlighter/dist/esm/styles/prism"; |
| 30 | +``` |
| 31 | + |
| 32 | +Keep all existing content/language/height derivation logic (lines 17-62) unchanged. Only replace the render output: |
| 33 | + |
| 34 | +```tsx |
| 35 | +// Replace the <Editor .../> JSX with: |
| 36 | +<SyntaxHighlighter |
| 37 | + style={theme === "light" ? oneLight : oneDark} |
| 38 | + language={language} |
| 39 | + wrapLongLines={true} |
| 40 | + customStyle={{ |
| 41 | + margin: 0, |
| 42 | + fontSize: "11px", |
| 43 | + maxHeight: height, |
| 44 | + overflow: "auto", |
| 45 | + borderRadius: "0.375rem", |
| 46 | + }} |
| 47 | +> |
| 48 | + {content} |
| 49 | +</SyntaxHighlighter> |
| 50 | +``` |
| 51 | + |
| 52 | +### Step 2: Verify no breaking changes |
| 53 | + |
| 54 | +- `DefaultTool` props interface (`selectedToolMessage`, `collapsed`) stays the same |
| 55 | +- Consumers (`ChatMessages.tsx` line 171, `ToolTimelineItem.tsx` line 88) need zero changes |
| 56 | +- Visual output: Prism's `oneDark`/`oneLight` closely match Monaco's `vs-dark`/`light` themes |
| 57 | + |
| 58 | +### Step 3: Verify build |
| 59 | + |
| 60 | +Run `npm run build` to confirm: |
| 61 | +- Monaco chunk is no longer pulled into the chat message rendering path |
| 62 | +- Bundle size for chat path decreases significantly |
| 63 | +- No build errors |
| 64 | + |
| 65 | +### Optional Future Enhancements (not in this PR) |
| 66 | + |
| 67 | +1. **Lazy-load Monaco for FileEditorPanel** - Use `React.lazy()` for `MonacoEditor` component |
| 68 | +2. **Configure Monaco loader for local bundles** - Eliminate CDN dependency for remaining Monaco usages |
| 69 | +3. **Change PWA caching** to `CacheFirst` for Monaco scripts (versions are pinned) |
| 70 | + |
| 71 | +## Files to Modify |
| 72 | + |
| 73 | +| File | Change | |
| 74 | +|------|--------| |
| 75 | +| `frontend/src/components/tools/Default.tsx` | Replace Monaco with SyntaxHighlighter | |
| 76 | + |
| 77 | +## Files for Reference (no changes needed) |
| 78 | + |
| 79 | +| File | Why | |
| 80 | +|------|-----| |
| 81 | +| `frontend/src/components/cards/MarkdownCard.tsx` | Pattern to follow for SyntaxHighlighter usage | |
| 82 | +| `frontend/src/components/lists/ChatMessages.tsx` | Consumer - verify no breaking changes | |
| 83 | +| `frontend/src/components/timeline/ToolTimelineItem.tsx` | Consumer - verify no breaking changes | |
| 84 | +| `frontend/src/components/inputs/MonacoEditor.tsx` | Unchanged - still used for interactive editing | |
| 85 | +| `frontend/src/components/panels/FileEditorPanel.tsx` | Unchanged - legitimate Monaco use case | |
| 86 | + |
| 87 | +## Verification |
| 88 | + |
| 89 | +1. `npm run build` -- no errors, Monaco chunk not loaded on chat path |
| 90 | +2. `npm run test` -- existing tests pass |
| 91 | +3. Manual: switch between 3+ threads rapidly -- no Monaco network requests in DevTools |
| 92 | +4. Manual: expand tool outputs in chat -- JSON is syntax-highlighted correctly |
| 93 | +5. Manual: verify FileEditorPanel still works with full Monaco (split view mode) |
| 94 | +6. Manual: verify light/dark theme switching works for tool output highlighting |
0 commit comments