Skip to content

perf(desktop): load heavy libraries and panels on demand - #161

Merged
GoDiao merged 2 commits into
GoDiao:mainfrom
ibrahimmaq14:perf/lazy-load-heavy-modules
Oct 22, 2026
Merged

GoDiao merged 2 commits into
GoDiao:mainfrom
ibrahimmaq14:perf/lazy-load-heavy-modules

Conversation

@ibrahimmaq14

Copy link
Copy Markdown
Contributor

Closes #152

Summary

Heavy libraries and panels no longer load at startup; each one now loads the first time it is needed. Startup JavaScript drops by 54%.

Startup chunk sizes (bun run build in desktop/)

"Startup" means the entry chunk, plus every chunk Vite preloads for the bootstrap imports in main.tsx (App, ErrorBoundary, diagnosticsCapture, uiStore), followed transitively through static imports. This is the same walk the issue used to get its ~2.3 MB figure.

Before (main @ 939d934) After Change
App-*.js chunk 1,152.7 KB 391.9 KB −66%
Startup JS, total 2,307.8 KB (32 files) 1,055.2 KB (21 files) −54%
Startup JS, gzip 630.1 KB 300.7 KB −52%
Startup CSS 233.5 KB 205.3 KB −28 KB (KaTeX CSS)

Largest chunks that left the startup path: KaTeX (251 KB), Mermaid core and its dependencies (≈400 KB), react-diff-viewer with emotion (≈110 KB), the Settings pages including qrcode, Fuse, and the workspace panel.

What loads when

Piece Loads when While loading
Mermaid First time a diagram renders Existing "Rendering diagram..." state
KaTeX + katex.min.css First time a message contains math TeX source shown in place; the formula renders when KaTeX arrives
Diff body (react-diff-viewer) First diff shown in a tool call or permission prompt Header (path, +/−, copy) renders at once; a placeholder reserves the diff's height (capped at the existing 400px)
Doctor panel Error screens / Diagnostics tab Nothing; it sits below the error message
Settings pages Settings window first opens Spinner inside the fixed-size window; tab switches after that are instant
Workspace panel Panel first opened Spinner filling the side panel at its normal width
Fuse Session search first opens Search already shows its loading state while sessions are fetched

xterm was already dynamically imported in TerminalSettings. I left that path unchanged; it's now additionally behind the Settings and workspace boundaries.

Notes for review

  • Settings granularity. I split SettingsModal into pages/SettingsModal.tsx and lazy-load the whole Settings page, rather than lazy-loading each tab. The shell has to stay eager because it records the opener synchronously, before the window takes focus. Loading per tab would add a spinner on every tab switch and would have meant rewriting most of the settings tests. Settings keeps its export from pages/Settings.tsx, so existing imports work.
  • Doctor panel on error screens. It goes through LazyDoctorPanel, which has a tiny error boundary that renders nothing if the chunk fails to load. Without it, a failure inside ErrorBoundary's fallback would escape to the root and blank the window.
  • Failed chunk loads elsewhere. These go to the existing app ErrorBoundary, the same way a failed bootstrap import does in main.tsx today. Mermaid, KaTeX and Fuse reset their cached promise on failure so a later use retries. If you'd like an inline retry for the panels (e.g. for H5 access over a flaky LAN), I can add one.

Tests

  • bun run lint (tsc --noEmit): clean
  • bun run test -- --run: 114 files, 1,190 passed, 4 skipped (on main: 111 files, 1,183 passed)
  • bun run build: succeeds, with no new warnings

New tests:

  • MarkdownRenderer.lazyMath.test.tsx:
    • KaTeX isn't loaded for a reply without math.
    • TeX source shows while KaTeX loads, then renders.
    • TeX that looks like markup stays plain text while loading.
  • MermaidRenderer.test.tsx: mermaid isn't imported until a diagram renders, then initializes with securityLevel: 'strict'.
  • DiffViewer.test.tsx: the header renders immediately, the placeholder reserves height, and the body renders after loading.
  • LazyDoctorPanel.test.tsx: the panel renders after loading, and a failed chunk load leaves the surrounding error screen intact.

Updated tests: the KaTeX output tests, the SettingsModal tests, the ErrorBoundary Doctor test and the ActiveSession workspace test now wait for the lazily loaded piece before asserting.

I did not run the server tests (bun test at the root), since this PR only touches desktop/. CI covers them.

