feat(sync,ui): show ahead and behind counts on sync buttons
Brought to you by:
thebguy
Originally created by: theBGuy
Originally owned by: theBGuy
Surface branch synchronization counts directly on the actions that use them, so users can immediately see what will be pushed or pulled without interpreting separate badges. Reorders the controls to prioritize Push and Pull while preserving clear disabled-state guidance and accessible labels.
src/features/repository/SyncControls.tsx to display ahead counts on the Push or Publish branch button and behind counts on the Pull button.src/features/repository/SyncControls.tsx to Push, Pull, Pull options, and Fetch.src/features/help/content.ts to describe the reordered controls and inline counts.README.md to document counts on the Push and Pull buttons.changelog.d/changed-sync-header-buttons.md describing the control reorder and count placement.src/features/repository/BranchSwitcher.tsx to match the new header button terminology.
Originally posted by: theBGuy
π€ GitDesktop AI security audit Β·
opusΒ· automatedNo security issues found in these changes. The diff is a UI-only refactor of the sync buttons (ahead/behind counts moved onto the Push/Pull buttons); all interpolated values (
aheadCount,behindCount,head?.upstream) are rendered as React text/attribute content, which is auto-escaped β nodangerouslySetInnerHTML, no injection sink, no auth/secret/data-flow surface touched.Posted by GitDesktop β AI output, verify before acting on it.
Originally posted by: cloudflare-workers-and-pages[bot]
Deploying gitdesktop with Β
Β Cloudflare Pages
c2e1019View logs
Originally posted by: theBGuy
Context for reviewers β deliberate calls in this change, with the reasoning on record:
<span title>wrappers because tooltip text must survive natively-disabled buttons (which swallow their owntitle). Those spans have nodata-slot, so they opt out of the vendoredButtonGroup's border-collapse/rounding child selectors (*:data-slot:*/[&>[data-slot]~[data-slot]]:*) β relying on the primitive is what made the caret visually fuse to Fetch in an intermediate revision. The explicitrounded-*-none border-l-0classes on each Button plusfocus-visible:relative focus-visible:z-10(replacing the group's unreachable*:focus-visible:z-10) are therefore load-bearing, documented at the group. Suggestions to "let ButtonGroup handle this" would reintroduce the fused-to-Fetch bug.titleand the Buttonaria-label(diverged β reason + behind count + remedy; detached; no-upstream; plain count; elseundefined= no tooltip, no aria-label β the visible label suffices). The visible count spans arearia-hiddenso the accessible name stays the composed sentence, and every string starts with the visible label (WCAG 2.5.3 label-in-name).β8icon+number pairs (compact row indicators); the header rendersβ Push β¦ 8(labeled action button with a trailing muted count). Different component classes, same data and tokens β reviewed by a three-lens design panel and accepted.head.ahead/behind), exactly what the old badges showed β no semantic change, presentation only. [#85]'s sync-vs-divergence split is untouched.# Fetch, pull, push & conflictsβ# Push, pull, fetch & conflicts(display-only; the sectionid/labelare separate).Verification:
pnpm buildgreen pre- and post-revision;tsc -b+ scoped biome clean; live-verified in the dev app β ahead-8 state renderedβ Push 8, post-revision cluster shows uniform 1px seams with rounded outer caps only, and the wrapper-span tooltip mechanism confirmed rendering on hover.Posted by GitDesktop β automated agent comment, verify before acting on it.
Related
Tickets:
#85Originally posted by: theBGuy
π€ GitDesktop AI review Β·
opusΒ· automatedSummary
This reworks
SyncControlsto move the ahead/behind counts onto the Push and Pull buttons (replacing the standalone animated badges), reorders the header to Push / Pull / Fetch, and manages the ButtonGroup seams manually because each button is now wrapped in atitlespan. The change is sound and non-blocking β the logic is careful and the accessibility handling (sharedtitle/aria-label,aria-hiddencount digits, label-in-name ordering) is well thought through. Only minor nits below.Correctness (verified clean β no action needed)
The label strings interpolate
${head?.upstream}, andupstreamis typedstring | nullβ but every interpolation is gated behind a nonzero count ordiverged, and git only reportsahead/behind > 0(and hencediverged) when a live upstream exists, so a literal"null"can't surface here. The manualborder-l-0seam classes correctly replace ButtonGroup's[&>[data-slot]~[data-slot]]:border-l-0, which no longer matches now that the direct children aredata-slot-less spans; borders collapse and there's no gap since the group adds none. UnusedBadge/motionimports were removed cleanly. Frontend has no test runner per project posture, so no missing-test finding.Readability
nit β
SyncControls.tsx, the ButtonGroup comment (~lines 256β257): it says the call site owns "rounded outer caps," but the baseButtonis square (rounded-noneat the cva root and in thesmsize variant), so therounded-r-none/rounded-l-none/rounded-noneclasses are inert no-ops β only theborder-l-0joins are actually load-bearing. A future maintainer reading this could believe corner-rounding is being managed when it isn't. Suggest trimming the "rounded outer caps" phrasing to reflect that the group is square and only the left-border collapse matters.nit β
src/features/help/content.ts,syncingsection (lines 491β497): the intro now reads "Push / Pull / Fetch," but the bullet list immediately below still explains them in the old Fetch β Pull β Push order. Given the reorder's stated rationale ("most-used action first"), reorder the bullets to Push / Pull / Fetch so the guide is internally consistent.Posted by GitDesktop β AI output, verify before acting on it.
Originally posted by: theBGuy
Round-1 dispositions (fixes land in the next push, alongside the guide-heading reorder already announced above):
rounded-*classes / "rounded outer caps" comment) β accepted, with an upgrade. Verified against the vendored cva:rounded-noneat the Button root and in thesmvariant, so the corner classes were indeed no-ops (and the context comment's "rounded outer caps" phrasing above was wrong β the caps are square, and were before this PR too). Rather than only rewording the comment, the inertrounded-r-none/rounded-none/rounded-l-noneutilities are removed from all four buttons β dead classes claiming un-honored intent are exactly the trap the comment warns about. The comment now states the Button is square and only theborder-l-0joins are load-bearing. No visual change (verified no-op removal;tsc -b+ scoped biome green).upstreamGonefinding β accepted and fixed (see the resolved thread): dedicated gone-upstream arm inpullDescription, distinguishing configured-but-dead from never-published.The review's correctness section (no
"null"interpolation reachable; seam mechanics; import cleanup) matches my own trace β nothing further to add there.Posted by GitDesktop β automated agent comment, verify before acting on it.
Originally posted by: theBGuy
π€ GitDesktop AI review Β·
opusΒ· automatedThis is a clean, well-reasoned refactor: it moves the ahead/behind counts onto the Push/Pull buttons, reorders the group to Push / Pull / Fetch, and manages the ButtonGroup seams manually because each segment is now wrapped in a
titlespan. I traced the label logic, the seam classes, the accessibility handling, and the import cleanup against the actual code β it's sound and nothing is blocking.Verification notes (no action needed)
${head?.upstream}interpolation is safe. Every string that interpolates the upstream ref is gated behind a state where the backend guarantees a non-nullupstream:aheadLabel/behindLabelrequireahead>0/behind>0(which git only reports against a live tracking ref),divergedrequires both counts>0, and the newupstreamGonearm fires exactly when the upstream is configured-but-dead (upstreamnon-null by contract, perBranchHeaddocs). No"null"can surface.rounded-r-none/rounded-none/rounded-l-noneis a genuine no-op. Confirmed insrc/components/ui/button.tsx:rounded-noneis in the cva root and in thesmsize variant, so those utilities were inert.border-l-0on the three inner joins (Pull, Pull-options caret, Fetch) is the only load-bearing seam class, and Push correctly omits it as the first segment β the comment now matches reality.upstreamGonePull arm is reachable and correct.hasUpstream = Boolean(head?.upstream) && !head?.upstreamGoneisfalsewhen gone, so the Pull button is disabled, and the dedicated arm (ahead of!hasUpstream) explains why rather than falsely claiming "never published."m/AnimatePresence/Badge/quickTransition, andArrowUpIcon/ArrowDownIconare still both imported and used.syncingsection (heading, intro, and bullets all now in Push/Pull/Fetch order), and the changelog fragment are consistent with the new UI.Resolved since last review
rounded-*classes / "rounded outer caps" comment (previous nit 1) β the current diff removes those utilities from all four buttons and rewrites the comment to state the Button is square and onlyborder-l-0is load-bearing.upstreamGonetreated like an untracked branch (Copilot,SyncControls.tsx:165) β now has its own arm producing "upstream<ref>was deleted on the remote (likely merged); use Publish branch to recreate it."No new findings.
Posted by GitDesktop β AI output, verify before acting on it.
Ticket changed by: theBGuy