update
Some checks failed
CI / check (push) Has been cancelled

This commit is contained in:
2026-06-17 17:35:45 +02:00
parent 513af0e164
commit 6beef86506
2 changed files with 34 additions and 4 deletions

View File

@@ -5,6 +5,22 @@ import { spawn } from 'node:child_process'
const delay = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms))
/** Map `fn` over `items` with at most `limit` running at once, preserving order.
* Keeps the per-file git/disk reads overlapping without spawning hundreds of
* subprocesses at once (macOS has a low default open-file limit). */
async function mapLimit<T, R>(items: T[], limit: number, fn: (item: T, index: number) => Promise<R>): Promise<R[]> {
const out = new Array<R>(items.length)
let next = 0
const worker = async (): Promise<void> => {
while (next < items.length) {
const i = next++
out[i] = await fn(items[i], i)
}
}
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker))
return out
}
export type GitStatusLetter = 'A' | 'M' | 'D' | 'R' | 'U'
export interface GitChange {
@@ -193,15 +209,19 @@ async function doLoad(root: string): Promise<GitLoad | null> {
if (raw == null) return null
const { branch, files } = parseStatus(raw)
const changes: GitChange[] = []
for (const f of files) {
// Each changed file needs its HEAD blob (a `git show` spawn) + its disk text.
// Done serially this is O(files) subprocess spawns in a row — staging one file
// re-reads ALL of them, which is the dominant cost of a reload. Run them with
// bounded concurrency instead so the spawns overlap (cap keeps us well under
// macOS's low default FD limit). Order is preserved by index.
const changes = await mapLimit(files, 12, async (f) => {
const { letter, staged } = classify(f.index, f.working)
const isNew = f.index === '?' || f.index === 'A'
const isDeleted = letter === 'D'
const original = isNew ? '' : await headText(root, f.path)
const updated = isDeleted ? '' : await diskText(root, f.path)
changes.push({ path: f.path, status: letter, staged, original, updated })
}
return { path: f.path, status: letter, staged, original, updated } as GitChange
})
return { branch, changes }
}

View File

@@ -30,11 +30,21 @@ let seq = 0
* `npm_config_prefix` (e.g. "/opt/homebrew") into the child shell, which makes
* nvm refuse to load ("nvm is not compatible with the npm_config_prefix
* environment variable"). Strip it so the user's shell init runs cleanly.
*
* A macOS app launched from Spotlight/Finder (launchd GUI context) inherits NO
* `LANG`/`LC_*`, so the child shell falls back to the `C`/POSIX locale — not
* UTF-8. Anything multibyte the shell or `claude` emits then renders as high-byte
* mojibake in xterm. Launching from a terminal (`npm run dev`) inherits the
* terminal's UTF-8 locale, which is why dev looks fine and the packaged app does
* not. Default a UTF-8 locale when none is set so both paths match.
*/
function ptyEnv(): { [key: string]: string } {
const env = { ...process.env } as { [key: string]: string }
delete env.npm_config_prefix
delete env.npm_config_globalconfig
if (process.platform !== 'win32' && !env.LC_ALL && !env.LC_CTYPE && !env.LANG) {
env.LANG = 'en_US.UTF-8'
}
return env
}