You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/09/19 03:45:04 UTC
(maka) 02/02: feat(desktop): fire the window before the Runtime Host module graph
This is an automated email from the ASF dual-hosted git repository.
Astro-Han pushed a commit to branch feat/5488-ui-first-startup
in repository https://gitbox.apache.org/repos/asf/maka.git
commit a01dc0043907243c2b1955f8c24ba3161b012243
Author: AstroHan <le...@outlook.com>
AuthorDate: Sat Sep 19 11:43:14 2026 +0800
feat(desktop): fire the window before the Runtime Host module graph
runtime-host-boot evaluates ~1100 compiled files before its first
statement, so window creation could not start until ~400ms after the app
was ready. early-window.ts holds the light slice the window actually
needs — storage root, settings, locale, diagnostics, the window
controller, the quit coordinator — and fires createWindow as soon as it
exists; main.ts imports it first so the remaining Runtime Host graph
loads while the renderer is already navigating. Window creation moves
from ~400ms to ~210ms on this machine.
The login-shell PATH probe now starts at module top and is awaited only
where a child process is spawned (Local Host start, remote profiles,
MCP) — it no longer sits serially ahead of the window (~100ms on a real
launch; e2e fixtures skip it as before). Independent small reads (client
instance id, Runtime Host startup) run in parallel.
window:notifyRendererReady is not a Host-scoped channel: it is
registered on ipcMain by early-window so the renderer's first commit
cannot outrun scoped-router registration. The quit coordinator and the
window controller keep their exact lifecycle semantics — before-quit
aborts in-flight creation, close hooks and diagnostics reach the Runtime
Host through boot-context late bindings.
Generated-by: Devin
---
.../main/__tests__/main-startup-lifetime.test.ts | 37 +--
.../main/__tests__/startup-storage-repair.test.ts | 8 +-
apps/desktop/src/main/app-ipc-main.ts | 7 +-
apps/desktop/src/main/boot-context.ts | 15 +
apps/desktop/src/main/early-window.ts | 304 +++++++++++++++++++++
apps/desktop/src/main/main.ts | 6 +-
apps/desktop/src/main/runtime-host-boot.ts | 289 +++-----------------
7 files changed, 393 insertions(+), 273 deletions(-)
diff --git a/apps/desktop/src/main/__tests__/main-startup-lifetime.test.ts b/apps/desktop/src/main/__tests__/main-startup-lifetime.test.ts
index a340d63273..6090526c88 100644
--- a/apps/desktop/src/main/__tests__/main-startup-lifetime.test.ts
+++ b/apps/desktop/src/main/__tests__/main-startup-lifetime.test.ts
@@ -30,8 +30,8 @@ const bootSource = readFileSync(
fileURLToPath(new URL('../../../src/main/runtime-host-boot.ts', import.meta.url)),
'utf8',
);
-const appIpcSource = readFileSync(
- fileURLToPath(new URL('../../../src/main/app-ipc-main.ts', import.meta.url)),
+const earlyWindowSource = readFileSync(
+ fileURLToPath(new URL('../../../src/main/early-window.ts', import.meta.url)),
'utf8',
);
const mainWindowSource = readFileSync(
@@ -73,14 +73,18 @@ test('retains process lifetime before a standalone startup dialog can close', ()
});
test('registers one shared quit cleanup before the initial Host handoff', () => {
+ const earlyWindowImport = mainSource.indexOf("import('./early-window.js')");
+ const bootImport = mainSource.indexOf("import('./runtime-host-boot.js')");
const hostStart = bootSource.indexOf('await runtimeHostManager?.start()');
- const quitRegistration = bootSource.indexOf('app.on("before-quit", quitCoordinator.handleBeforeQuit)');
- const workBoardDeclaration = bootSource.indexOf('let workBoardIpc:');
- assert.ok(workBoardDeclaration >= 0 && workBoardDeclaration < quitRegistration);
- assert.ok(quitRegistration >= 0 && quitRegistration < hostStart);
- assert.equal(bootSource.match(/createAppQuitCoordinator\(\{/gu)?.length, 1);
- assert.equal(bootSource.match(/app\.on\("before-quit"/gu)?.length, 1);
- assert.match(bootSource, /cleanup: closeRuntimeHostDesktop/u);
+ const quitRegistration = earlyWindowSource.indexOf(
+ 'app.on("before-quit", quitCoordinator.handleBeforeQuit)',
+ );
+ assert.ok(earlyWindowImport >= 0 && earlyWindowImport < bootImport);
+ assert.ok(quitRegistration >= 0);
+ assert.ok(hostStart >= 0);
+ assert.equal(earlyWindowSource.match(/createAppQuitCoordinator\(\{/gu)?.length, 1);
+ assert.equal(earlyWindowSource.match(/app\.on\("before-quit"/gu)?.length, 1);
+ assert.match(bootSource, /bootContext\.cleanup = closeRuntimeHostDesktop/u);
assert.match(bootSource, /return runtimeHostDesktopShutdown \?\?= disposeRuntimeHostDesktop\(\)/u);
assert.match(bootSource, /workBoardIpc\?\.close\(\)/u);
});
@@ -100,14 +104,15 @@ test('creates the main window before starting Local Host reconciliation', () =>
const hostStart = bootSource.indexOf('await runtimeHostManager?.start()', managerCreate);
assert.ok(managerCreate >= 0);
assert.ok(lifecycleWire > managerCreate && hostStart > lifecycleWire);
+ assert.match(earlyWindowSource, /void quitCoordinator\.focusOrCreateWindow\(\)/u);
assert.doesNotMatch(mainSource, /startup-presentation/u);
});
test('resolves persisted locale before first post-settings recovery prompt', () => {
- const rendererRecoveryStart = bootSource.indexOf('onRendererProcessGone: async');
- const rendererRecovery = bootSource.slice(
+ const rendererRecoveryStart = earlyWindowSource.indexOf('onRendererProcessGone: async');
+ const rendererRecovery = earlyWindowSource.slice(
rendererRecoveryStart,
- bootSource.indexOf('resolveBrowserDialogParent =', rendererRecoveryStart),
+ earlyWindowSource.indexOf('mainWindowDelegates.resolveBrowserDialogParent =', rendererRecoveryStart),
);
const defaultHostRecoveryStart = bootSource.indexOf(
'async function promptForDefaultRuntimeHostRecovery',
@@ -152,12 +157,12 @@ test('lets the Runtime Host migrate its State Root before Desktop opens shared t
});
test('routes the first-paint IPC only to the active Renderer recovery listener', () => {
- const ipcHandlerStart = appIpcSource.indexOf(
- "targetIpc.handle('window:notifyRendererReady'",
+ const ipcHandlerStart = earlyWindowSource.indexOf(
+ 'ipcMain.handle("window:notifyRendererReady"',
);
- const ipcHandler = appIpcSource.slice(
+ const ipcHandler = earlyWindowSource.slice(
ipcHandlerStart,
- appIpcSource.indexOf("targetIpc.handle('window:setThemeSource'", ipcHandlerStart),
+ earlyWindowSource.indexOf('void quitCoordinator.focusOrCreateWindow()', ipcHandlerStart),
);
const readyHandlerStart = mainWindowSource.indexOf(
'notifyRendererReady(sender, senderFrame)',
diff --git a/apps/desktop/src/main/__tests__/startup-storage-repair.test.ts b/apps/desktop/src/main/__tests__/startup-storage-repair.test.ts
index 66847495c1..e8241fbdd6 100644
--- a/apps/desktop/src/main/__tests__/startup-storage-repair.test.ts
+++ b/apps/desktop/src/main/__tests__/startup-storage-repair.test.ts
@@ -44,14 +44,18 @@ async function compile(name: string, asynchronous = false): Promise<string> {
const imports = parse(source, { sourceType: 'module', plugins: ['typescript'] }).program.body
.filter((node) => node.type === 'ImportDeclaration');
const end = imports.at(-1)?.end ?? 0;
- source = `${source.slice(0, end)}\nexport default async function() {\n${source.slice(end)}\n}`;
+ // The wrapped body keeps the module's own `export` keywords (early-window
+ // exports its products) — they are illegal inside the function wrapper and
+ // the sandbox reaches the bindings through the deps object anyway.
+ const body = source.slice(end).replace(/\bexport\s+(?=(?:async\s+)?(?:function|const|let|var|class)\b)/gu, '');
+ source = `${source.slice(0, end)}\nexport default async function() {\n${body}\n}`;
}
return (await transform(source, {
loader: 'ts', format: 'cjs', target: 'esnext', define: { 'import.meta': 'importMeta' },
})).code;
}
-const boot = await compile('runtime-host-boot', true);
+const boot = await compile('early-window', true);
const context = await compile('startup-context');
for (const accept of [false, true]) {
diff --git a/apps/desktop/src/main/app-ipc-main.ts b/apps/desktop/src/main/app-ipc-main.ts
index d2dc25ff9c..5893a4e1f1 100644
--- a/apps/desktop/src/main/app-ipc-main.ts
+++ b/apps/desktop/src/main/app-ipc-main.ts
@@ -76,9 +76,10 @@ export function registerAppClientIpc(
targetIpc.handle('window:setTitlebarControlsVisible', (event, visible: unknown): void => {
mainWindowController.setTitlebarControlsVisible(event.sender, visible);
});
- targetIpc.handle('window:notifyRendererReady', (event): void => {
- mainWindowController.notifyRendererReady(event.sender, event.senderFrame);
- });
+ // `window:notifyRendererReady` is registered directly on ipcMain by
+ // early-window.js: it is a window-lifecycle signal that must exist before
+ // this scoped router does, or the first React commit can outrun it.
+
targetIpc.handle('window:setThemeSource', (event, themePref: unknown): void => {
mainWindowController.setThemeSource(event.sender, themePref);
});
diff --git a/apps/desktop/src/main/boot-context.ts b/apps/desktop/src/main/boot-context.ts
new file mode 100644
index 0000000000..e509aab139
--- /dev/null
+++ b/apps/desktop/src/main/boot-context.ts
@@ -0,0 +1,15 @@
+import type { DesktopDiagnosticsDeps } from './main-process-diagnostics.js';
+import type { RuntimeHostDesktopManager } from './runtime-host-desktop-manager.js';
+import type { DesktopTargetScope } from '../shared/runtime-host-identity.js';
+
+// Cross-module late bindings between the early window path and the Runtime
+// Host boot: the window is created while the heavy module graph is still
+// evaluating, so pieces the window needs early (diagnostics, quit hooks) read
+// the Host-side products through this holder once they exist.
+export const bootContext: {
+ runtimeHostManager?: RuntimeHostDesktopManager;
+ activeRuntimeHostRef?: () => DesktopTargetScope | undefined;
+ resolveRuntimeHostDiagnostics?: DesktopDiagnosticsDeps['resolveRuntimeHost'];
+ prepareToQuit?: () => Promise<'ready' | 'cancelled'>;
+ cleanup?: () => Promise<void>;
+} = {};
diff --git a/apps/desktop/src/main/early-window.ts b/apps/desktop/src/main/early-window.ts
new file mode 100644
index 0000000000..2f4008ec2b
--- /dev/null
+++ b/apps/desktop/src/main/early-window.ts
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// The light startup slice: everything the main window needs — storage root,
+// settings, locale, diagnostics, the window controller — and nothing else.
+// runtime-host-boot imports this module first so the window loads while the
+// heavy Runtime Host module graph is still evaluating.
+
+import { join } from "node:path";
+import {
+ app,
+ type BrowserWindow,
+ clipboard,
+ ipcMain,
+ type MessageBoxOptions,
+ type MessageBoxReturnValue,
+ nativeTheme,
+} from "electron";
+import { resolveSystemUiLocale } from "@maka/core/ui-locale";
+import { resolveStorageRoot } from "@maka/storage/root-authority";
+import { createSettingsStore } from "@maka/storage/settings-store";
+import { createAppQuitCoordinator } from "./app-quit-coordinator.js";
+import { bootContext } from "./boot-context.js";
+import { showBrowserMessageBox, type BrowserMessageBoxTheme } from "./browser-message-box.js";
+import { resolveBuildInfo } from "./build-info.js";
+import { createDesktopLocaleAuthority } from "./desktop-locale-authority.js";
+import { resolveE2eFixture, seedE2eFixture } from "./e2e-fixture.js";
+import {
+ captureDesktopDiagnosticEnvironment,
+ copyDesktopDiagnosticReport,
+ createDesktopMainRendererDiagnosticInput,
+ createDesktopStartupDiagnosticInput,
+ mainProcessLogBuffer,
+ runtimeHostProcessLogBuffer,
+ type DesktopDiagnosticsDeps,
+} from "./main-process-diagnostics.js";
+import { createMainWindowController } from "./main-window.js";
+import { getNativeDiagnosticDialogCopy } from "./native-diagnostic-dialog-copy.js";
+import {
+ showMainRendererProcessGoneDialog,
+ showMessageBoxWithDiagnostics,
+} from "./native-diagnostic-dialog.js";
+import { resolveShellEnv } from "./shell-env.js";
+import { revealMode } from "./startup-context.js";
+import { resolveDesktopStorageRoot } from "./storage-root-startup.js";
+import { startupStep } from "./startup-step.js";
+import { isDarkAppearance } from "./theme-source.js";
+import { desktopDiagnosticUpdateChannel } from "./app-update-attestation.js";
+
+// The login-shell PATH probe spawns the user's interactive shell — a hundred
+// milliseconds of .zshrc on a typical dev machine. Start it now but only
+// await it where a child process is actually spawned; window creation and
+// storage reads do not need it.
+export const shellEnvReady = resolveShellEnv();
+export const buildInfo = resolveBuildInfo(app.isPackaged, app.getAppPath());
+export const userDataDir = app.getPath("userData");
+
+export const e2eFixture = resolveDesktopE2eFixture();
+export const workspaceRoot = join(
+ userDataDir,
+ "workspaces",
+ e2eFixture?.workspaceName ?? "default",
+);
+
+// Delegates the Runtime Host boot assigns once its services exist; the window
+// controller reads them lazily so the window never waits on that wiring.
+export const mainWindowDelegates = {
+ onMainWindowClose: (): void => {},
+ onMainWindowClosed: (): void => {},
+};
+
+export const desktopDiagnostics: DesktopDiagnosticsDeps = {
+ environment: () =>
+ captureDesktopDiagnosticEnvironment({
+ appVersion: app.getVersion(),
+ buildMode: buildInfo.mode,
+ updateChannel: desktopDiagnosticUpdateChannel({
+ isPackaged: app.isPackaged,
+ appPath: app.getAppPath(),
+ }),
+ buildCommit: buildInfo.commit,
+ locale: app.getLocale(),
+ workspacePath: workspaceRoot,
+ }),
+ mainLogs: () => mainProcessLogBuffer.snapshot(),
+ runtimeHostProcessLogs: () => runtimeHostProcessLogBuffer.snapshot(),
+ runtimeHostConnections: () => bootContext.runtimeHostManager?.entries() ?? [],
+ resolveActiveRuntimeHost: () => {
+ const scope = bootContext.activeRuntimeHostRef?.();
+ return scope ? bootContext.resolveRuntimeHostDiagnostics?.(scope) : undefined;
+ },
+ resolveRuntimeHost: (scope) => {
+ const resolve = bootContext.resolveRuntimeHostDiagnostics;
+ if (!resolve) throw new Error("Desktop Runtime Host diagnostics are unavailable");
+ return resolve(scope);
+ },
+ writeClipboard: (report) => clipboard.writeText(report),
+};
+
+// The storage-root repair dialog can fire before settingsStore/desktopLocale
+// exist, so both resolvers start at safe defaults and are rebound below once
+// the settings-backed versions can actually run.
+let resolveBrowserDialogParent = (): BrowserWindow | undefined => undefined;
+let resolveBrowserDialogAppearance = async (): Promise<BrowserMessageBoxTheme> => ({
+ locale: resolveSystemUiLocale(app.getPreferredSystemLanguages()),
+ palette: "default",
+});
+
+export async function showDesktopMessageBox(
+ options: MessageBoxOptions,
+ override?: Partial<BrowserMessageBoxTheme>,
+): Promise<MessageBoxReturnValue> {
+ const appearance = {
+ ...(await resolveBrowserDialogAppearance()),
+ ...override,
+ revealMode,
+ };
+ return showBrowserMessageBox(options, resolveBrowserDialogParent(), appearance);
+}
+
+export function showStartupDiagnosticDialog(
+ options: MessageBoxOptions,
+ locale: ReturnType<typeof resolveSystemUiLocale>,
+ diagnosticDetails = options.detail,
+): Promise<MessageBoxReturnValue> {
+ return showMessageBoxWithDiagnostics(options, {
+ locale,
+ showMessageBox: (nextOptions) => showDesktopMessageBox(nextOptions, { locale }),
+ copyDiagnostics: () =>
+ copyDesktopDiagnosticReport(
+ desktopDiagnostics,
+ createDesktopStartupDiagnosticInput({
+ title: options.title || options.message,
+ description: options.message,
+ ...(diagnosticDetails ? { details: diagnosticDetails } : {}),
+ }),
+ ),
+ });
+}
+
+if (e2eFixture) {
+ console.log(
+ `[e2e-fixture] scenario=${e2eFixture.scenario} workspace=${workspaceRoot}`,
+ );
+ await seedE2eFixture({ workspaceRoot, fixture: e2eFixture });
+}
+const resolvedLocalStorageRoot = await (e2eFixture
+ ? resolveStorageRoot({ path: workspaceRoot, kind: "interactive" })
+ : startupStep(
+ "storage root",
+ resolveDesktopStorageRoot(workspaceRoot, {
+ confirmRepair: () => confirmDesktopStorageRootRepair(workspaceRoot),
+ }),
+ ));
+if (!resolvedLocalStorageRoot) {
+ app.quit();
+ await new Promise<never>(() => {});
+ throw new Error("Desktop storage root resolution did not complete");
+}
+export const startupLocalStorageRoot = resolvedLocalStorageRoot;
+export const settingsStore = createSettingsStore(workspaceRoot);
+export const desktopLocale = createDesktopLocaleAuthority({
+ readSettings: () => settingsStore.get(),
+ preferredSystemLanguages: () => app.getPreferredSystemLanguages(),
+});
+resolveBrowserDialogAppearance = async () => {
+ try {
+ const settings = await settingsStore.get();
+ return {
+ locale: desktopLocale.observe(settings),
+ palette: settings.appearance.palette,
+ dark: isDarkAppearance(
+ e2eFixture?.theme ?? settings.appearance.theme,
+ nativeTheme.shouldUseDarkColors,
+ ),
+ };
+ } catch {
+ return { locale: desktopLocale.current(), palette: "default" };
+ }
+};
+
+export const mainWindowController = createMainWindowController({
+ workspaceRoot,
+ e2eFixture,
+ settingsStore,
+ revealMode,
+ onClose: () => mainWindowDelegates.onMainWindowClose(),
+ onClosed: () => mainWindowDelegates.onMainWindowClosed(),
+ onRendererProcessGone: async (details) => {
+ const diagnosticInput = createDesktopMainRendererDiagnosticInput({
+ title: "Maka main Renderer process exited unexpectedly",
+ description: `Reason: ${details.reason}`,
+ details: `Exit code: ${details.exitCode}`,
+ });
+ for (;;) {
+ const locale = await desktopLocale.resolve();
+ const decision = await showMainRendererProcessGoneDialog({
+ locale,
+ copyDiagnostics: () =>
+ copyDesktopDiagnosticReport(desktopDiagnostics, diagnosticInput),
+ // showBrowserMessageBox attaches only to a visible, non-minimized
+ // parent. A pre-first-paint crash therefore gets a standalone window.
+ showMessageBox: (options) => showDesktopMessageBox(options, { locale }),
+ });
+ if (decision !== "recover") break;
+ if (await mainWindowController.reloadMainRenderer()) return;
+ if (!mainWindowController.browserWindow()) break;
+ }
+ app.quit();
+ },
+});
+resolveBrowserDialogParent = () => {
+ const main = mainWindowController.browserWindow();
+ return main?.isVisible() ? main : undefined;
+};
+
+export const quitCoordinator = createAppQuitCoordinator({
+ // Until the Runtime Host boot wires its quit hooks there is nothing to
+ // retire — a quit in that gap proceeds straight to cleanup.
+ prepareToQuit: () => bootContext.prepareToQuit?.() ?? Promise.resolve("ready" as const),
+ cleanup: () => bootContext.cleanup?.() ?? Promise.resolve(),
+ focusOrCreateWindow: (signal) => {
+ if (mainWindowController.hasOpenWindows()) mainWindowController.focus();
+ else return mainWindowController.createWindow(signal);
+ },
+ onPreparationError: (error) => {
+ console.error("[runtime-host] quit retirement failed:", error);
+ },
+ onCleanupError: (error) =>
+ console.error("[runtime-host] shutdown failed:", error),
+ onWindowCreationError: (error) =>
+ console.error("[window] creation failed:", error),
+ resumeQuit: () => app.quit(),
+});
+app.on("before-quit", quitCoordinator.handleBeforeQuit);
+// Renderer-ready is a window-lifecycle signal, not a Runtime Host scope: it
+// must be handled before the scoped IPC router exists, or the first React
+// commit could race ahead of target wiring and leave the window hidden.
+ipcMain.handle("window:notifyRendererReady", (event): void => {
+ mainWindowController.notifyRendererReady(event.sender, event.senderFrame);
+});
+// The window loads the renderer while the Runtime Host services assemble;
+// `ready-to-show` reveals the loading surface on the first painted frame.
+void quitCoordinator.focusOrCreateWindow();
+
+async function confirmDesktopStorageRootRepair(
+ workspaceRoot: string,
+): Promise<boolean> {
+ console.log(
+ "[storage-root] root-identity conflict; parking at repair dialog",
+ );
+ const locale = resolveSystemUiLocale(app.getPreferredSystemLanguages());
+ const copy = getNativeDiagnosticDialogCopy(locale).storageRootRepair;
+ const { response } = await showStartupDiagnosticDialog(
+ {
+ type: "warning",
+ title: copy.title,
+ message: copy.message,
+ detail: copy.detail(workspaceRoot),
+ buttons: [copy.repair, copy.exit],
+ defaultId: 1,
+ cancelId: 1,
+ noLink: true,
+ },
+ locale,
+ );
+ return response === 0;
+}
+
+function resolveDesktopE2eFixture(): ReturnType<typeof resolveE2eFixture> {
+ try {
+ return resolveE2eFixture(
+ process.env.MAKA_E2E_FIXTURE,
+ app.isPackaged,
+ process.env.MAKA_E2E_FIXTURE_REDUCED_MOTION,
+ process.env.MAKA_E2E_FIXTURE_THEME,
+ process.env.MAKA_E2E_FIXTURE_LOCALE,
+ process.env.MAKA_E2E_FIXTURE_TIMEZONE,
+ process.env.MAKA_E2E_FIXTURE_PLATFORM,
+ );
+ } catch (error) {
+ if (!process.env.MAKA_E2E_FIXTURE) throw error;
+ console.error(
+ `[e2e-fixture] fatal: ${error instanceof Error ? error.message : String(error)}`,
+ );
+ process.exit(1);
+ }
+}
diff --git a/apps/desktop/src/main/main.ts b/apps/desktop/src/main/main.ts
index 110bc4c3d5..a8944cb453 100644
--- a/apps/desktop/src/main/main.ts
+++ b/apps/desktop/src/main/main.ts
@@ -197,9 +197,13 @@ if (!app.requestSingleInstanceLock()) {
// store/db write".
app
.whenReady()
- .then(() => {
+ .then(async () => {
console.log('[startup] app ready');
installDesktopStartupBranding(revealMode);
+ // early-window holds the light slice (storage root, settings, window
+ // controller) and fires the renderer load; the heavy Runtime Host
+ // module graph evaluates while the window is already loading.
+ await import('./early-window.js');
return import('./runtime-host-boot.js');
})
.catch(async (error: unknown) => {
diff --git a/apps/desktop/src/main/runtime-host-boot.ts b/apps/desktop/src/main/runtime-host-boot.ts
index 5fc52bb2db..3851e2f715 100644
--- a/apps/desktop/src/main/runtime-host-boot.ts
+++ b/apps/desktop/src/main/runtime-host-boot.ts
@@ -30,8 +30,6 @@ import {
powerSaveBlocker,
shell,
Tray,
- type MessageBoxOptions,
- type MessageBoxReturnValue,
} from "electron";
import { randomUUID } from "node:crypto";
import { readFileSync } from "node:fs";
@@ -39,7 +37,6 @@ import { join } from "node:path";
import { type ConnectionEvent } from '@maka/core/connections';
import { type SessionChangedEvent, type SessionChangedReason } from '@maka/core/session';
import { isBotDeliveryProvider } from '@maka/core/bot-chat-settings';
-import { resolveSystemUiLocale } from '@maka/core/ui-locale';
import {
PROVIDER_REGISTRY,
providerAuthRequiresSecret,
@@ -77,8 +74,6 @@ import { createWorkBoardStore } from "@maka/storage/work-board-store";
import { normalizeWorkBoardLinkedSession } from "@maka/core/work-board";
import { createFileCredentialStore } from "@maka/storage/credential-store";
import { createMcpConfigStore } from "@maka/storage/mcp-config-store";
-import { createSettingsStore } from "@maka/storage/settings-store";
-import { resolveStorageRoot } from "@maka/storage/root-authority";
import { createMcpOAuthController } from "./mcp-oauth-controller.js";
import { CommandCodeBrowserLoginController } from "./commandcode-browser-login.js";
@@ -89,7 +84,23 @@ import { createWorkHubRuntime } from './workhub-runtime.js';
import { createWindowsAppTray } from './windows-app-tray.js';
import { readableAppIconPath } from './app-icon-surface.js';
import { registerAppClientIpc, registerAppIpc } from "./app-ipc-main.js";
-import { createAppQuitCoordinator } from "./app-quit-coordinator.js";
+import { bootContext } from "./boot-context.js";
+import {
+ buildInfo,
+ desktopDiagnostics,
+ desktopLocale,
+ e2eFixture,
+ mainWindowController,
+ mainWindowDelegates,
+ quitCoordinator,
+ settingsStore,
+ shellEnvReady,
+ showDesktopMessageBox,
+ showStartupDiagnosticDialog,
+ startupLocalStorageRoot,
+ userDataDir,
+ workspaceRoot,
+} from "./early-window.js";
import {
desktopDiagnosticUpdateChannel,
desktopUpdateChannelFromManifest,
@@ -107,51 +118,29 @@ import { browserViewHost } from "./browser/browser-host.js";
import { releaseBrowserSession } from "./browser/session.js";
import {
isBrowserMessageBoxPresentationActive,
- showBrowserMessageBox,
- type BrowserMessageBoxTheme,
} from "./browser-message-box.js";
import { createE2eFixtureBotOnboardingAdapters } from "./bot-onboarding-e2e-fixture.js";
-import { resolveBuildInfo } from "./build-info.js";
import { computerUseServiceHealth } from "./computer-use-host.js";
import { registerDesktopDiagnosticsIpc } from "./desktop-diagnostics-ipc-main.js";
import { assembleDesktopNativeCapabilities } from "./desktop-native-capability-assembly.js";
import { clientSettingsConfirmation } from "./client-settings-confirmation-copy.js";
import { nativeFileDialogCopy } from "./native-file-dialog-copy.js";
-import { createDesktopLocaleAuthority } from "./desktop-locale-authority.js";
import { buildRiveWorkflowTool } from "./rive-workflow-tool.js";
import { applyAppIcon } from "./app-icon-surface.js";
import { registerAppIconIpc } from "./app-icon-ipc.js";
import { listAppIconPreviews } from "./app-icon-surface.js";
import { importCustomAppIcon } from "./custom-app-icons.js";
import { installDesktopShellPresentation } from "./desktop-shell-presentation.js";
-import {
- resolveE2eFixture,
- seedE2eFixture,
-} from "./e2e-fixture.js";
import { PARTIAL_HISTORY_TRANSCRIPT_BYTES } from "./e2e-fixture/seed-helpers.js";
import { createKeepSystemAwakeController } from "./keep-system-awake.js";
-import { isDarkAppearance } from "./theme-source.js";
import {
readWithFallback,
type ReconnectableReadIpcMain,
} from "./ipc-reconnect-policy.js";
-import { createMainWindowController } from "./main-window.js";
import type { DesktopRuntimeHostIdentity } from "../preload/bridge-contract.js";
-import {
- captureDesktopDiagnosticEnvironment,
- copyDesktopDiagnosticReport,
- createDesktopMainRendererDiagnosticInput,
- createDesktopStartupDiagnosticInput,
- mainProcessLogBuffer,
- runtimeHostProcessLogBuffer,
- type DesktopDiagnosticsDeps,
-} from "./main-process-diagnostics.js";
import {
defaultRuntimeHostRecoveryDialog,
- showMainRendererProcessGoneDialog,
- showMessageBoxWithDiagnostics,
} from "./native-diagnostic-dialog.js";
-import { getNativeDiagnosticDialogCopy } from "./native-diagnostic-dialog-copy.js";
import {
resolveDesktopSessionWorkspace,
} from "./new-session-project.js";
@@ -258,7 +247,6 @@ import {
import { registerRuntimeHostSkillsIpc } from "./runtime-host-skills-ipc-main.js";
import { registerRuntimeHostUsageIpc } from "./runtime-host-usage-ipc-main.js";
import { registerRuntimeHostWorkspaceIpc } from "./runtime-host-workspace-ipc-main.js";
-import { resolveShellEnv } from "./shell-env.js";
import {
registerSettingsBotsIpc,
type SettingsBotsIpcHandle,
@@ -269,8 +257,6 @@ import {
isIsolatedE2e,
revealMode,
} from "./startup-context.js";
-import { resolveDesktopStorageRoot } from "./storage-root-startup.js";
-import { startupStep } from "./startup-step.js";
import { registerWorkspaceSearchIpc } from "./workspace-search-ipc-main.js";
import {
parseDesktopSessionResourceKey,
@@ -278,11 +264,11 @@ import {
type DesktopTargetScope,
} from "../shared/runtime-host-identity.js";
-await resolveShellEnv();
-
const MANAGED_UPDATE_RECONNECT_TIMEOUT_MS = 10_000;
-const buildInfo = resolveBuildInfo(app.isPackaged, app.getAppPath());
-const userDataDir = app.getPath("userData");
+bootContext.prepareToQuit = prepareRuntimeHostDesktopQuit;
+bootContext.cleanup = closeRuntimeHostDesktop;
+bootContext.activeRuntimeHostRef = activeRuntimeHostRef;
+bootContext.resolveRuntimeHostDiagnostics = resolveRuntimeHostDiagnostics;
const runtimeHostPeerConfiguration = await configureDesktopRuntimeHostPeerClient({
isPackaged: app.isPackaged,
enableDevelopmentPeer: process.argv.includes('--runtime-host-peer'),
@@ -329,19 +315,19 @@ if (runtimeHostPeerConfiguration) {
}
}
const runtimeHostDirectPeerAvailable = runtimeHostPeerClient !== undefined;
-const runtimeHostClientInstanceId = await loadOrCreateRuntimeHostClientInstanceId(
- join(userDataDir, "runtime-host-client.json"),
-);
const runtimeHostCandidateLaunchBarrier = createRuntimeHostCandidateLaunchBarrier();
const runtimeHostCredentialStore = createClientRuntimeHostCredentialStore(userDataDir);
const runtimeHostProfileCatalog = createClientRuntimeHostProfileCatalog(
userDataDir,
runtimeHostCredentialStore,
);
-const runtimeHostStartup = await resolveDesktopRuntimeHostStartup(userDataDir, {
- catalog: runtimeHostProfileCatalog,
- credentialStore: runtimeHostCredentialStore,
-});
+const [runtimeHostClientInstanceId, runtimeHostStartup] = await Promise.all([
+ loadOrCreateRuntimeHostClientInstanceId(join(userDataDir, "runtime-host-client.json")),
+ resolveDesktopRuntimeHostStartup(userDataDir, {
+ catalog: runtimeHostProfileCatalog,
+ credentialStore: runtimeHostCredentialStore,
+ }),
+]);
let runtimeHostManager: RuntimeHostDesktopManager | undefined;
function activeRuntimeHostRef(): DesktopTargetScope | undefined {
const current = runtimeHostManager?.current();
@@ -350,111 +336,7 @@ function activeRuntimeHostRef(): DesktopTargetScope | undefined {
: undefined;
}
const runtimeHostGeneration = app.isPackaged ? app.getVersion() : randomUUID();
-const e2eFixture = resolveDesktopE2eFixture();
const useBotOnboardingFixture = e2eFixture?.scenario === "settings-bots-onboarding";
-const workspaceRoot = join(
- userDataDir,
- "workspaces",
- e2eFixture?.workspaceName ?? "default",
-);
-const desktopDiagnostics: DesktopDiagnosticsDeps = {
- environment: () =>
- captureDesktopDiagnosticEnvironment({
- appVersion: app.getVersion(),
- buildMode: buildInfo.mode,
- updateChannel: desktopDiagnosticUpdateChannel({
- isPackaged: app.isPackaged,
- appPath: app.getAppPath(),
- }),
- buildCommit: buildInfo.commit,
- locale: app.getLocale(),
- workspacePath: workspaceRoot,
- }),
- mainLogs: () => mainProcessLogBuffer.snapshot(),
- runtimeHostProcessLogs: () => runtimeHostProcessLogBuffer.snapshot(),
- runtimeHostConnections: () => runtimeHostManager?.entries() ?? [],
- resolveActiveRuntimeHost: () => {
- const scope = activeRuntimeHostRef();
- return scope ? resolveRuntimeHostDiagnostics(scope) : undefined;
- },
- resolveRuntimeHost: resolveRuntimeHostDiagnostics,
- writeClipboard: (report) => clipboard.writeText(report),
-};
-let resolveBrowserDialogParent = (): BrowserWindow | undefined => undefined;
-let resolveBrowserDialogAppearance = async (): Promise<BrowserMessageBoxTheme> => ({
- locale: resolveSystemUiLocale(app.getPreferredSystemLanguages()),
- palette: "default",
-});
-
-async function showDesktopMessageBox(
- options: MessageBoxOptions,
- override?: Partial<BrowserMessageBoxTheme>,
-): Promise<MessageBoxReturnValue> {
- const appearance = { ...(await resolveBrowserDialogAppearance()), ...override, revealMode };
- return showBrowserMessageBox(options, resolveBrowserDialogParent(), appearance);
-}
-
-function showStartupDiagnosticDialog(
- options: MessageBoxOptions,
- locale: ReturnType<typeof resolveSystemUiLocale>,
- diagnosticDetails = options.detail,
-): Promise<MessageBoxReturnValue> {
- return showMessageBoxWithDiagnostics(options, {
- locale,
- showMessageBox: (nextOptions) => showDesktopMessageBox(nextOptions, { locale }),
- copyDiagnostics: () =>
- copyDesktopDiagnosticReport(
- desktopDiagnostics,
- createDesktopStartupDiagnosticInput({
- title: options.title || options.message,
- description: options.message,
- ...(diagnosticDetails ? { details: diagnosticDetails } : {}),
- }),
- ),
- });
-}
-if (e2eFixture) {
- console.log(
- `[e2e-fixture] scenario=${e2eFixture.scenario} workspace=${workspaceRoot}`,
- );
- await seedE2eFixture({ workspaceRoot, fixture: e2eFixture });
-}
-const resolveLocalStorageRoot = () =>
- e2eFixture
- ? resolveStorageRoot({ path: workspaceRoot, kind: "interactive" })
- : startupStep(
- "storage root",
- resolveDesktopStorageRoot(workspaceRoot, {
- confirmRepair: () => confirmDesktopStorageRootRepair(workspaceRoot),
- }),
- );
-const startupLocalStorageRoot =
- await resolveLocalStorageRoot();
-if (!startupLocalStorageRoot) {
- app.quit();
- await new Promise<never>(() => {});
- throw new Error("Desktop storage root resolution did not complete");
-}
-const settingsStore = createSettingsStore(workspaceRoot);
-const desktopLocale = createDesktopLocaleAuthority({
- readSettings: () => settingsStore.get(),
- preferredSystemLanguages: () => app.getPreferredSystemLanguages(),
-});
-resolveBrowserDialogAppearance = async () => {
- try {
- const settings = await settingsStore.get();
- return {
- locale: desktopLocale.observe(settings),
- palette: settings.appearance.palette,
- dark: isDarkAppearance(
- e2eFixture?.theme ?? settings.appearance.theme,
- nativeTheme.shouldUseDarkColors,
- ),
- };
- } catch {
- return { locale: desktopLocale.current(), palette: "default" };
- }
-};
const mcpConfigStore = createMcpConfigStore(workspaceRoot);
const mcpManager = new McpClientManager({
clientName: "maka-desktop",
@@ -480,9 +362,9 @@ const mcpOAuthController = createMcpOAuthController({
let mcpStartup: Promise<void> | undefined;
function ensureMcpReady(): Promise<void> {
if (!mcpStartup) {
- const startup = mcpConfigStore
- .get()
- .then((config) => mcpManager.sync(config));
+ const startup = shellEnvReady.then(() =>
+ mcpConfigStore.get().then((config) => mcpManager.sync(config)),
+ );
mcpStartup = startup;
void startup.catch(() => {
if (mcpStartup === startup) mcpStartup = undefined;
@@ -491,42 +373,6 @@ function ensureMcpReady(): Promise<void> {
return mcpStartup;
}
const keepSystemAwake = createKeepSystemAwakeController(powerSaveBlocker);
-let onMainWindowClose = (): void => {};
-let onMainWindowClosed = (): void => {};
-const mainWindowController = createMainWindowController({
- workspaceRoot,
- e2eFixture,
- settingsStore,
- revealMode,
- onClose: () => onMainWindowClose(),
- onClosed: () => onMainWindowClosed(),
- onRendererProcessGone: async (details) => {
- const diagnosticInput = createDesktopMainRendererDiagnosticInput({
- title: "Maka main Renderer process exited unexpectedly",
- description: `Reason: ${details.reason}`,
- details: `Exit code: ${details.exitCode}`,
- });
- for (;;) {
- const locale = await desktopLocale.resolve();
- const decision = await showMainRendererProcessGoneDialog({
- locale,
- copyDiagnostics: () =>
- copyDesktopDiagnosticReport(desktopDiagnostics, diagnosticInput),
- // showBrowserMessageBox attaches only to a visible, non-minimized
- // parent. A pre-first-paint crash therefore gets a standalone window.
- showMessageBox: (options) => showDesktopMessageBox(options, { locale }),
- });
- if (decision !== "recover") break;
- if (await mainWindowController.reloadMainRenderer()) return;
- if (!mainWindowController.browserWindow()) break;
- }
- app.quit();
- },
-});
-resolveBrowserDialogParent = () => {
- const main = mainWindowController.browserWindow();
- return main?.isVisible() ? main : undefined;
-};
const runtimeHostSshTerminal = createDesktopRuntimeHostSshTerminal({
ipcMain,
send: (channel, event) => mainWindowController.send(channel, event),
@@ -574,7 +420,7 @@ const releaseDesktopInteractionSession = (sessionId: string): void => {
const permissionOverlay = createPermissionOverlayMain({
resolveLocale: () => desktopLocale.resolve(),
});
-onMainWindowClose = () => {
+mainWindowDelegates.onMainWindowClose = () => {
native.computerUseOverlay.destroyAll();
native.computerUsePip.destroyAll();
};
@@ -952,7 +798,7 @@ const windowsAppTray = createWindowsAppTray({
quit: () => app.quit(),
onError: (error) => console.error('[tray]', error),
});
-onMainWindowClosed = () => {
+mainWindowDelegates.onMainWindowClosed = () => {
// A hidden WorkHub host window can keep window-all-closed from firing.
// Without a tray, use the existing quit flow; cancelling it restores Maka.
if (process.platform !== 'darwin' && !windowsAppTray.hasTray()) app.quit();
@@ -1416,34 +1262,18 @@ let workBoardIpc: ReturnType<typeof registerWorkBoardIpc> | undefined;
let runtimeHostDesktopShutdown: Promise<void> | undefined;
// The quit coordinator owns cleanup for every later stage, including a Host
// handoff cancelled while the main window is still loading.
-const quitCoordinator = createAppQuitCoordinator({
- prepareToQuit: prepareRuntimeHostDesktopQuit,
- cleanup: closeRuntimeHostDesktop,
- focusOrCreateWindow: (signal) => {
- if (!runtimeHostManager) return;
- if (mainWindowController.hasOpenWindows()) mainWindowController.focus();
- else return mainWindowController.createWindow(signal);
- },
- onPreparationError: (error) => {
- console.error("[runtime-host] quit retirement failed:", error);
- },
- onCleanupError: (error) =>
- console.error("[runtime-host] shutdown failed:", error),
- onWindowCreationError: (error) =>
- console.error("[window] creation failed:", error),
- resumeQuit: () => app.quit(),
-});
-app.on("before-quit", quitCoordinator.handleBeforeQuit);
// The manager registers its IPC router on construction; starting the Local
// Host is a background reconciliation, not a prerequisite for the window.
runtimeHostManager = createLocalRuntimeHostManager();
+bootContext.runtimeHostManager = runtimeHostManager;
runtimeHostManager.setDefaultProfile(runtimeHostStartup.preferences.defaultProfileId);
wireLifecycle();
sessionLocal.wake();
windowsAppTray.start();
// Remote profiles do not depend on the Local Host: a handoff parked on a
-// user decision must not hold their activation for the whole session.
-void runtimeHostProfileService.startEnabledProfiles();
+// user decision must not hold their activation for the whole session. Remote
+// transports spawn SSH/relay children, so they still wait on the shell PATH.
+void shellEnvReady.then(() => runtimeHostProfileService.startEnabledProfiles());
// Runtime Host is the only schema-migration authority for its State Root.
// Work Board remains a Desktop-owned table, but it opens only while a ready
// Host has verified the schema — including a Local Host that only becomes
@@ -1484,6 +1314,7 @@ const registerDesktopWorkBoard = (): void => {
}
};
void (async () => {
+ await shellEnvReady;
await runtimeHostManager?.start();
registerDesktopWorkBoard();
await guestSessionMountService.start().catch((error: unknown) => {
@@ -2246,50 +2077,6 @@ function wakePeerRecoveryAfterResume(): void {
runtimeHostManager?.wakePeerRecovery();
}
-function resolveDesktopE2eFixture(): ReturnType<typeof resolveE2eFixture> {
- try {
- return resolveE2eFixture(
- process.env.MAKA_E2E_FIXTURE,
- app.isPackaged,
- process.env.MAKA_E2E_FIXTURE_REDUCED_MOTION,
- process.env.MAKA_E2E_FIXTURE_THEME,
- process.env.MAKA_E2E_FIXTURE_LOCALE,
- process.env.MAKA_E2E_FIXTURE_TIMEZONE,
- process.env.MAKA_E2E_FIXTURE_PLATFORM,
- );
- } catch (error) {
- if (!process.env.MAKA_E2E_FIXTURE) throw error;
- console.error(
- `[e2e-fixture] fatal: ${error instanceof Error ? error.message : String(error)}`,
- );
- process.exit(1);
- }
-}
-
-async function confirmDesktopStorageRootRepair(
- workspaceRoot: string,
-): Promise<boolean> {
- console.log(
- "[storage-root] root-identity conflict; parking at repair dialog",
- );
- const locale = resolveSystemUiLocale(app.getPreferredSystemLanguages());
- const copy = getNativeDiagnosticDialogCopy(locale).storageRootRepair;
- const { response } = await showStartupDiagnosticDialog(
- {
- type: "warning",
- title: copy.title,
- message: copy.message,
- detail: copy.detail(workspaceRoot),
- buttons: [copy.repair, copy.exit],
- defaultId: 1,
- cancelId: 1,
- noLink: true,
- },
- locale,
- );
- return response === 0;
-}
-
async function promptForDefaultRuntimeHostRecovery(input: {
readonly profileId: string;
readonly profileName: string;
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org