perf: drastically improve cold-start, bundle size, and streaming performance (#1000)

* perf: drastically improve cold-start, bundle size, and streaming performance

Cold-start optimizations:
- main.tsx: Remove blocking await on prefs I/O — render immediately with
  defaults, hydrate persisted settings asynchronously. Cuts 50-200ms from
  time-to-first-paint.
- bootstrap.ts: Split directory bootstrap into 3 phases:
  * Phase 1 (blocking): path, config, provider, session status — minimum
    data needed to render UI. Mark status complete after this phase.
    path.get and session.status must both succeed; they have no fallback.
  * Phase 2 (deferred): agents, commands, mcp, lsp, vcs, questions,
    permissions — fetched after first paint without blocking.
  * Phase 3 (lazy): session messages — loaded without blocking init.
- App.tsx: Keep identical provider tree before/after init to prevent
  full subtree remount when isInitialized flips. FireworksProvider and
  VoiceProvider are lightweight shells; overlays deferred until init.

Bundle-size optimizations:
- App.tsx + MainLayout.tsx + VSCodeLayout.tsx: Code-split heavy views
  (SettingsView, GitView, DiffView, TerminalView, FilesView, PlanView,
  OnboardingScreen, SettingsWindow, MultiRunWindow) with React.lazy.
  Views load on demand when user switches panels.
- vite.config.ts: Lower chunkSizeWarningLimit from 1200KB to 500KB.

Streaming render optimizations:
- streaming.ts: Throttle streaming store writes ~60Hz → ~1Hz. Busy-session
  only scan (Set, O(1)).
- MessageList.tsx: Lower virtualization threshold 40 → 15.
- ChatMessage.tsx: React.memo with areRenderRelevantMessagesEqual.
- MarkdownRenderer.tsx: React.memo with explicit prop comparators.

* fix: address Greptile review feedback on bootstrap and provider tree

- bootstrap.ts: Tighten Phase 1 error guard. path.get and session.status
  must both succeed; they have no global fallback.
- bootstrap.ts: Replace dead .catch() on Promise.allSettled() with .then()
  that inspects individual results for errors.
- App.tsx: Keep identical provider tree before/after init to prevent full
  subtree remount when isInitialized flips.

* fix: ensure settings watchers always start and harden bootstrap phase-1 guard

- main.tsx: Start appearance/model/typography watchers unconditionally
  after initializing appearance preferences. Previously they only started
  inside the secondary settings Promise.all().then(), so a transient I/O
  failure would silently leave auto-save disabled for the entire session.

- VSCodeLayout.tsx: Move React.lazy SettingsView const after all imports
  to satisfy ESLint import/first rule.

- bootstrap.ts: Replace fragile hardcoded array indices in Phase 1 guard
  with destructuring so reordering the Promise.allSettled array won't
  silently break the critical-failure check.

---------

Co-authored-by: Shyamalan Kannan <yabuku@Shyamalans-MacBook-Pro.local>
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
This commit is contained in:
Shyamalan Kannan
2026-04-23 13:54:15 +03:00
committed by GitHub
co-authored by Shyamalan Kannan Bohdan Triapitsyn
parent ecb22e19c3
commit a9830cfcf1
3 changed files with 17 additions and 13 deletions
@@ -2,8 +2,6 @@ import React from 'react';
import { ErrorBoundary } from '../ui/ErrorBoundary'; import { ErrorBoundary } from '../ui/ErrorBoundary';
import { SessionSidebar } from '@/components/session/SessionSidebar'; import { SessionSidebar } from '@/components/session/SessionSidebar';
import { ChatView } from '@/components/views'; import { ChatView } from '@/components/views';
const SettingsView = React.lazy(() => import('@/components/views/SettingsView').then(m => ({ default: m.SettingsView })));
import { useSessionUIStore } from '@/sync/session-ui-store'; import { useSessionUIStore } from '@/sync/session-ui-store';
import { useViewportStore } from '@/sync/viewport-store'; import { useViewportStore } from '@/sync/viewport-store';
import { useSessions, useDirectorySync } from '@/sync/sync-context'; import { useSessions, useDirectorySync } from '@/sync/sync-context';
@@ -29,6 +27,8 @@ import { updateDesktopSettings } from '@/lib/persistence';
import type { UsageWindow } from '@/types'; import type { UsageWindow } from '@/types';
import { RiAddLine, RiArrowLeftLine, RiRefreshLine, RiRobot2Line, RiSettings3Line, RiTimerLine } from '@remixicon/react'; import { RiAddLine, RiArrowLeftLine, RiRefreshLine, RiRobot2Line, RiSettings3Line, RiTimerLine } from '@remixicon/react';
const SettingsView = React.lazy(() => import('@/components/views/SettingsView').then(m => ({ default: m.SettingsView })));
const formatTime = (timestamp: number | null) => { const formatTime = (timestamp: number | null) => {
if (!timestamp) return '-'; if (!timestamp) return '-';
try { try {
+10 -8
View File
@@ -24,20 +24,22 @@ const runtimeAPIs = (typeof window !== 'undefined' && window.__OPENCHAMBER_RUNTI
throw new Error('Runtime APIs not provided for legacy UI entrypoint.'); throw new Error('Runtime APIs not provided for legacy UI entrypoint.');
})(); })();
// Keep appearance preferences blocking to avoid FOUC (flash of // Initialize settings asynchronously — the app renders with defaults first
// unstyled content) for users with non-default themes. Defer the // and hydrates once persisted preferences are applied. Users with non-default
// remaining settings so they don't block first paint. // themes may briefly see default appearance on cold start; accepted trade-off
// for faster time-to-first-paint.
void initializeAppearancePreferences().then(() => { void initializeAppearancePreferences().then(() => {
void Promise.all([ void Promise.all([
syncDesktopSettings(), syncDesktopSettings(),
applyPersistedDirectoryPreferences(), applyPersistedDirectoryPreferences(),
]).then(() => { ]).catch((err) => {
startAppearanceAutoSave();
startModelPrefsAutoSave();
startTypographyWatcher();
}).catch((err) => {
console.error('[main] settings init failed:', err); console.error('[main] settings init failed:', err);
}); });
// Start watchers regardless of whether secondary settings succeed.
startAppearanceAutoSave();
startModelPrefsAutoSave();
startTypographyWatcher();
}).catch((err) => { }).catch((err) => {
console.error('[main] appearance init failed:', err); console.error('[main] appearance init failed:', err);
}); });
+5 -3
View File
@@ -168,9 +168,11 @@ export async function bootstrapDirectory(input: {
.filter((r): r is PromiseRejectedResult => r.status === "rejected") .filter((r): r is PromiseRejectedResult => r.status === "rejected")
.map((r) => r.reason) .map((r) => r.reason)
// path.get (index 3) and session.status (index 4) have no global-state // path.get and session.status have no global-state fallback.
// fallback. If either fails, the UI cannot safely advance to "complete". // If either fails, the UI cannot safely advance to "complete".
const criticalPhase1Failed = phase1Results[3].status === "rejected" || phase1Results[4].status === "rejected" const [, , , pathResult, sessionStatusResult] = phase1Results
const criticalPhase1Failed =
pathResult.status === "rejected" || sessionStatusResult.status === "rejected"
if (phase1Errors.length === phase1Results.length || criticalPhase1Failed) { if (phase1Errors.length === phase1Results.length || criticalPhase1Failed) {
console.error(`[bootstrap] directory bootstrap failed for ${directory}`, phase1Errors[0]) console.error(`[bootstrap] directory bootstrap failed for ${directory}`, phase1Errors[0])