Skip to content

fix(branches): keep the behind-count and Rebase button visible during git refresh - #908

Merged
matt2e merged 3 commits into
mainfrom
rebase-button-hidden
Aug 5, 2026
Merged

fix(branches): keep the behind-count and Rebase button visible during git refresh#908
matt2e merged 3 commits into
mainfrom
rebase-button-hidden

Conversation

@matt2e

@matt2e matt2e commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Problem

BranchCard zeroed parentAheadCount while a background git-state refresh was in flight, so the +N behind-count — and with it the Rebase button, which is gated on parentAheadCount > 0 — vanished from the card header and then popped back once the refresh landed. Every branch-card refresh flickered the primary action out from under the user.

A second, related bug made this worse: refreshBranchGitState resolves Ok without emitting git-state-updated when a branch has no workdir or its worktree path no longer exists. Since refreshingGitState was only cleared on the event (or on rejection), those branches kept the flag set forever — a perpetual spinner, and after the first fix, a permanently dimmed count.

Changes

  • Show the real count during refresh. Pass timeline?.gitState?.base.commitsSinceFork straight through instead of substituting 0. The count and Rebase button stay put; the refresh updates them in place.
  • Signal staleness without hiding it. The +N capsule dims to 60% opacity (.ahead-count.provisional) while refreshing, and its tooltip becomes <base> · N commits behind (checking for updates…).
  • Clear the flag on settle, not on the event. Both refresh call sites now use .catch(() => {}).finally(...), so the spinner and dimming stop even on the no-workdir arms. The command resolves only after the impl (including the emit, when there is one) completes, so the happy path still merges fresh state. The git-state-updated listener stays for refreshes triggered outside the card — bulk refresh on project open, post-push force refresh.

matt2e added 2 commits August 5, 2026 11:40
… refresh

The branch card header zeroed `parentAheadCount` while a background git-state
refresh was in flight, which hid both the "+N" pill and the Rebase button until
the fetch completed — several seconds per repo on project switch, despite a
perfectly good last-known count already being cached.

Pass the cached `commitsSinceFork` through unconditionally. The separate
spinner still signals that a refresh is running, and the count reconciles (or
the pill and button slide away) when the `git-state-updated` event lands. The
genuine placeholder path from the fast timeline stream still reports 0, so
first-ever loads render nothing until the fetch finishes, as before.

While refreshing, dim the count and extend the capsule tooltip with
"(checking for updates…)" so the value reads as provisional.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
…t an event

refresh_branch_git_state_impl returns Ok without emitting git-state-updated
when the branch has no workdir or its worktree path no longer exists. For such
branches the refreshingGitState flag never cleared, leaving a perpetual
spinner — and, since the previous commit, a permanently dimmed behind-count
with a stale "(checking for updates…)" tooltip.

Clear the flag when the refresh promise settles instead of only on rejection.
Both the desktop command and the web-mode dispatch resolve only after the
impl — including the emit, when there is one — has completed, so on the happy
path the event still lands with (or just after) settlement and merges the
fresh state; on the no-workdir arms the spinner and dimming now stop instead
of sticking forever. The event listener remains for refreshes triggered
outside the card (bulk refresh on project open, post-push force refresh).

Signed-off-by: Matt Toohey <contact@matttoohey.com>
@matt2e
matt2e requested review from baxen and wesbillman as code owners August 5, 2026 02:18
…refreshes

Two card-initiated git-state refreshes can overlap: the mount-time hydration
path in applyCachedTimeline can still be mid-fetch when a session completion
or timeline-invalidated event re-runs loadTimeline and kicks off a second
call. The first refresh to finish then ended the spinner and the provisional
dimming early — via its .finally() and via its git-state-updated event, whose
listener also cleared the flag unconditionally.

Extract the duplicated kick-off block into startGitStateRefresh() and guard
the settle-time clear with a generation counter, matching the
revalidationVersion pattern already used for timeline revalidation: only the
newest refresh's settlement clears the flag. Drop the clear from the event
listener — settlement-based clearing covers every card-initiated refresh
(the command resolves only after the emit, verified in the previous commit),
and an event from an external refresh (bulk refresh on project open,
post-push force refresh) must not end the spinner while this card's own
refresh is still in flight. The listener still merges the fresh git state.

Signed-off-by: Matt Toohey <contact@matttoohey.com>
@matt2e
matt2e merged commit d0f6929 into main Aug 5, 2026
4 checks passed
@matt2e
matt2e deleted the rebase-button-hidden branch August 5, 2026 03:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant