Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 28 additions & 20 deletions apps/desktop/src/api/PluginManager.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
import * as builtinAiAssistant from "@/addons/builtin.ai-assistant/index";
import * as builtinGitExplorer from "@/addons/builtin.git-explorer/index";
import * as builtinLanguageTypescript from "@/addons/builtin.language.typescript/index";
import * as builtinLanguagePython from "@/addons/builtin.language.python/index";
import * as builtinLanguageRust from "@/addons/builtin.language.rust/index";
import * as builtinLanguageHtml from "@/addons/builtin.language.html/index";
import * as builtinLanguageMarkdown from "@/addons/builtin.language.markdown/index";
import { registerBuiltinTranslations } from "./builtin.l10n";
import { logger } from "@/lib/logger";

Expand All @@ -26,32 +19,47 @@ export class PluginManager {
// Load translations first
registerBuiltinTranslations();

// Built-in addons are imported dynamically so they become their own
// chunks instead of riding in the initial bundle. AiChatComponent in
// particular pulls react-markdown + remark-gfm and GitExplorer pulls
// picomatch + git dialogs, so defer both until the user is past the
// first paint.
try {
builtinAiAssistant.activate();
const mod = await import("@/addons/builtin.ai-assistant/index");
mod.activate();
logger.debug("[PluginManager] builtin.ai-assistant activated.");
} catch (e) {
logger.error("Failed to activate AI assistant", e);
}

try {
builtinGitExplorer.activate();
const mod = await import("@/addons/builtin.git-explorer/index");
mod.activate();
logger.debug("[PluginManager] builtin.git-explorer activated.");
} catch (e) {
logger.error("Failed to activate Git Explorer", e);
}

// Language Addons
try {
builtinLanguageTypescript.activate(window.trixty);
builtinLanguagePython.activate(window.trixty);
builtinLanguageRust.activate(window.trixty);
builtinLanguageHtml.activate(window.trixty);
builtinLanguageMarkdown.activate(window.trixty);
// Language Addons — registered in parallel so one slow import doesn't
// block the rest of the bootstrap chain.
try {
const [ts, py, rs, html, md] = await Promise.all([
import("@/addons/builtin.language.typescript/index"),
import("@/addons/builtin.language.python/index"),
import("@/addons/builtin.language.rust/index"),
import("@/addons/builtin.language.html/index"),
import("@/addons/builtin.language.markdown/index"),
]);
ts.activate(window.trixty);
py.activate(window.trixty);
rs.activate(window.trixty);
html.activate(window.trixty);
md.activate(window.trixty);

logger.debug("[PluginManager] Built-in language addons activated.");
} catch (e) {
logger.error("Failed to activate language addons", e);
}
logger.debug("[PluginManager] Built-in language addons activated.");
} catch (e) {
logger.error("Failed to activate language addons", e);
}

// Dynamically load external scripts from Tauri File System
try {
Expand Down
15 changes: 11 additions & 4 deletions apps/desktop/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
"use client";

import React, { useEffect } from "react";
import dynamic from "next/dynamic";
import ActivityBar from "@/components/ActivityBar";
import LeftSidebarSlot from "@/components/slots/LeftSidebarSlot";
import EditorArea from "@/components/EditorArea";
import WelcomeScreen from "@/components/WelcomeScreen";
import RightPanelSlot from "@/components/slots/RightPanelSlot";
import BottomPanel from "@/components/BottomPanel";
import TitleBar from "@/components/TitleBar";
import SettingsView from "@/components/SettingsView";
import UpdaterDialog from "@/components/UpdaterDialog";
import OnboardingWizard from "@/components/OnboardingWizard";
import { ErrorBoundary } from "@/components/ErrorBoundary";
import { useApp } from "@/context/AppContext";
import { PluginManager } from "@/api/PluginManager";

// Code-split heavy panels so Monaco, xterm, Marketplace, AI chat, and the
// Settings modal aren't downloaded until the user actually opens them.
// `ssr: false` skips the RSC attempt — this app is always client-rendered
// inside Tauri, and several of these components touch `window` at module
// scope.
const EditorArea = dynamic(() => import("@/components/EditorArea"), { ssr: false });
const BottomPanel = dynamic(() => import("@/components/BottomPanel"), { ssr: false });
const RightPanelSlot = dynamic(() => import("@/components/slots/RightPanelSlot"), { ssr: false });
const SettingsView = dynamic(() => import("@/components/SettingsView"), { ssr: false });

export default function Home() {
const {
isRightPanelOpen,
Expand Down
28 changes: 20 additions & 8 deletions apps/desktop/src/components/EditorArea.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
"use client";

import React, { useRef } from "react";
import MonacoEditor, { OnMount, Monaco, loader } from "@monaco-editor/react";
import { editor } from "monaco-editor";

// Configure Monaco loader to use local assets
if (typeof window !== "undefined") {
loader.config({ paths: { vs: "/vs" } });
}
import dynamic from "next/dynamic";
import type { OnMount, Monaco } from "@monaco-editor/react";
import type { editor } from "monaco-editor";
import { useApp } from "@/context/AppContext";
import TabBar from "./TabBar";
import { useL10n } from "@/hooks/useL10n";
import MarketplaceView from "./MarketplaceView";
import { ErrorBoundary } from "./ErrorBoundary";

// Monaco is ~1.5 MB of gzipped JS and pulls language workers on top of that.
// Loading it with `next/dynamic` keeps it off the boot path; it only arrives
// once the user opens their first real file. The loader also needs `window`,
// so keep `ssr: false`.
const MonacoEditor = dynamic(
async () => {
const mod = await import("@monaco-editor/react");
mod.loader.config({ paths: { vs: "/vs" } });
return mod.default;
},
{ ssr: false },
);

// Marketplace is only reachable via the virtual `extensions` tab — no reason
// to ship it in the initial bundle next to Monaco.
const MarketplaceView = dynamic(() => import("./MarketplaceView"), { ssr: false });

const EditorArea: React.FC = () => {
const { currentFile, updateFileContent, openFiles, editorSettings } = useApp();
const { t } = useL10n();
Expand Down
Loading