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:02 UTC
(maka) branch feat/5488-ui-first-startup created (now a01dc00439)
This is an automated email from the ASF dual-hosted git repository.
Astro-Han pushed a change to branch feat/5488-ui-first-startup
in repository https://gitbox.apache.org/repos/asf/maka.git
at a01dc00439 feat(desktop): fire the window before the Runtime Host module graph
This branch includes the following new commits:
new 4180f77b21 feat(desktop): reveal the window on the first painted frame
new a01dc00439 feat(desktop): fire the window before the Runtime Host module graph
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org
(maka) 02/02: feat(desktop): fire the window before the Runtime Host module graph
Posted by as...@apache.org.
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
(maka) 01/02: feat(desktop): reveal the window on the first painted frame
Posted by as...@apache.org.
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 4180f77b21ed9a9044dec8cdee837db624320fa7
Author: AstroHan <le...@outlook.com>
AuthorDate: Sat Sep 19 11:43:02 2026 +0800
feat(desktop): reveal the window on the first painted frame
The window stayed hidden until the renderer's first React commit, so the
designed .maka-preload loading surface — meant to be the loading UI —
was never visible and perceived startup was bounded by React mount
(~750-940ms). Electron's ready-to-show fires as soon as the skeleton has
painted; routing it through the existing reveal gate shows the window
~500ms earlier while keeping inactive/hidden modes, deferred focus, and
the notifyRendererReady/fallback backstops intact. Perceived startup now
matches the UI-first goal: the window appears with the loading surface
while Runtime Host reconciliation continues in the background.
Generated-by: Devin
---
apps/desktop/src/main/main-window.ts | 27 +++++++++++++++++----------
apps/desktop/src/main/window-reveal.ts | 4 ++--
2 files changed, 19 insertions(+), 12 deletions(-)
diff --git a/apps/desktop/src/main/main-window.ts b/apps/desktop/src/main/main-window.ts
index 88a32f8ee2..88eda08c0b 100644
--- a/apps/desktop/src/main/main-window.ts
+++ b/apps/desktop/src/main/main-window.ts
@@ -415,12 +415,10 @@ export function createMainWindowController(deps: MainWindowControllerDeps): Main
minHeight: SAFE_MIN_HEIGHT,
backgroundColor: initialBg,
// PR-SHOW-AFTER-FIRST-COMMIT: create hidden on every run so the OS never
- // flashes the index.html `.maka-preload` skeleton before React paints.
- // The renderer signals `window:notifyRendererReady` after its first
- // commit (app.tsx) and a fallback timer below reveals the window if that
- // signal never arrives; the reveal gate (showWindowOnceReady) keeps the
- // window hidden until the first real content can paint, so the app
- // never flashes the `.maka-preload` skeleton past it.
+ // shows an unpainted window; `ready-to-show` reveals it on the first
+ // painted frame (the `.maka-preload` loading surface), and the reveal
+ // gate (showWindowOnceReady) routes that plus the renderer-ready IPC,
+ // the fallback timer, and deferred focus/maximize through the mode.
show: false,
// Native sidebar vibrancy lets the CSS-side sidebar render
// transparent and inherit the system's blurred window material
@@ -446,6 +444,15 @@ export function createMainWindowController(deps: MainWindowControllerDeps): Main
});
mainWindowShutdownSignal = signal;
observeRendererProcess(mainWindow, signal);
+ // The designed `.maka-preload` surface is the loading UI: reveal on the
+ // first painted frame instead of waiting out the whole React mount.
+ // markReady is mode-suppressed (hidden/inactive) and idempotent, so the
+ // later `window:notifyRendererReady` signal and the fallback timer stay
+ // as no-op backstops.
+ mainWindow.once('ready-to-show', () => {
+ clearShowFallbackTimer();
+ revealGate.markReady(mainWindow);
+ });
installMainWindowPermissionPolicy(mainWindow.webContents, rendererEntry.url);
// Two-layer external-link hygiene: assistant markdown often emits `<a href>`
@@ -726,10 +733,10 @@ export function createMainWindowController(deps: MainWindowControllerDeps): Main
},
focus() {
// ChatGPT Pro review P2: second-instance / activate must not show() the
- // still-hidden window ahead of the renderer's first commit — that would
- // flash the `.maka-preload` skeleton past the reveal gate. The gate
- // defers the request and flushes it (restore+show+focus) on markReady;
- // after that, focus behaves exactly as before.
+ // still-hidden window before it has painted — that would flash an
+ // unpainted frame past the reveal gate. The gate defers the request
+ // and flushes it (restore+show+focus) on markReady; after that, focus
+ // behaves exactly as before.
revealGate.requestFocus(mainWindow);
},
isFocused() {
diff --git a/apps/desktop/src/main/window-reveal.ts b/apps/desktop/src/main/window-reveal.ts
index 0e1c1f91ec..9e89cf4036 100644
--- a/apps/desktop/src/main/window-reveal.ts
+++ b/apps/desktop/src/main/window-reveal.ts
@@ -137,9 +137,9 @@ export interface WindowRevealGate {
/**
* Readiness-aware wrapper around showWindowOnceReady. Focus requests that
- * arrive before the renderer's first commit (user re-launches or clicks the
+ * arrive before the window has painted (user re-launches or clicks the
* dock icon while the window is still hidden) must NOT show() the window —
- * that would flash the `.maka-preload` skeleton the hidden creation exists to
+ * that would flash an unpainted frame the hidden creation exists to
* suppress. They are remembered and flushed as show()+focus() when markReady
* fires, so the user's foreground intent is honored, just not early.
*
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org