Desktop Material

Repository list sync summary

Every row in the repository side sheet carries a small, low-emphasis second line under the repository name summarizing how far that repository has drifted from its tracked upstream: how many commits are waiting to be pushed, and how many are waiting at the remote to be pulled.

Before this line existed, drift was only visible after selecting a repository and reading the push/pull toolbar button, so in a workspace with dozens of repositories the one that actually needed attention was invisible until you went looking for it.

Behavior

The line reads the same ILocalRepositoryState snapshot the push/pull toolbar button is built from — the ahead/behind counts recorded by the repository indicator refresh — rather than deriving a second source of truth.

States

State When English (funny level 1)
Ahead Tracking branch, ahead only 2 commits to push, nothing to pull
Behind Tracking branch, behind only 3 commits to pull, nothing to push
Diverged Tracking branch, both 2 commits to push, 3 commits to pull
In sync Tracking branch, both zero In sync as of the last check
Unknown No status has been read, or a tracking branch with no recorded counts Sync state unknown, not checked yet
No upstream On a branch with no tracking branch No upstream branch
Detached Detached HEAD Detached HEAD, no branch to compare
Empty Unborn HEAD (no commits yet) No commits yet
Cloning The row is a clone in progress Cloning, sync state not known yet
Missing The repository is missing from disk Missing from disk, sync state unknown

Unknown is never rendered as 0 and never as "in sync." That is the whole point of the state: a fabricated zero tells the user they are up to date when nobody has looked. ILocalRepositoryState.upstreamState exists precisely so the row can tell "there is nothing to report" apart from "nobody has looked yet" — aheadBehind alone is null in both cases. A repository with no cache entry at all resolves to 'unknown'; a repository whose HEAD tracks an upstream but has no recorded counts also resolves to 'unknown', not to zero.

"In sync" is deliberately worded as as of the last check, because the row reflects the last known remote state and cannot prove present-tense freshness.

Deliberately no network

Rendering this line performs no fetch, no Git invocation, and no dispatcher call. It is a pure derivation of already-loaded state (app/src/ui/repositories-list/repository-sync-summary.ts imports nothing from lib/git, the API, or the dispatcher, and a test asserts that).

A list of forty repositories must not become forty network calls, and typing in the filter box must not trigger any. Freshness therefore rides entirely on the existing background-fetch cadence — RepositoryIndicatorUpdater and the repository indicator refresh — which already updates the same state cache the line reads.

Layout, localization, and accessibility

Performance

The list re-renders on every keystroke in the filter box, so nothing here may scale with typing:

Failure modes

Cloning rows and repositories without a gitHubRepository are ordinary members of this list and are handled explicitly; a missing repository outranks whatever stale counts survive in the cache. Corrupt or absent persisted audio settings fall back to the default funny level rather than throwing, and an absent upstreamState prop falls back to 'unknown' — the honest state — rather than to a zero.

Verification

app/test/unit/repository-sync-summary-test.ts covers state derivation for ahead/behind/diverged/in-sync, singular and plural commit grammar, the status-to-upstream-state mapping, per-language funny bands, the accessible-name sentence, both translation catalogs being complete and distinct, the no-network source contract, and the SCSS geometry.

app/test/unit/ui/repository-list-sync-summary-test.tsx renders the row and the list: exact text per state, the unknown state asserted to contain no digit and no "in sync" wording, no-upstream/detached/empty/cloning/missing rows rendering without crashing, the summary folded into each row's accessible name, and no network call (spied fetch, plus a recording dispatcher proxy) during render or while filtering.