perf: improve startup and runtime performance

Tier 1 performance optimizations for snappier app experience:

1. Lazy-load SettingsModal in App.tsx
   - SettingsModal is ~2.6K lines, now only loaded when settings opened
   - Wrapped in Suspense with conditional rendering

2. Add manual chunk splitting to vite.config.mts
   - Split vendor chunks: react, xterm, markdown, syntax, mermaid, charts, flow, diff
   - Enables better browser caching and smaller initial bundle
   - Heavy visualization libs (mermaid ~500KB, recharts ~400KB, etc.) now in separate chunks

3. Cache static files in web server (staticRoutes.ts)
   - index.html, manifest.json, sw.js now read once and cached
   - Eliminates blocking sync file reads on every web request
   - Improves web/mobile interface responsiveness
This commit is contained in:
Pedram Amini
2026-02-02 20:46:09 -06:00
parent 2fd83bdc4a
commit 03d157ee54
3 changed files with 194 additions and 83 deletions

View File

@@ -25,6 +25,43 @@ const LOG_CONTEXT = 'WebServer:Static';
// Redirect URL for invalid/missing token requests // Redirect URL for invalid/missing token requests
const REDIRECT_URL = 'https://runmaestro.ai'; const REDIRECT_URL = 'https://runmaestro.ai';
/**
* File cache for static assets that don't change at runtime.
* Prevents blocking file reads on every request.
*/
interface CachedFile {
content: string;
exists: boolean;
}
const fileCache = new Map<string, CachedFile>();
/**
* Read a file with caching - only reads from disk once per path.
* Returns null if file doesn't exist.
*/
function getCachedFile(filePath: string): string | null {
const cached = fileCache.get(filePath);
if (cached !== undefined) {
return cached.exists ? cached.content : null;
}
// First access - read from disk and cache
if (!existsSync(filePath)) {
fileCache.set(filePath, { content: '', exists: false });
return null;
}
try {
const content = readFileSync(filePath, 'utf-8');
fileCache.set(filePath, { content, exists: true });
return content;
} catch {
fileCache.set(filePath, { content: '', exists: false });
return null;
}
}
/** /**
* Static Routes Class * Static Routes Class
* *
@@ -77,7 +114,8 @@ export class StaticRoutes {
} }
const indexPath = path.join(this.webAssetsPath, 'index.html'); const indexPath = path.join(this.webAssetsPath, 'index.html');
if (!existsSync(indexPath)) { const cachedHtml = getCachedFile(indexPath);
if (cachedHtml === null) {
reply.code(404).send({ reply.code(404).send({
error: 'Not Found', error: 'Not Found',
message: 'Web interface index.html not found.', message: 'Web interface index.html not found.',
@@ -86,8 +124,8 @@ export class StaticRoutes {
} }
try { try {
// Read and transform the HTML to fix asset paths // Use cached HTML and transform asset paths
let html = readFileSync(indexPath, 'utf-8'); let html = cachedHtml;
// Transform relative paths to use the token-prefixed absolute paths // Transform relative paths to use the token-prefixed absolute paths
html = html.replace(/\.\/assets\//g, `/${this.securityToken}/assets/`); html = html.replace(/\.\/assets\//g, `/${this.securityToken}/assets/`);
@@ -138,28 +176,30 @@ export class StaticRoutes {
return { status: 'ok', timestamp: Date.now() }; return { status: 'ok', timestamp: Date.now() };
}); });
// PWA manifest.json // PWA manifest.json (cached)
server.get(`/${token}/manifest.json`, async (_request, reply) => { server.get(`/${token}/manifest.json`, async (_request, reply) => {
if (!this.webAssetsPath) { if (!this.webAssetsPath) {
return reply.code(404).send({ error: 'Not Found' }); return reply.code(404).send({ error: 'Not Found' });
} }
const manifestPath = path.join(this.webAssetsPath, 'manifest.json'); const manifestPath = path.join(this.webAssetsPath, 'manifest.json');
if (!existsSync(manifestPath)) { const content = getCachedFile(manifestPath);
if (content === null) {
return reply.code(404).send({ error: 'Not Found' }); return reply.code(404).send({ error: 'Not Found' });
} }
return reply.type('application/json').send(readFileSync(manifestPath, 'utf-8')); return reply.type('application/json').send(content);
}); });
// PWA service worker // PWA service worker (cached)
server.get(`/${token}/sw.js`, async (_request, reply) => { server.get(`/${token}/sw.js`, async (_request, reply) => {
if (!this.webAssetsPath) { if (!this.webAssetsPath) {
return reply.code(404).send({ error: 'Not Found' }); return reply.code(404).send({ error: 'Not Found' });
} }
const swPath = path.join(this.webAssetsPath, 'sw.js'); const swPath = path.join(this.webAssetsPath, 'sw.js');
if (!existsSync(swPath)) { const content = getCachedFile(swPath);
if (content === null) {
return reply.code(404).send({ error: 'Not Found' }); return reply.code(404).send({ error: 'Not Found' });
} }
return reply.type('application/javascript').send(readFileSync(swPath, 'utf-8')); return reply.type('application/javascript').send(content);
}); });
// Dashboard - list all live sessions // Dashboard - list all live sessions

View File

@@ -8,7 +8,10 @@ import React, {
lazy, lazy,
Suspense, Suspense,
} from 'react'; } from 'react';
import { SettingsModal } from './components/SettingsModal'; // SettingsModal is lazy-loaded for performance (large component, only loaded when settings opened)
const SettingsModal = lazy(() =>
import('./components/SettingsModal').then((m) => ({ default: m.SettingsModal }))
);
import { SessionList } from './components/SessionList'; import { SessionList } from './components/SessionList';
import { RightPanel, RightPanelHandle } from './components/RightPanel'; import { RightPanel, RightPanelHandle } from './components/RightPanel';
import { slashCommands } from './slashCommands'; import { slashCommands } from './slashCommands';
@@ -14326,79 +14329,83 @@ You are taking over this conversation. Based on the context above, provide a bri
{/* Old settings modal removed - using new SettingsModal component below */} {/* Old settings modal removed - using new SettingsModal component below */}
{/* NOTE: NewInstanceModal and EditAgentModal are now rendered via AppSessionModals */} {/* NOTE: NewInstanceModal and EditAgentModal are now rendered via AppSessionModals */}
{/* --- SETTINGS MODAL (New Component) --- */} {/* --- SETTINGS MODAL (Lazy-loaded for performance) --- */}
<SettingsModal {settingsModalOpen && (
isOpen={settingsModalOpen} <Suspense fallback={null}>
onClose={handleCloseSettings} <SettingsModal
theme={theme} isOpen={settingsModalOpen}
themes={THEMES} onClose={handleCloseSettings}
activeThemeId={activeThemeId} theme={theme}
setActiveThemeId={setActiveThemeId} themes={THEMES}
customThemeColors={customThemeColors} activeThemeId={activeThemeId}
setCustomThemeColors={setCustomThemeColors} setActiveThemeId={setActiveThemeId}
customThemeBaseId={customThemeBaseId} customThemeColors={customThemeColors}
setCustomThemeBaseId={setCustomThemeBaseId} setCustomThemeColors={setCustomThemeColors}
llmProvider={llmProvider} customThemeBaseId={customThemeBaseId}
setLlmProvider={setLlmProvider} setCustomThemeBaseId={setCustomThemeBaseId}
modelSlug={modelSlug} llmProvider={llmProvider}
setModelSlug={setModelSlug} setLlmProvider={setLlmProvider}
apiKey={apiKey} modelSlug={modelSlug}
setApiKey={setApiKey} setModelSlug={setModelSlug}
shortcuts={shortcuts} apiKey={apiKey}
setShortcuts={setShortcuts} setApiKey={setApiKey}
tabShortcuts={tabShortcuts} shortcuts={shortcuts}
setTabShortcuts={setTabShortcuts} setShortcuts={setShortcuts}
defaultShell={defaultShell} tabShortcuts={tabShortcuts}
setDefaultShell={setDefaultShell} setTabShortcuts={setTabShortcuts}
customShellPath={customShellPath} defaultShell={defaultShell}
setCustomShellPath={setCustomShellPath} setDefaultShell={setDefaultShell}
shellArgs={shellArgs} customShellPath={customShellPath}
setShellArgs={setShellArgs} setCustomShellPath={setCustomShellPath}
shellEnvVars={shellEnvVars} shellArgs={shellArgs}
setShellEnvVars={setShellEnvVars} setShellArgs={setShellArgs}
ghPath={ghPath} shellEnvVars={shellEnvVars}
setGhPath={setGhPath} setShellEnvVars={setShellEnvVars}
enterToSendAI={enterToSendAI} ghPath={ghPath}
setEnterToSendAI={setEnterToSendAI} setGhPath={setGhPath}
enterToSendTerminal={enterToSendTerminal} enterToSendAI={enterToSendAI}
setEnterToSendTerminal={setEnterToSendTerminal} setEnterToSendAI={setEnterToSendAI}
defaultSaveToHistory={defaultSaveToHistory} enterToSendTerminal={enterToSendTerminal}
setDefaultSaveToHistory={setDefaultSaveToHistory} setEnterToSendTerminal={setEnterToSendTerminal}
defaultShowThinking={defaultShowThinking} defaultSaveToHistory={defaultSaveToHistory}
setDefaultShowThinking={setDefaultShowThinking} setDefaultSaveToHistory={setDefaultSaveToHistory}
fontFamily={fontFamily} defaultShowThinking={defaultShowThinking}
setFontFamily={setFontFamily} setDefaultShowThinking={setDefaultShowThinking}
fontSize={fontSize} fontFamily={fontFamily}
setFontSize={setFontSize} setFontFamily={setFontFamily}
terminalWidth={terminalWidth} fontSize={fontSize}
setTerminalWidth={setTerminalWidth} setFontSize={setFontSize}
logLevel={logLevel} terminalWidth={terminalWidth}
setLogLevel={setLogLevel} setTerminalWidth={setTerminalWidth}
maxLogBuffer={maxLogBuffer} logLevel={logLevel}
setMaxLogBuffer={setMaxLogBuffer} setLogLevel={setLogLevel}
maxOutputLines={maxOutputLines} maxLogBuffer={maxLogBuffer}
setMaxOutputLines={setMaxOutputLines} setMaxLogBuffer={setMaxLogBuffer}
osNotificationsEnabled={osNotificationsEnabled} maxOutputLines={maxOutputLines}
setOsNotificationsEnabled={setOsNotificationsEnabled} setMaxOutputLines={setMaxOutputLines}
audioFeedbackEnabled={audioFeedbackEnabled} osNotificationsEnabled={osNotificationsEnabled}
setAudioFeedbackEnabled={setAudioFeedbackEnabled} setOsNotificationsEnabled={setOsNotificationsEnabled}
audioFeedbackCommand={audioFeedbackCommand} audioFeedbackEnabled={audioFeedbackEnabled}
setAudioFeedbackCommand={setAudioFeedbackCommand} setAudioFeedbackEnabled={setAudioFeedbackEnabled}
toastDuration={toastDuration} audioFeedbackCommand={audioFeedbackCommand}
setToastDuration={setToastDuration} setAudioFeedbackCommand={setAudioFeedbackCommand}
checkForUpdatesOnStartup={checkForUpdatesOnStartup} toastDuration={toastDuration}
setCheckForUpdatesOnStartup={setCheckForUpdatesOnStartup} setToastDuration={setToastDuration}
enableBetaUpdates={enableBetaUpdates} checkForUpdatesOnStartup={checkForUpdatesOnStartup}
setEnableBetaUpdates={setEnableBetaUpdates} setCheckForUpdatesOnStartup={setCheckForUpdatesOnStartup}
crashReportingEnabled={crashReportingEnabled} enableBetaUpdates={enableBetaUpdates}
setCrashReportingEnabled={setCrashReportingEnabled} setEnableBetaUpdates={setEnableBetaUpdates}
customAICommands={customAICommands} crashReportingEnabled={crashReportingEnabled}
setCustomAICommands={setCustomAICommands} setCrashReportingEnabled={setCrashReportingEnabled}
initialTab={settingsTab} customAICommands={customAICommands}
hasNoAgents={hasNoAgents} setCustomAICommands={setCustomAICommands}
onThemeImportError={(msg) => setFlashNotification(msg)} initialTab={settingsTab}
onThemeImportSuccess={(msg) => setFlashNotification(msg)} hasNoAgents={hasNoAgents}
/> onThemeImportError={(msg) => setFlashNotification(msg)}
onThemeImportSuccess={(msg) => setFlashNotification(msg)}
/>
</Suspense>
)}
{/* --- WIZARD RESUME MODAL (asks if user wants to resume incomplete wizard) --- */} {/* --- WIZARD RESUME MODAL (asks if user wants to resume incomplete wizard) --- */}
{wizardResumeModalOpen && wizardResumeState && ( {wizardResumeModalOpen && wizardResumeState && (

View File

@@ -49,6 +49,70 @@ export default defineConfig(({ mode }) => ({
build: { build: {
outDir: path.join(__dirname, 'dist/renderer'), outDir: path.join(__dirname, 'dist/renderer'),
emptyOutDir: true, emptyOutDir: true,
rollupOptions: {
output: {
// Manual chunking for better caching and code splitting
manualChunks: (id) => {
// React core in its own chunk for optimal caching
if (id.includes('node_modules/react-dom')) {
return 'vendor-react';
}
if (id.includes('node_modules/react/') || id.includes('node_modules/react-is')) {
return 'vendor-react';
}
if (id.includes('node_modules/scheduler')) {
return 'vendor-react';
}
// Terminal (xterm) in its own chunk - large and not immediately needed
if (id.includes('node_modules/xterm')) {
return 'vendor-xterm';
}
// Markdown processing libraries
if (
id.includes('node_modules/react-markdown') ||
id.includes('node_modules/remark-') ||
id.includes('node_modules/rehype-') ||
id.includes('node_modules/unified') ||
id.includes('node_modules/unist-') ||
id.includes('node_modules/mdast-') ||
id.includes('node_modules/hast-') ||
id.includes('node_modules/micromark')
) {
return 'vendor-markdown';
}
// Syntax highlighting (large)
if (
id.includes('node_modules/react-syntax-highlighter') ||
id.includes('node_modules/prismjs') ||
id.includes('node_modules/refractor')
) {
return 'vendor-syntax';
}
// Heavy visualization libraries (lazy-loaded components)
if (id.includes('node_modules/mermaid')) {
return 'vendor-mermaid';
}
if (id.includes('node_modules/recharts') || id.includes('node_modules/d3-')) {
return 'vendor-charts';
}
if (id.includes('node_modules/reactflow') || id.includes('node_modules/@reactflow')) {
return 'vendor-flow';
}
// Diff viewer
if (id.includes('node_modules/react-diff-view') || id.includes('node_modules/diff')) {
return 'vendor-diff';
}
// Return undefined to let Rollup handle other modules automatically
return undefined;
},
},
},
}, },
server: { server: {
port: process.env.VITE_PORT ? parseInt(process.env.VITE_PORT, 10) : 5173, port: process.env.VITE_PORT ? parseInt(process.env.VITE_PORT, 10) : 5173,