Skip to content

[Improvement]: Code-split Monaco, Marketplace, AI chat, terminal, and Settings via next/dynamic#177

Open
matiaspalmac wants to merge 1 commit intoTrixtyAI:mainfrom
matiaspalmac:improvements/code-split
Open

[Improvement]: Code-split Monaco, Marketplace, AI chat, terminal, and Settings via next/dynamic#177
matiaspalmac wants to merge 1 commit intoTrixtyAI:mainfrom
matiaspalmac:improvements/code-split

Conversation

@matiaspalmac
Copy link
Copy Markdown
Contributor

Description

The desktop shell was pulling every heavy surface into the initial JavaScript bundle even though most of them are only reachable after the user makes a deliberate choice — open a file, open the AI panel, open the terminal, open the marketplace, open Settings. The result is a larger-than-necessary first paint, especially on cold startups where nothing except the title bar and activity bar is actually visible.

This PR routes the heavy modules through next/dynamic and plain await import() so they land in their own chunks and arrive only when needed.

Related Issue

Closes #81

Change

  • page.tsxEditorArea, BottomPanel, RightPanelSlot, and SettingsView are now next/dynamic({ ssr: false }) imports. ssr: false is correct for this app: it's always client-rendered inside Tauri and several of these components touch window at module scope.
  • EditorArea.tsxMonacoEditor (the ~1.5 MB gzipped beast, plus language workers) and MarketplaceView are now dynamic imports. Monaco's loader.config({ paths: { vs: "/vs" } }) is invoked inside the lazy factory so the asset path is wired up before the editor's first mount.
  • PluginManager.ts — built-in addons (AI assistant, Git explorer, and the five language addons) switched from top-level import * as to await import() inside bootstrap(). Language addons are kicked off in parallel so one slow chunk doesn't block the rest.

Trade-offs

  • Lazy-loading cost on first open: the very first time a user opens Monaco / the terminal / the AI panel / the marketplace / Settings there is now a small extra network or disk fetch. Inside Tauri these chunks ship from the local WebView bundle, so the cost is trivial — and subsequent opens are cached.
  • No loading states added: next/dynamic without a loading fallback shows nothing for a beat while the chunk resolves. For panels that are conditionally rendered behind a user action this is consistent with how they already appeared (nothing → panel). Introducing bespoke skeletons would be a follow-up that belongs with the wider UX pass.
  • ssr: false everywhere — intentional. Monaco's loader.config, xterm's imports, and Tauri-specific APIs assume a real browser. A hybrid "sometimes SSR" path would save nothing here and cost complexity.

Verification

  • pnpm --filter @trixty/desktop lint passes clean.
  • npx tsc --noEmit passes clean.
  • pnpm --filter @trixty/desktop build (Next 16 with Turbopack) compiles the production build and emits the static page without errors.
  • Spot-checked the five code-split modules in the build manifest: Monaco, xterm (via Terminal), MarketplaceView, AiChatComponent, and SettingsView each land in their own chunks.

Checklist

  • I have followed the project's coding guidelines.
  • Documentation has been updated (if applicable).
  • My changes generate no new warnings or errors.
  • This change is a minor improvement (for major new features use the Feature template).

…Settings

The desktop shell was pulling every heavy surface into the initial
JavaScript bundle even though most of them are only reachable after
the user has made a deliberate choice (open a file, open the AI
panel, open the terminal, open the marketplace, open Settings).

Route heavy modules through `next/dynamic` and dynamic `import()` so
they ship as their own chunks and arrive only when needed:

- `page.tsx` — `EditorArea`, `BottomPanel`, `RightPanelSlot`, and
  `SettingsView` are now dynamic imports (`ssr: false` because this
  app is always client-rendered inside Tauri and several components
  touch `window` at module scope).
- `EditorArea.tsx` — `MonacoEditor` and `MarketplaceView` are now
  dynamic imports. Monaco's loader config runs inside the lazy
  factory so the `/vs` path is still wired up before first mount.
- `PluginManager.ts` — built-in addons (AI assistant, Git explorer,
  five language addons) switched from top-level `import * as` to
  `await import()` inside `bootstrap()`. AiChat (which pulls
  react-markdown + remark-gfm) and GitExplorer (picomatch + git
  dialogs) are the biggest wins; language addons are kicked off in
  parallel so one slow chunk doesn't block the rest.

No behavior change for end users — same panels, same interactions,
just smaller first-paint cost. `next build` still produces a clean
static page.

Closes TrixtyAI#81
@github-actions github-actions bot added documentation Improvements or additions to documentation enhancement New feature or request labels Apr 21, 2026
@github-actions
Copy link
Copy Markdown

Thanks for the contribution! I'll review it as soon as possible. If you have still changes, please mark this PR as draft and all reviews will be cancelled. Tests reviews will be re-run only when the PR is marked as ready for review.

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

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Improvement]: Code-split Monaco, Marketplace, AiChat, Terminal and Settings via next/dynamic

1 participant