Manual check (Windows 11, browser mode from docs/WEB_USAGE.md)

  • Production build (vite build + vite preview, backend with an isolated DREAMCODER_CONFIG_DIR):
    • On first load, none of the KaTeX, Mermaid, diff, Fuse, Settings or workspace chunks were requested.
    • Opening Settings fetched the Settings chunk and rendered Providers.
    • Diagnostics fetched DoctorPanel and rendered it.
  • Vite dev server: mounted the real MarkdownRenderer (inline + display math and a mermaid block) and DiffViewer in the page.
    • First paint showed the TeX source, "Rendering diagram..." and the diff placeholder.
    • After loading, both formulas rendered with KaTeX fonts (the CSS loaded), Mermaid drew its SVG, and the diff body appeared.
    • No console errors.
  • Not checked by hand:
    • The terminal, which needs the packaged desktop runtime.
    • The workspace panel inside a live session (covered by the updated ActiveSession test).
    • A Tauri build on macOS.

Startup JavaScript (entry plus the bootstrap chunks) drops from 2,307.8 KB
to 1,055.2 KB (630.1 KB to 300.7 KB gzip), measured from `bun run build`.

- Mermaid loads with a dynamic import the first time a diagram renders.
- KaTeX and its stylesheet load the first time a message contains math;
  until then the TeX source is shown in place.
- The diff body (react-diff-viewer) loads on first use; the header renders
  at once and a placeholder reserves the diff's height.
- The Doctor panel loads on demand on the error screens, guarded so a
  failed chunk load cannot break the screen that is reporting an error.
- The Settings pages load when the Settings window first opens. The modal
  shell moves to pages/SettingsModal.tsx and stays eager so it still
  remembers the opener before taking focus.
- The workspace panel loads the first time it is opened.
- Fuse loads when session search opens.

Closes GoDiao#152
Copilot AI balanced review requested due to automatic review settings October 22, 2026 12:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@GoDiao GoDiao left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this, it's a really thorough first PR! We checked that the lazy boundaries are complete, and none of the moved libraries or panels are still pulled in statically from the startup path. We also checked the math fallback: it escapes TeX while KaTeX loads and re-renders once it arrives. The Settings split keeps the opener and focus handling in the eager shell. The loaders share one promise and reset it on failure. The tests would actually catch a regression, and the before/after tables made this easy to review.

One thing before merging: please remove the comments. We keep new code free of comments, JSDoc included. That rule isn't in CONTRIBUTING yet, which is on us. The ones added in this PR are:

  • components/chat/DiffViewer.tsx L6-7
  • components/chat/DiffViewerBody.tsx L112-113
  • components/chat/MermaidRenderer.tsx L30, L44
  • components/doctor/LazyDoctorPanel.tsx L12-13, L26
  • components/markdown/MarkdownRenderer.tsx L239, L478, and the trailing comment on L510 (keep the katexReady dep)
  • hooks/useSessionSearch.ts L13, L41
  • lib/katexLoader.ts L8, L13-16, L27
  • pages/ActiveSession.tsx L36
  • pages/SettingsModal.tsx L9-10 (the three comments moved over from Settings.tsx can stay)
  • tests: DiffViewer.test.tsx L19, LazyDoctorPanel.test.tsx L39, MarkdownRenderer.test.tsx L29, ActiveSession.test.tsx L600, SettingsModal.test.tsx L26

On your question about inline retry for the panels: no need. In the desktop app the chunks load from local files, and in H5 mode the app ErrorBoundary's reload already recovers.

No need to touch the CHANGELOG, we'll add the entry after merging. Once the comments are gone, we'll merge. Thanks again!

@ibrahimmaq14

Copy link
Copy Markdown
Contributor Author

Thanks for the review! I've removed all the listed comments in 1d10198. The three that moved over from Settings.tsx into SettingsModal.tsx are still there, as you said, and the katexReady dependency stays. I also moved the lazy WorkspacePanel declaration above the existing constants in ActiveSession.tsx, so that block now matches main again. Typecheck, the desktop tests (114 files, 1,190 passed) and bun run build all pass locally.

@ibrahimmaq14
ibrahimmaq14 requested a review from GoDiao October 22, 2026 14:14
@GoDiao
GoDiao merged commit 49b9bc0 into GoDiao:main Oct 22, 2026
4 checks passed
GoDiao added a commit that referenced this pull request Oct 22, 2026
@GoDiao

GoDiao commented Oct 22, 2026

Copy link
Copy Markdown
Owner

Merged, thanks @ibrahimmaq14! Startup JavaScript cut roughly in half is a big win, and the write-up with the before/after tables made this a pleasure to review. Thanks also for turning the cleanup around so fast. If you enjoy DreamCoder, a ⭐ on the repo would mean a lot to us. Hope to see more PRs from you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Load heavy libraries and panels on demand to cut desktop startup JavaScript

3 participants