From 8aab742c69f1cf4f025d0afe0e639a24a383bae6 Mon Sep 17 00:00:00 2001 From: Bill Brown Date: Thu, 27 Aug 2026 14:25:06 -0700 Subject: [PATCH] Improved Electron startup performance. --- core/app/components/walkthrough/CommitView.tsx | 14 ++++++++++++-- electron/plan-review.cjs | 5 +++-- electron/walkthrough-commit.cjs | 8 ++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/core/app/components/walkthrough/CommitView.tsx b/core/app/components/walkthrough/CommitView.tsx index a5dca493..dd5e990e 100644 --- a/core/app/components/walkthrough/CommitView.tsx +++ b/core/app/components/walkthrough/CommitView.tsx @@ -2,7 +2,7 @@ import { ArrowsClockwiseIcon as ArrowsClockwise } from '@phosphor-icons/react/Ar import { CheckIcon as Check } from '@phosphor-icons/react/Check'; import { GitBranchIcon as GitBranch } from '@phosphor-icons/react/GitBranch'; import { XIcon as X } from '@phosphor-icons/react/X'; -import { init as initGhostty, Terminal } from 'ghostty-web'; +import type { Terminal } from 'ghostty-web'; import { useEffect, useRef, useState } from 'react'; import { changeTypeLabel, @@ -78,6 +78,16 @@ function resolveBackdrop(element: HTMLElement): string { return '#00000000'; } +// `ghostty-web` inlines its WASM binary as a ~550KB base64 data URL, and only +// the commit log terminal needs it. Importing it on demand keeps that payload +// out of the entry bundle every window parses before first paint. +let ghosttyModule: Promise | null = null; +const loadGhostty = async () => { + const ghostty = await (ghosttyModule ??= import('ghostty-web')); + await ghostty.init(); + return ghostty; +}; + /** * Read-only ghostty-web terminal that replays the streamed commit output, so * ANSI colors and cursor movement from pre-commit hooks render as they would @@ -97,7 +107,7 @@ function CommitLogTerminal({ output }: { output: string }) { let instance: Terminal | null = null; // ghostty-web loads its WASM module asynchronously; output streamed in the // meantime is replayed by the write effect once the terminal exists. - void initGhostty().then(() => { + void loadGhostty().then(({ Terminal }) => { if (disposed) { return; } diff --git a/electron/plan-review.cjs b/electron/plan-review.cjs index 9ae3d88f..f185b639 100644 --- a/electron/plan-review.cjs +++ b/electron/plan-review.cjs @@ -3,7 +3,6 @@ const { createHash, randomUUID } = require('node:crypto'); const { mkdir, open, readFile, rename, unlink } = require('node:fs/promises'); const { dirname, join, resolve } = require('node:path'); -const lockfile = require('proper-lockfile'); const MAX_PLAN_REVIEW_BYTES = 2 * 1024 * 1024; const PLAN_REVIEW_LOCK_STALE_MS = 10_000; @@ -144,7 +143,9 @@ const readPlanReviewAtPath = async (path) => { /** @template T @param {string} path @param {() => Promise} operation */ const withPlanReviewLock = async (path, operation) => { await mkdir(dirname(path), { recursive: true }); - const release = await lockfile.lock(path, { + // Only plan reviews take this lock, so `proper-lockfile` and its dependency + // tree stay out of the startup path. + const release = await require('proper-lockfile').lock(path, { realpath: false, retries: { factor: 1, diff --git a/electron/walkthrough-commit.cjs b/electron/walkthrough-commit.cjs index 92cb22fc..8063e6ba 100644 --- a/electron/walkthrough-commit.cjs +++ b/electron/walkthrough-commit.cjs @@ -9,10 +9,13 @@ const { accessSync, chmodSync, constants, mkdtempSync, rmSync, writeFileSync } = const { tmpdir } = require('node:os'); const { dirname, join } = require('node:path'); -const pty = require('node-pty'); - const { git, validateRepositoryPath } = require('./git-state/common.cjs'); +// `node-pty` is a native addon and only a walkthrough commit needs it, so it is +// loaded on first use rather than at startup. +/** @returns {typeof import('node-pty')} */ +const loadPty = () => require('node-pty'); + /** * @typedef {import('../core/types.ts').WalkthroughCommitRequest} WalkthroughCommitRequest * @typedef {import('../core/types.ts').WalkthroughCommitResult} WalkthroughCommitResult @@ -73,6 +76,7 @@ const normalizeTerminalOutput = (text) => */ const gitStreaming = (repoPath, args, onOutput) => new Promise((resolve, reject) => { + const pty = loadPty(); ensureSpawnHelperIsExecutable(); /** @type {import('node-pty').IPty} */ let child;