perf: speed up desktop startup and unify theme-aware branding (#596)

* fix: unify startup logo and loading theme behavior

Show desktop window immediately with animated splash logo
Align splash/logo colors with selected app theme and default themes
Keep auth loading state on full-screen logo without size jump
Make project SVG icons follow active app theme
Use the active theme foreground color for project icons discovered from favicons
Apply server-side SVG color overrides for currentColor icons via icon request params
Keep non-SVG project icons unchanged while preserving existing fallback behavior

* perf: speed up desktop startup and unify loading logo visuals

Desktop startup now shows UI sooner while backend boot continues in background
Startup host probing uses a faster local path with safer remote fallback retries
OpenChamber logo cube highlights now match splash screens consistently

* fix: keep macOS traffic-light buttons in the correct position on load

Stop native window title updates on macOS during app initialization
Prevent title bar relayout that reset custom traffic-light positioning

* fix: recover missing providers and agents after fast startup

Retries provider/agent loading when connection is up but core config is still empty
Prevents cold-start state where models/agents appear only after manual project switch
Keeps startup responsive with throttled background recovery in app bootstrap
This commit is contained in:
Bohdan Triapitsyn
2026-03-04 19:16:45 +02:00
committed by GitHub
parent 037a70f114
commit d1a41000ca
22 changed files with 763 additions and 190 deletions
+190 -93
View File
@@ -784,6 +784,36 @@ const resolveZenModel = async (override) => {
return validatedZenFallback || ZEN_DEFAULT_MODEL;
};
const validateZenModelAtStartup = async () => {
try {
const freeModels = await fetchFreeZenModels();
const freeModelIds = freeModels.map((m) => m.id);
if (freeModelIds.length > 0) {
validatedZenFallback = freeModelIds[0];
const settings = await readSettingsFromDisk();
const storedModel = typeof settings?.zenModel === 'string' ? settings.zenModel.trim() : '';
if (!storedModel || !freeModelIds.includes(storedModel)) {
const fallback = freeModelIds[0];
console.log(
storedModel
? `[zen] Stored model "${storedModel}" not found in free models, falling back to "${fallback}"`
: `[zen] No model configured, setting default to "${fallback}"`
);
await persistSettings({ zenModel: fallback });
} else {
console.log(`[zen] Stored model "${storedModel}" verified as available`);
}
} else {
console.warn('[zen] No free models returned from API, skipping validation');
}
} catch (error) {
console.warn('[zen] Startup model validation failed (non-blocking):', error?.message || error);
}
};
const summarizeText = async (text, targetLength, zenModel) => {
if (!text || typeof text !== 'string' || text.trim().length === 0) return text;
@@ -1148,6 +1178,11 @@ const PROJECT_ICON_EXTENSION_TO_MIME = Object.fromEntries(
);
const PROJECT_ICON_SUPPORTED_MIMES = new Set(Object.keys(PROJECT_ICON_MIME_TO_EXTENSION));
const PROJECT_ICON_MAX_BYTES = 5 * 1024 * 1024;
const PROJECT_ICON_THEME_COLORS = {
light: '#111111',
dark: '#f5f5f5',
};
const PROJECT_ICON_HEX_COLOR_PATTERN = /^#(?:[\da-fA-F]{3}|[\da-fA-F]{4}|[\da-fA-F]{6}|[\da-fA-F]{8})$/;
const normalizeProjectIconMime = (value) => {
if (typeof value !== 'string') {
@@ -1230,6 +1265,54 @@ const parseProjectIconDataUrl = (value) => {
}
};
const normalizeProjectIconThemeVariant = (value) => {
if (typeof value !== 'string') {
return null;
}
const normalized = value.trim().toLowerCase();
if (normalized === 'light' || normalized === 'dark') {
return normalized;
}
return null;
};
const normalizeProjectIconColor = (value) => {
if (typeof value !== 'string') {
return null;
}
const normalized = value.trim();
if (!PROJECT_ICON_HEX_COLOR_PATTERN.test(normalized)) {
return null;
}
return normalized;
};
const applyProjectIconSvgTheme = (svgMarkup, themeVariant, iconColor) => {
if (typeof svgMarkup !== 'string') {
return svgMarkup;
}
const color = iconColor || PROJECT_ICON_THEME_COLORS[themeVariant];
if (!color) {
return svgMarkup;
}
const svgTagIndex = svgMarkup.search(/<svg\b/i);
if (svgTagIndex === -1) {
return svgMarkup;
}
const svgOpenTagEndIndex = svgMarkup.indexOf('>', svgTagIndex);
if (svgOpenTagEndIndex === -1) {
return svgMarkup;
}
const overrideStyle = `<style data-openchamber-theme-icon="1">:root{color:${color}!important;}</style>`;
return `${svgMarkup.slice(0, svgOpenTagEndIndex + 1)}${overrideStyle}${svgMarkup.slice(svgOpenTagEndIndex + 1)}`;
};
const findProjectById = (settings, projectId) => {
const projects = sanitizeProjects(settings?.projects) || [];
const index = projects.findIndex((project) => project.id === projectId);
@@ -1795,6 +1878,18 @@ const sanitizeSettingsUpdate = (payload) => {
if (typeof candidate.darkThemeId === 'string' && candidate.darkThemeId.length > 0) {
result.darkThemeId = candidate.darkThemeId;
}
if (typeof candidate.splashBgLight === 'string' && candidate.splashBgLight.trim().length > 0) {
result.splashBgLight = candidate.splashBgLight.trim();
}
if (typeof candidate.splashFgLight === 'string' && candidate.splashFgLight.trim().length > 0) {
result.splashFgLight = candidate.splashFgLight.trim();
}
if (typeof candidate.splashBgDark === 'string' && candidate.splashBgDark.trim().length > 0) {
result.splashBgDark = candidate.splashBgDark.trim();
}
if (typeof candidate.splashFgDark === 'string' && candidate.splashFgDark.trim().length > 0) {
result.splashFgDark = candidate.splashFgDark.trim();
}
if (typeof candidate.lastDirectory === 'string' && candidate.lastDirectory.length > 0) {
result.lastDirectory = candidate.lastDirectory;
}
@@ -5929,6 +6024,72 @@ async function refreshOpenCodeAfterConfigChange(reason, options = {}) {
}
}
async function bootstrapOpenCodeAtStartup() {
try {
syncFromHmrState();
if (await isOpenCodeProcessHealthy()) {
console.log(`[HMR] Reusing existing OpenCode process on port ${openCodePort}`);
} else if (ENV_SKIP_OPENCODE_START && ENV_EFFECTIVE_PORT) {
const label = ENV_CONFIGURED_OPENCODE_HOST ? ENV_CONFIGURED_OPENCODE_HOST.origin : `http://localhost:${ENV_EFFECTIVE_PORT}`;
console.log(`Using external OpenCode server at ${label} (skip-start mode)`);
openCodeBaseUrl = ENV_CONFIGURED_OPENCODE_HOST?.origin ?? null;
setOpenCodePort(ENV_EFFECTIVE_PORT);
isOpenCodeReady = true;
isExternalOpenCode = true;
lastOpenCodeError = null;
openCodeNotReadySince = 0;
syncToHmrState();
} else if (ENV_EFFECTIVE_PORT && await probeExternalOpenCode(ENV_EFFECTIVE_PORT, ENV_CONFIGURED_OPENCODE_HOST?.origin)) {
const label = ENV_CONFIGURED_OPENCODE_HOST ? ENV_CONFIGURED_OPENCODE_HOST.origin : `http://localhost:${ENV_EFFECTIVE_PORT}`;
console.log(`Auto-detected existing OpenCode server at ${label}`);
openCodeBaseUrl = ENV_CONFIGURED_OPENCODE_HOST?.origin ?? null;
setOpenCodePort(ENV_EFFECTIVE_PORT);
isOpenCodeReady = true;
isExternalOpenCode = true;
lastOpenCodeError = null;
openCodeNotReadySince = 0;
syncToHmrState();
} else if (!ENV_EFFECTIVE_PORT && await probeExternalOpenCode(4096)) {
console.log('Auto-detected existing OpenCode server on default port 4096');
setOpenCodePort(4096);
isOpenCodeReady = true;
isExternalOpenCode = true;
lastOpenCodeError = null;
openCodeNotReadySince = 0;
syncToHmrState();
} else {
if (ENV_EFFECTIVE_PORT) {
console.log(`Using OpenCode port from environment: ${ENV_EFFECTIVE_PORT}`);
setOpenCodePort(ENV_EFFECTIVE_PORT);
} else {
openCodePort = null;
syncToHmrState();
}
lastOpenCodeError = null;
openCodeProcess = await startOpenCode();
syncToHmrState();
}
await waitForOpenCodePort();
try {
await waitForOpenCodeReady();
} catch (error) {
console.error(`OpenCode readiness check failed: ${error.message}`);
scheduleOpenCodeApiDetection();
}
scheduleOpenCodeApiDetection();
startHealthMonitoring();
void startGlobalEventWatcher().catch((error) => {
console.warn(`Global event watcher startup failed: ${error?.message || error}`);
});
} catch (error) {
console.error(`Failed to start OpenCode: ${error.message}`);
console.log('Continuing without OpenCode integration...');
lastOpenCodeError = error.message;
scheduleOpenCodeApiDetection();
}
}
function setupProxy(app) {
if (app.get('opencodeProxyConfigured')) {
return;
@@ -6564,35 +6725,8 @@ async function main(options = {}) {
sayTTSCapability = { available: false, voices: [], reason: 'Not macOS' };
}
// Validate stored zen model at startup best-effort, never blocks startup
try {
const freeModels = await fetchFreeZenModels();
const freeModelIds = freeModels.map((m) => m.id);
if (freeModelIds.length > 0) {
// Set the validated fallback to the first available free model
validatedZenFallback = freeModelIds[0];
const settings = await readSettingsFromDisk();
const storedModel = typeof settings?.zenModel === 'string' ? settings.zenModel.trim() : '';
if (!storedModel || !freeModelIds.includes(storedModel)) {
const fallback = freeModelIds[0];
console.log(
storedModel
? `[zen] Stored model "${storedModel}" not found in free models, falling back to "${fallback}"`
: `[zen] No model configured, setting default to "${fallback}"`
);
await persistSettings({ zenModel: fallback });
} else {
console.log(`[zen] Stored model "${storedModel}" verified as available`);
}
} else {
console.warn('[zen] No free models returned from API, skipping validation');
}
} catch (error) {
console.warn('[zen] Startup model validation failed (non-blocking):', error?.message || error);
}
// Startup model validation is best-effort and runs in background.
void validateZenModelAtStartup();
const app = express();
const serverStartedAt = new Date().toISOString();
@@ -8091,11 +8225,34 @@ async function main(options = {}) {
? [preferredPath, ...projectIconPathCandidates(projectId).filter((candidate) => candidate !== preferredPath)]
: projectIconPathCandidates(projectId);
const themeQuery = Array.isArray(req.query?.theme) ? req.query.theme[0] : req.query?.theme;
const requestedThemeVariant = normalizeProjectIconThemeVariant(themeQuery);
const iconColorQuery = Array.isArray(req.query?.iconColor) ? req.query.iconColor[0] : req.query?.iconColor;
const requestedIconColor = normalizeProjectIconColor(iconColorQuery);
for (const iconPath of candidates) {
try {
const data = await fsPromises.readFile(iconPath);
const ext = path.extname(iconPath).slice(1).toLowerCase();
const contentType = metadataMime || PROJECT_ICON_EXTENSION_TO_MIME[ext] || 'application/octet-stream';
const resolvedMime = metadataMime || PROJECT_ICON_EXTENSION_TO_MIME[ext] || 'application/octet-stream';
const contentType = resolvedMime === 'image/svg+xml' ? 'image/svg+xml; charset=utf-8' : resolvedMime;
if (resolvedMime === 'image/svg+xml' && requestedThemeVariant) {
const svgMarkup = data.toString('utf8');
const themedSvgMarkup = applyProjectIconSvgTheme(svgMarkup, requestedThemeVariant, requestedIconColor);
res.setHeader('Content-Type', contentType);
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
return res.send(themedSvgMarkup);
}
if (resolvedMime === 'image/svg+xml' && requestedIconColor) {
const svgMarkup = data.toString('utf8');
const themedSvgMarkup = applyProjectIconSvgTheme(svgMarkup, requestedThemeVariant, requestedIconColor);
res.setHeader('Content-Type', contentType);
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
return res.send(themedSvgMarkup);
}
res.setHeader('Content-Type', contentType);
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
return res.send(data);
@@ -13006,69 +13163,9 @@ async function main(options = {}) {
res.json({ success: true, killedCount });
});
try {
syncFromHmrState();
if (await isOpenCodeProcessHealthy()) {
console.log(`[HMR] Reusing existing OpenCode process on port ${openCodePort}`);
} else if (ENV_SKIP_OPENCODE_START && ENV_EFFECTIVE_PORT) {
const label = ENV_CONFIGURED_OPENCODE_HOST ? ENV_CONFIGURED_OPENCODE_HOST.origin : `http://localhost:${ENV_EFFECTIVE_PORT}`;
console.log(`Using external OpenCode server at ${label} (skip-start mode)`);
openCodeBaseUrl = ENV_CONFIGURED_OPENCODE_HOST?.origin ?? null;
setOpenCodePort(ENV_EFFECTIVE_PORT);
isOpenCodeReady = true;
isExternalOpenCode = true;
lastOpenCodeError = null;
openCodeNotReadySince = 0;
syncToHmrState();
} else if (ENV_EFFECTIVE_PORT && await probeExternalOpenCode(ENV_EFFECTIVE_PORT, ENV_CONFIGURED_OPENCODE_HOST?.origin)) {
const label = ENV_CONFIGURED_OPENCODE_HOST ? ENV_CONFIGURED_OPENCODE_HOST.origin : `http://localhost:${ENV_EFFECTIVE_PORT}`;
console.log(`Auto-detected existing OpenCode server at ${label}`);
openCodeBaseUrl = ENV_CONFIGURED_OPENCODE_HOST?.origin ?? null;
setOpenCodePort(ENV_EFFECTIVE_PORT);
isOpenCodeReady = true;
isExternalOpenCode = true;
lastOpenCodeError = null;
openCodeNotReadySince = 0;
syncToHmrState();
} else if (!ENV_EFFECTIVE_PORT && await probeExternalOpenCode(4096)) {
console.log('Auto-detected existing OpenCode server on default port 4096');
setOpenCodePort(4096);
isOpenCodeReady = true;
isExternalOpenCode = true;
lastOpenCodeError = null;
openCodeNotReadySince = 0;
syncToHmrState();
} else {
if (ENV_EFFECTIVE_PORT) {
console.log(`Using OpenCode port from environment: ${ENV_EFFECTIVE_PORT}`);
setOpenCodePort(ENV_EFFECTIVE_PORT);
} else {
openCodePort = null;
syncToHmrState();
}
lastOpenCodeError = null;
openCodeProcess = await startOpenCode();
syncToHmrState();
}
await waitForOpenCodePort();
try {
await waitForOpenCodeReady();
} catch (error) {
console.error(`OpenCode readiness check failed: ${error.message}`);
scheduleOpenCodeApiDetection();
}
setupProxy(app);
scheduleOpenCodeApiDetection();
startHealthMonitoring();
void startGlobalEventWatcher();
} catch (error) {
console.error(`Failed to start OpenCode: ${error.message}`);
console.log('Continuing without OpenCode integration...');
lastOpenCodeError = error.message;
setupProxy(app);
scheduleOpenCodeApiDetection();
}
setupProxy(app);
scheduleOpenCodeApiDetection();
void bootstrapOpenCodeAtStartup();
const distPath = (() => {
const env = typeof process.env.OPENCHAMBER_DIST_DIR === 'string' ? process.env.OPENCHAMBER_DIST_DIR.trim() : '';