Skip to content

improvements: parallelize refreshGit sub-calls and gate polling on focus#166

Merged
jmaxdev merged 1 commit intoTrixtyAI:mainfrom
matiaspalmac:improvements/parallel-git-refresh
Apr 21, 2026
Merged

improvements: parallelize refreshGit sub-calls and gate polling on focus#166
jmaxdev merged 1 commit intoTrixtyAI:mainfrom
matiaspalmac:improvements/parallel-git-refresh

Conversation

@matiaspalmac
Copy link
Copy Markdown
Contributor

[Improvement]: Parallelize refreshGit sub-calls and gate polling on focus

Description

refreshGit in GitExplorerComponent.tsx awaited four independent git
invocations in sequence:

  1. get_git_status
  2. get_git_branches
  3. git_log
  4. git_stash_list

Each one spawns its own backend git child process, so the chain took
roughly the sum of the four call latencies — measured in the
~150-400 ms range per 5 s poll cycle on realistic repos. Running them
concurrently drops that to roughly the max of any single call, and
the interval itself still ran full-speed while the window was visible
but unfocused, which dominated idle CPU on battery.

Change

All in apps/desktop/src/addons/builtin.git-explorer/GitExplorerComponent.tsx:

  • Parallel dispatch. The four sub-queries now go through
    Promise.allSettled together instead of sequential awaits. A
    failure in any one no longer aborts the rest — git_log on a repo
    with zero commits used to throw and silently drop status parsing
    mid-flight; now each result is handled independently.
  • Status drives the fallback. The "dubious ownership" detection
    and the no-repo empty-state still key off the get_git_status
    rejection specifically. The other three degrade to empty arrays if
    they fail, which matches the old per-call try/catch for log and
    stashes.
  • Tighter polling guard. The 5 s interval now also checks
    document.hasFocus(). visibilityState === "visible" was too loose:
    a visible-but-unfocused window (user typing in the browser) still
    cost a full git refresh every 5 s.
  • Snap back on refocus. Added a window.addEventListener("focus", …) handler that triggers a refresh immediately when the user
    returns, instead of showing up-to-5-s-stale data until the next
    tick.

Trade-offs

  • Point 3 of the issue suggested collapsing the four calls into a
    single Rust get_git_snapshot command that runs them concurrently
    inside tokio. That is a larger change (new command, new type
    bindings, more CI surface) and is out of scope for this PR — the
    JS-side Promise.allSettled already captures the performance win
    with a single-file diff.
  • Promise.allSettled fires all four processes even on a non-git
    directory (the get_git_status rejection branch only runs after
    all four settle). That is a wash in practice: the three other calls
    fail fast on the same "not a git repository" error path the status
    call takes.
  • The focus-based refresh triggers on every window focus while the
    git tab is active. That is intentional — users expect fresh state
    after stepping away — and the existing gitLoadingRef guard
    prevents overlapping refreshes from stacking up.

Verification

  • pnpm tsc --noEmit across the desktop app → clean.
  • pnpm eslint on the touched file → 0 findings.
  • Manually traced all four Promise.allSettled branches:
    • All succeed → state updated exactly as before.
    • Only git_log fails → log clears, rest renders.
    • get_git_status fails with "dubious ownership" → existing confirm
      dialog fires and retries.
    • get_git_status fails with "not a git repository" → silent
      empty-state branch, same as before.

Related Issue

Closes #84

Checklist

  • I have checked for existing feature requests.
  • I have followed the project's coding guidelines.
  • Documentation has been updated (if applicable).
  • My changes generate no new warnings or errors.

Copilot AI review requested due to automatic review settings April 21, 2026 02:30
@github-actions github-actions bot added documentation Improvements or additions to documentation enhancement New feature or request labels Apr 21, 2026
@github-actions
Copy link
Copy Markdown

Thanks for the contribution! I'll review it as soon as possible. If you have still changes, please mark this PR as draft and all reviews will be cancelled. Tests reviews will be re-run only when the PR is marked as ready for review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the Git Explorer’s periodic refresh behavior by reducing backend git process latency and avoiding refresh work when the app isn’t focused, improving responsiveness and lowering idle CPU usage in the desktop app.

Changes:

  • Parallelized the four independent refreshGit backend invocations via Promise.allSettled, handling each result independently.
  • Updated error handling so get_git_status remains the source of truth for “not a repo” / “dubious ownership” fallback behavior.
  • Tightened polling to require document.hasFocus() and added an immediate refresh on window focus.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

refreshGit awaited four independent git invocations in sequence
(get_git_status, get_git_branches, git_log, git_stash_list). Each
spawns its own backend `git` child process, so the chain took roughly
the sum of the four call latencies — ~150-400 ms per 5 s cycle on
realistic repos. Running them concurrently drops that to roughly the
max of any single call.

Changes:

- Use Promise.allSettled to dispatch the four sub-queries in parallel.
  A failure in any one no longer aborts the rest (git_log on a repo
  with zero commits used to throw and silently drop status parsing
  mid-flight; now each result is handled independently).
- The "dubious ownership" detection and the no-repo fallback still key
  off the get_git_status result; the other three just degrade to
  empty arrays if they fail.
- Tighten the interval guard: poll only when the window is visible AND
  document.hasFocus() is true. A visible-but-unfocused window (user
  typing in the browser) was still costing a full git refresh every
  5 s, which dominated idle CPU on battery.
- Add a focus listener that snaps back to a fresh refresh as soon as
  the user returns, instead of displaying up-to-5-s-stale data until
  the next tick.
@jmaxdev jmaxdev force-pushed the improvements/parallel-git-refresh branch from e875e11 to 18a5139 Compare April 21, 2026 03:25
@jmaxdev jmaxdev merged commit 40adb30 into TrixtyAI:main Apr 21, 2026
1 check passed
matiaspalmac added a commit to matiaspalmac/ide that referenced this pull request Apr 21, 2026
…ent overlapping refreshes

Addresses review feedback on TrixtyAI#166: the interval tick and the new
window-focus handler both checked `gitLoadingRef.current`, which
tracks *user-initiated* git operations (commit, checkout, stash). That
flag is not set during a polling refresh, so if the user switched
focus back while a refresh was still in flight, the focus handler
kicked off a second `refreshGit()` and the two polling refreshes
raced. Whichever `Promise.allSettled` resolved second clobbered the
first's state updates, sometimes displaying stale branch/log data.

Added a separate `gitRefreshingRef` that is:

- flipped to `true` at the top of `refreshGit` and reset in a
  `finally` at the bottom, so the flag accurately mirrors the
  refresh's lifetime including the error/early-return paths.
- checked by both the 5 s interval and the focus handler before
  calling `refreshGit()`, so a pending refresh is never stacked with
  a new one.

The dubious-ownership retry path (which calls `refreshGit()`
recursively) clears the flag before the recursive call so the inner
refresh isn't a no-op.

`gitLoadingRef` stays distinct and continues to gate the interval
during user-initiated operations — the two flags track different
things and both matter.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Improvement]: Parallelize refreshGit sub-calls with Promise.all and pause when unfocused

3 participants