fix(desktop): resolve PortableGit for update checks + reserve titlebar tools space

- runGit() hardcoded spawn('git'), which ENOENTs on fresh installer-driven
  Windows installs (git is PortableGit under %LOCALAPPDATA%\hermes\git, never
  on PATH) — so "Check for updates" failed with "Couldn't check for updates".
  Add resolveGitBinary() mirroring findGitBash (PortableGit → Git-for-Windows
  → PATH) and use it in runGit.
- PageSearchShell rendered a full-width search input in the titlebar row, so
  on Windows its right edge slid under the fixed top-right tools + native
  window controls. Reserve that footprint via --titlebar-tools-* vars.
This commit is contained in:
Brooklyn Nicholson 2026-05-29 19:39:22 -05:00
parent 8f29ad23c2
commit 5e7a7f6a38
2 changed files with 46 additions and 7 deletions

View file

@ -926,6 +926,35 @@ function getVenvPython(venvRoot) {
return path.join(venvRoot, IS_WINDOWS ? path.join('Scripts', 'python.exe') : path.join('bin', 'python'))
}
// resolveGitBinary — locate git.exe on Windows. A fresh installer-driven
// install only has PortableGit under %LOCALAPPDATA%\hermes\git (never on
// PATH), so a bare spawn('git') ENOENTs and self-update checks fail with
// "Couldn't check for updates". Mirror findGitBash: PortableGit first, then
// standard Git-for-Windows locations, then PATH. Cached after first probe.
let _gitBinaryCache = null
function resolveGitBinary() {
if (_gitBinaryCache) return _gitBinaryCache
if (!IS_WINDOWS) {
_gitBinaryCache = findOnPath('git') || 'git'
return _gitBinaryCache
}
const localAppData = process.env.LOCALAPPDATA || ''
const candidates = []
if (localAppData) {
candidates.push(path.join(localAppData, 'hermes', 'git', 'cmd', 'git.exe'))
candidates.push(path.join(localAppData, 'hermes', 'git', 'bin', 'git.exe'))
}
candidates.push(path.join(process.env['ProgramFiles'] || 'C:\\Program Files', 'Git', 'cmd', 'git.exe'))
candidates.push(path.join(process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)', 'Git', 'cmd', 'git.exe'))
if (localAppData) {
candidates.push(path.join(localAppData, 'Programs', 'Git', 'cmd', 'git.exe'))
}
_gitBinaryCache = candidates.find(fileExists) || findOnPath('git') || 'git'
return _gitBinaryCache
}
function recentHermesLog() {
return hermesLog.slice(-20).join('\n')
}
@ -962,7 +991,7 @@ function resolveUpdateRoot() {
function runGit(args, options = {}) {
return new Promise((resolve, reject) => {
const child = spawn('git', IS_WINDOWS ? ['-c', 'windows.appendAtomically=false', ...args] : args, {
const child = spawn(resolveGitBinary(), IS_WINDOWS ? ['-c', 'windows.appendAtomically=false', ...args] : args, {
cwd: options.cwd,
env: { ...process.env, ...(options.env || {}), GIT_TERMINAL_PROMPT: '0' },
stdio: ['ignore', 'pipe', 'pipe']

View file

@ -29,12 +29,22 @@ export function PageSearchShell({
className={cn('flex h-full min-w-0 flex-col overflow-hidden bg-(--ui-chat-surface-background)', className)}
>
<div className="relative z-10 grid gap-2 border-b border-(--ui-stroke-tertiary) px-3 py-2.5">
<PageSearchInput
onChange={onSearchChange}
placeholder={searchPlaceholder}
trailingAction={searchTrailingAction}
value={searchValue}
/>
{/* Reserve the top-right titlebar tools + native window-controls
footprint so the full-width search input never slides under them
(this header sits in the titlebar row at the window top). */}
<div
style={{
paddingRight:
'max(0px, calc(var(--titlebar-tools-right, 0px) + var(--titlebar-tools-width, 0px) - 0.75rem))'
}}
>
<PageSearchInput
onChange={onSearchChange}
placeholder={searchPlaceholder}
trailingAction={searchTrailingAction}
value={searchValue}
/>
</div>
{filters ? <div className="flex flex-wrap items-center justify-center gap-1.5">{filters}</div> : null}
</div>
<div className="min-h-0 flex-1 overflow-hidden bg-(--ui-chat-surface-background)">{children}</div>