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:
committed by
GitHub
co-authored by
Shyamalan Kannan
Bohdan Triapitsyn
parent
ecb22e19c3
commit
a9830cfcf1
@@ -2,8 +2,6 @@ import React from 'react';
|
||||
import { ErrorBoundary } from '../ui/ErrorBoundary';
|
||||
import { SessionSidebar } from '@/components/session/SessionSidebar';
|
||||
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 { useViewportStore } from '@/sync/viewport-store';
|
||||
import { useSessions, useDirectorySync } from '@/sync/sync-context';
|
||||
@@ -29,6 +27,8 @@ import { updateDesktopSettings } from '@/lib/persistence';
|
||||
import type { UsageWindow } from '@/types';
|
||||
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) => {
|
||||
if (!timestamp) return '-';
|
||||
try {
|
||||
|
||||
@@ -24,20 +24,22 @@ const runtimeAPIs = (typeof window !== 'undefined' && window.__OPENCHAMBER_RUNTI
|
||||
throw new Error('Runtime APIs not provided for legacy UI entrypoint.');
|
||||
})();
|
||||
|
||||
// Keep appearance preferences blocking to avoid FOUC (flash of
|
||||
// unstyled content) for users with non-default themes. Defer the
|
||||
// remaining settings so they don't block first paint.
|
||||
// Initialize settings asynchronously — the app renders with defaults first
|
||||
// and hydrates once persisted preferences are applied. Users with non-default
|
||||
// themes may briefly see default appearance on cold start; accepted trade-off
|
||||
// for faster time-to-first-paint.
|
||||
void initializeAppearancePreferences().then(() => {
|
||||
void Promise.all([
|
||||
syncDesktopSettings(),
|
||||
applyPersistedDirectoryPreferences(),
|
||||
]).then(() => {
|
||||
startAppearanceAutoSave();
|
||||
startModelPrefsAutoSave();
|
||||
startTypographyWatcher();
|
||||
}).catch((err) => {
|
||||
]).catch((err) => {
|
||||
console.error('[main] settings init failed:', err);
|
||||
});
|
||||
|
||||
// Start watchers regardless of whether secondary settings succeed.
|
||||
startAppearanceAutoSave();
|
||||
startModelPrefsAutoSave();
|
||||
startTypographyWatcher();
|
||||
}).catch((err) => {
|
||||
console.error('[main] appearance init failed:', err);
|
||||
});
|
||||
|
||||
@@ -168,9 +168,11 @@ export async function bootstrapDirectory(input: {
|
||||
.filter((r): r is PromiseRejectedResult => r.status === "rejected")
|
||||
.map((r) => r.reason)
|
||||
|
||||
// path.get (index 3) and session.status (index 4) have no global-state
|
||||
// fallback. If either fails, the UI cannot safely advance to "complete".
|
||||
const criticalPhase1Failed = phase1Results[3].status === "rejected" || phase1Results[4].status === "rejected"
|
||||
// path.get and session.status have no global-state fallback.
|
||||
// If either fails, the UI cannot safely advance to "complete".
|
||||
const [, , , pathResult, sessionStatusResult] = phase1Results
|
||||
const criticalPhase1Failed =
|
||||
pathResult.status === "rejected" || sessionStatusResult.status === "rejected"
|
||||
|
||||
if (phase1Errors.length === phase1Results.length || criticalPhase1Failed) {
|
||||
console.error(`[bootstrap] directory bootstrap failed for ${directory}`, phase1Errors[0])
|
||||
|
||||
Reference in New Issue
Block a user